functionalscript
Version:
FunctionalScript is a purely functional subset of JavaScript
208 lines (207 loc) • 5.95 kB
JavaScript
/**
* 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;
};