UNPKG

@thi.ng/sexpr

Version:

Extensible S-Expression parser & runtime infrastructure

61 lines (60 loc) 1.75 kB
import { isString } from "@thi.ng/checks/is-string"; import { unescape } from "@thi.ng/strings/escape"; import { DEFAULT_SYNTAX } from "./api.js"; import { tokenize } from "./tokenize.js"; class ParseError extends Error { line; col; constructor(msg, line, col) { super(msg); this.line = line; this.col = col; } } const parse = (src, opts) => { const { scopes } = { ...DEFAULT_SYNTAX, ...opts }; const scopeOpen = scopes.map((x) => x[0]); const scopeClose = scopes.map((x) => x[1]); const tree = [{ type: "root", children: [] }]; let currScope = -1; for (let token of isString(src) ? tokenize(src, opts) : src) { const t = token.value; let tmp; if ((tmp = scopeOpen.indexOf(t)) !== -1) { tree.push({ type: "expr", value: t, children: [] }); currScope = tmp; } else if ((tmp = scopeClose.indexOf(t)) !== -1) { if (tree.length < 2 || currScope !== tmp) { throw new ParseError(`unmatched '${t}'`, token.line, token.col); } tree[tree.length - 2].children.push(tree.pop()); currScope = scopeOpen.indexOf( tree[tree.length - 1].value ); } else { let node; let value; if (t.startsWith('"')) { node = { type: "str", value: unescape(t.substring(1, t.length - 1)) }; } else if (t.startsWith("0x") && !isNaN(value = parseInt(t.substring(2), 16)) || !isNaN(value = parseFloat(t))) { node = { type: "num", value }; } else { node = { type: "sym", value: t }; } tree[tree.length - 1].children.push(node); } } if (tree.length > 1) { throw new ParseError("unclosed s-expression", -1, -1); } return tree[0]; }; export { ParseError, parse };