UNPKG

functionalscript

Version:

FunctionalScript is a purely functional subset of JavaScript

208 lines (207 loc) 5.95 kB
/** * Rules for serializing and deserializing the BNF grammar. * * @module * * @example * * ```ts * // Define a simple grammar * const grammar: Rule = () => [ * [range('AZ')], // 'A-Z' * [range('az'), grammar], // 'a-z' followed by more grammar * ] * * const parse = parser(toRuleMap(grammar)) * * // Parse an input * const input = toArray(stringToCodePointList('abcdefgA')) * const result = parse(grammar.name, input) * if (result === null) { throw result } * const [, b] = result * if (b?.length !== 0) { throw b } * ``` */ import { empty as emptyArray } from "../../types/array/module.f.js"; import { strictEqual } from "../../types/function/operator/module.f.js"; import { toArray } from "../../types/list/module.f.js"; import { contains } from "../../types/range/module.f.js"; import { rangeMap } from "../../types/range_map/module.f.js"; const findName = (map, rule) => { const { name } = rule; let result = name; { let i = 0; while (result in map) { result = name + i; ++i; } } return result; }; /** * Add a new rule to the temporary map. */ const tmpAdd = ({ queue, result }) => (src) => { // find a name for the item. const name = findName(result, src); return [{ // add the item to a queue under the name. queue: [...queue, [src, name]], // add the name to the result and fill the rule later. result: { ...result, [name]: emptyArray }, }, name]; }; const tmpNew = tmpAdd({ queue: emptyArray, result: {}, }); const tmpItem = (tmp, src) => { const found = tmp.queue.find(([f]) => f === src); return found !== undefined ? [tmp, found[1]] : tmpAdd(tmp)(src); }; /** * Transforming functional rule to a serializable rule map. * * @param src a functional rule. * @returns a serializable rule map. */ export const toRuleMap = (src) => { let [tmp] = tmpNew(src); let i = 0; do { const [srcOr, name] = tmp.queue[i]; let rule = emptyArray; // iterate all sequences of the `Or` rule. for (const srcSeq of srcOr()) { let s = emptyArray; // iterate all items of the sequence. for (const srcItem of srcSeq) { let item; if (srcItem instanceof Array) { item = srcItem; } else { [tmp, item] = tmpItem(tmp, srcItem); } s = [...s, item]; } rule = [...rule, s]; } // fix the rule in the result. tmp = { queue: tmp.queue, result: { ...tmp.result, [name]: rule }, }; ++i; } while (i !== tmp.queue.length); return tmp.result; }; const dispatchOp = rangeMap({ union: a => b => { if (a === null) { return b; } if (b === null) { return a; } throw ['can not merge [', a, '][', b, ']']; }, equal: strictEqual, def: null, }); /** * Creates a dispatch map for LL1 parser. * * @param ruleMap a serializable rule map. * @returns A dispatch map */ const dispatchMap = (ruleMap) => { const dispatchSequence = (dm, sequence) => { let empty = true; let result = []; for (const item of sequence) { if (typeof item === 'string') { dm = dispatchRule(dm, item); const [e, dispatch] = dm[item]; result = toArray(dispatchOp.merge(result)(dispatch.map(x => [x[0] === null ? null : sequence, x[1]]))); if (e) { continue; } } else { const dispatch = dispatchOp.fromRange(item)(sequence); result = toArray(dispatchOp.merge(result)(dispatch)); } empty = false; break; } return [dm, [empty, result]]; }; const dispatchRule = (dm, name) => { if (name in dm) { return dm; } let empty = false; let dispatch = []; for (const sequence of ruleMap[name]) { const [newDm, [e, d]] = dispatchSequence(dm, sequence); dm = newDm; empty ||= e; dispatch = toArray(dispatchOp.merge(dispatch)(d)); } return { ...dm, [name]: [empty, dispatch] }; }; let result = {}; for (const k in ruleMap) { result = dispatchRule(result, k); } // TODO: validate all sequences if they are deterministic return result; }; /** * Creates a simple LL1 parser for the given map. * * @param map a prepared rule map. * @returns a parser. */ export const parser = (rm) => { const map = dispatchMap(rm); const f = (name, s) => { const mr = (a, r) => [[name, a], r]; const mre = (a) => mr(a, null); const [empty, sequence] = map[name]; if (s.length === 0) { return mr([], empty ? s : null); } const cp = s[0]; const i = dispatchOp.get(cp)(sequence); if (i === null) { return mr([], empty ? s : null); } let a = []; let si = s; for (const c of i) { if (typeof c === 'string') { // c is a name const [astRule, newSi] = f(c, si); a = [...a, astRule]; if (newSi === null) { return mre(a); } si = newSi; } else { // c is TerminalRange const [first, ...newSi] = si; if (first === undefined || !contains(c)(first)) { return mre(a); } a = [...a, first]; si = newSi; } } return mr(a, si); }; return f; };