Skip to content

Instantly share code, notes, and snippets.

@susisu
Last active August 22, 2019 18:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save susisu/cb6b1256c37ee3269ce5dceeef9157da to your computer and use it in GitHub Desktop.
Save susisu/cb6b1256c37ee3269ce5dceeef9157da to your computer and use it in GitHub Desktop.
import * as lq from "@loquat/simple";
const key = Symbol("handle");
type PerformFunc = <U>(parser: lq.Parser<U>) => U;
export function handle<T>(func: (perform: PerformFunc) => T): lq.Parser<T> {
return new lq.StrictParser(state => {
let currentState = state;
let currentErr = lq.ParseError.unknown(state.pos);
let consumed = false;
const vals: any[] = [];
while (true) {
let i = 0;
try {
const val = func(<U>(parser: lq.Parser<U>): U => {
if (i < vals.length) {
const val = vals[i];
i += 1;
return val;
}
throw { key, parser };
});
return consumed
? lq.Result.csucc(currentErr, val, currentState)
: lq.Result.esucc(currentErr, val, currentState);
} catch (err) {
if (!err || err.key !== key) {
throw err;
}
const parser = err.parser as lq.Parser<any>;
const res = parser.run(currentState);
if (res.success) {
if (res.consumed) {
consumed = true;
currentState = res.state;
currentErr = res.err;
} else {
currentState = res.state;
currentErr = lq.ParseError.merge(currentErr, res.err);
}
vals.push(res.val);
} else {
if (res.consumed) {
return res;
} else {
return consumed
? lq.Result.cfail(lq.ParseError.merge(currentErr, res.err))
: lq.Result.efail(lq.ParseError.merge(currentErr, res.err));
}
}
}
}
});
}
// JSON parser
const interpretEscapes = (str: string): string => {
const escapes = new Map([
["b", "\b"],
["f", "\f"],
["n", "\n"],
["r", "\r"],
["t", "\t"],
]);
return str.replace(/\\(u[0-9a-fA-F]{4}|[^u])/g, (_, escape) => {
const type = escape.charAt(0);
const hex = escape.slice(1);
if (type === "u") {
return String.fromCharCode(parseInt(hex, 16));
}
if (escapes.has(type)) {
return escapes.get(type);
}
return type;
});
}
const whitespace = lq.regexp(/\s*/);
const lexeme = <T>(parser: lq.Parser<T>): lq.Parser<T> => parser.left(whitespace);
type JsonValue = {} | null;
const value: lq.Parser<JsonValue> = lq.lazy(() => lq.choice([
object,
array,
string,
number,
litNull,
litTrue,
litFalse
]));
const lbrace = lexeme(lq.char("{"));
const rbrace = lexeme(lq.char("}"));
const lbracket = lexeme(lq.char("["));
const rbracket = lexeme(lq.char("]"));
const comma = lexeme(lq.char(","));
const colon = lexeme(lq.char(":"));
const litNull = lexeme(lq.string("null")).return(null);
const litTrue = lexeme(lq.string("true")).return(true);
const litFalse = lexeme(lq.string("false")).return(false);
const string = lexeme(lq.regexp(/"((?:\\.|.)*?)"/, 1))
.map(interpretEscapes)
.label("string");
const number = lexeme(lq.regexp(/-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?/))
.map(parseFloat)
.label("number");
const array = handle(perform => {
perform(lbracket);
const elems = perform(value.sepBy(comma));
perform(rbracket);
return elems;
});
const keyValue = handle<[string, JsonValue]>(perform => {
const key = perform(string);
perform(colon);
const val = perform(value);
return [key, val];
});
const object = handle(perform => {
perform(lbrace);
const kvs = perform(keyValue.sepBy(comma));
perform(rbrace);
const obj = {};
for (const kv of kvs) {
Object.defineProperty(obj, kv[0], {
writable: true,
configurable: true,
enumerable: true,
value: kv[1],
})
}
return obj;
});
const json = handle(perform => {
perform(whitespace);
const val = perform(value);
perform(lq.eof);
return val;
});
const src = `
{
"string": "foo\\nbar\\uD83C\\uDF64",
"number": 42.42e+42,
"boolean": true,
"null": null,
"array": ["", 0, false],
"object": {
"foo": "bar",
"baz": 42
}
}
`;
const res = json.parse("", src, undefined, { unicode: false });
if (res.success) {
console.log(res.value);
} else {
console.log(res.error.toString());
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment