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);