Skip to content

Instantly share code, notes, and snippets.

@wavebeem
Created October 6, 2015 00:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wavebeem/b0d599377256b7fcc6b0 to your computer and use it in GitHub Desktop.
Save wavebeem/b0d599377256b7fcc6b0 to your computer and use it in GitHub Desktop.
This does not actually generate the correct AST because I was using arrays and dang does that get confusing over time, but this helped me figure out how to parse my binary operators correctly while also including unary operators at the right precedence level and make foo.bar foo[bar] and foo(bar) all chain together correctly.
"use strict";
const util = require("util");
const P = require("parsimmon");
console.log(Date() + "\n");
const _ = P.optWhitespace;
const tag = name => rest =>
[name].concat(rest)
const combine = (acc, xs) =>
[xs[0], acc].concat(xs.slice(1));
const spaced = p =>
_.then(p).skip(_);
const word = str =>
spaced(P.string(str));
const arrow = length => {
let s = "";
let n = length;
while (n-- > 0) {
s += "-";
}
s += "^";
return s;
};
const wrap = (l, x, r) =>
word(l).then(x).skip(word(r));
const Separator = word(",");
const list0 = p =>
P.seq(
p,
Separator.then(p).many()
)
.map(x => [x[0]].concat(x[1]))
.or(P.of([]));
const squish = xs =>
[xs[0]].concat(xs.slice(1)[0]);
const bh = (ops, next) => {
const opParsers = ops
.split(" ")
.map(op => word(op));
const combinedOpParser = P.alt.apply(P, opParsers);
return P.seq(
next,
P.seq(combinedOpParser, next).atLeast(1)
)
.map(xs => {
const firstArgument = xs[0];
const opSections = xs[1];
const combineOps = (acc, x) =>
["BinOp", x[0], acc].concat(x.slice(1));
return opSections.reduce(combineOps, firstArgument);
})
.or(next);
};
const parsers = {
Expr: ps =>
ps.OrExpr,
OrExpr: ps => bh("or", ps.AndExpr),
AndExpr: ps => bh("and", ps.EqExpr),
EqExpr: ps => bh("== !=", ps.HasExpr),
HasExpr: ps =>
ps.UnaryExpr,
UnaryExpr: ps =>
P.alt(
word("-").then(ps.PropCallExpr).map(tag("Negate")),
word("not").then(ps.PropCallExpr).map(tag("Not")),
ps.PropCallExpr
),
PropCallExpr: ps =>
P.seq(
ps.BasicExpr,
P.alt(
wrap("(", list0(ps.Expr), ")").map(tag("FnCall")),
wrap("[", ps.Expr, "]").map(tag("ComputedMember")),
word(".").then(ps.Identifier).map(tag("DotMember"))
).many()
)
.or(ps.BasicExpr),
BasicExpr: ps =>
ps.Literal.or(ps.ParenExpr),
Literal: ps =>
P.alt(ps.Identifier, ps.Number),
Identifier: ps =>
P.regex(/[a-zA-Z][a-zA-Z0-9]*/)
.desc("Identifier")
.map(tag("Identifier")),
Number: ps =>
P.regex(/0-9+/)
.desc("Number")
.map(tag("Number")),
ParenExpr: ps =>
word("(")
.then(ps.Expr)
.skip(word(")")),
};
Object
.keys(parsers)
.forEach(k => {
// Injected parsers as dependency.
const bound = parsers[k].bind(null, parsers);
parsers[k] = P.lazy(bound);
});
Object.freeze(parsers);
const inspectOpts =
Object.freeze({
depth: null,
colors: true
});
const inspect = x =>
util.inspect(x, inspectOpts);
const show = x => {
console.log(inspect(x));
};
const main = () => {
// const text = "\\";
// const text = "p1.x or p2.y or f(q) or global.document.querySelector(css)";
// const text = "p1.x + p2.y * f(q) * global.document.querySelector(css)";
// const text = "-player.vx * modifiers(player).vx";
// const text = "modifiers(player).vx";
// const text = "a.b.x(x).q + q.w";
// const text = "a and b or c and d and q or x == z != a == b";
// const text = "a == b or c or z";
// const text = "a or b or c";
// const text = "-a or b or c";
// const text = "a.b()"
// const text = "foo(a)";
// const text = "f.a";
// const text = "a.b[c](d).x(x)(x)[y][y][z].q";
const text = "foo.bar[baz].quux().q[q]";
const result = parsers.Expr.parse(text);
// const result = parsers.GetPropExpr.parse(text);
// const result = parsers.FnCall.parse(text);
if (result.status) {
show(result.value);
} else {
show(result);
console.log();
console.log(text);
console.log(arrow(result.index));
}
};
main();
module.exports = word;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment