blog-static/content/blog/05_spa_agda_semantics/parser.js

129 lines
3.4 KiB
JavaScript
Raw Permalink Normal View History

const match = str => input => {
if (input.startsWith(str)) {
return [[str, input.slice(str.length)]]
}
return [];
};
const map = (f, m) => input => {
return m(input).map(([v, rest]) => [f(v), rest]);
};
const apply = (m1, m2) => input => {
return m1(input).flatMap(([f, rest]) => m2(rest).map(([v, rest]) => [f(v), rest]));
};
const pure = v => input => [[v, input]];
const liftA = (f, ...ms) => input => {
if (ms.length <= 0) return []
let results = map(v => [v], ms[0])(input);
for (let i = 1; i < ms.length; i++) {
results = results.flatMap(([vals, rest]) =>
ms[i](rest).map(([val, rest]) => [[...vals, val], rest])
);
}
return results.map(([vals, rest]) => [f(...vals), rest]);
};
const many1 = (m) => liftA((x, xs) => [x].concat(xs), m, oneOf([
lazy(() => many1(m)),
pure([])
]));
const many = (m) => oneOf([ pure([]), many1(m) ]);
const oneOf = ms => input => {
return ms.flatMap(m => m(input));
};
const takeWhileRegex0 = regex => input => {
let idx = 0;
while (idx < input.length && regex.test(input[idx])) {
idx++;
}
return [[input.slice(0, idx), input.slice(idx)]];
};
const takeWhileRegex = regex => input => {
const result = takeWhileRegex0(regex)(input);
if (result[0][0].length > 0) return result;
return [];
};
const spaces = takeWhileRegex0(/\s/);
const digits = takeWhileRegex(/\d/);
const alphas = takeWhileRegex(/[a-zA-Z]/);
const left = (m1, m2) => liftA((a, _) => a, m1, m2);
const right = (m1, m2) => liftA((_, b) => b, m1, m2);
const word = s => left(match(s), spaces);
const end = s => s.length == 0 ? [['', '']] : [];
const lazy = deferred => input => deferred()(input);
const ident = left(alphas, spaces);
const number = oneOf([
liftA((a, b) => a + b, word("-"), left(digits, spaces)),
left(digits, spaces),
]);
const basicExpr = oneOf([
map(n => `lit(${n})`, number),
map(x => `var(${x})`, ident),
liftA((lp, v, rp) => v, word("("), lazy(() => expr), word(")")),
]);
const opExpr = oneOf([
liftA((_a, _b, e) => ["plus", e], word("+"), spaces, lazy(() => expr)),
liftA((_a, _b, e) => ["minus", e], word("-"), spaces, lazy(() => expr)),
]);
const flatten = (e, es) => {
return es.reduce((e1, [op, e2]) => `${op}(${e1}, ${e2})`, e);
}
const expr = oneOf([
basicExpr,
liftA(flatten, basicExpr, many(opExpr)),
]);
const basicStmt = oneOf([
liftA((x, _, e) => `assign(${x}, ${e})`, ident, word("="), expr),
word("noop"),
]);
const stmt = oneOf([
basicStmt,
liftA((_if, _lp_, cond, _rp, _lbr1_, s1, _rbr1, _else, _lbr2, s2, _rbr2) => `if(${cond}, ${s1}, ${s2})`,
word("if"), word("("), expr, word(")"),
word("{"), lazy(() => stmtSeq), word("}"),
word("else"), word("{"), lazy(() => stmtSeq), word("}")),
liftA((_while, _lp_, cond, _rp, _lbr_, s1, _rbr) => `while(${cond}, ${s1})`,
word("while"), word("("), expr, word(")"),
word("{"), lazy(() => stmtSeq), word("}")),
]);
const stmtSeq = oneOf([
liftA((s1, _semi, rest) => `seq(${s1}, ${rest})`, stmt, word(";"), lazy(() => stmtSeq)),
stmt,
]);
const parseWhole = m => string => {
const result = left(m, end)(string);
console.log(result);
if (result.length > 0) return result[0][0];
return null;
}
window.parseExpr = parseWhole(expr);
window.parseBasicStmt = parseWhole(basicStmt);
window.parseStmt = parseWhole(stmtSeq);