Skip to content

Instantly share code, notes, and snippets.

@piranha
Created June 11, 2023 16:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save piranha/81544fca192032a6a461999c1cfdb625 to your computer and use it in GitHub Desktop.
Save piranha/81544fca192032a6a461999c1cfdb625 to your computer and use it in GitHub Desktop.
Simple recursive descent in JavaScript
const test = require('node:test');
const assert = require('node:assert').strict;
let RE = /[\s,()]/;
function tokenize(s) {
var tokens = []
let j = 0;
for (var i = 0; i < s.length; i++) {
if (RE.exec(s[i])) {
if (i != j) {
let x = s.slice(j, i);
if (x == '+' || x == '-')
tokens.push({op: x})
else
tokens.push({term: x})
}
if (s[i] != ' ')
tokens.push({sep: s[i]})
j = i + 1;
}
}
if (j < i) {
tokens.push({term: s.slice(j, i)})
}
return tokens;
}
function parse(s) {
let i = 0;
let tokens = tokenize(s);
function consume(k, x, optional) {
if (peek(k, x))
i++;
else if (!optional)
throw new Error(
`Expected '{${k}: ${x}}', got '${JSON.stringify(tokens[i])}'`)
}
function peek(k, x) {
if (x == undefined)
return !!tokens[i][k]
return tokens[i][k] == x;
}
function parseChildren() {
var children = [];
while (!(peek('sep', ')'))) {
children.push(parseExpr());
if (peek('sep', ','))
consume('sep', ',')
}
return children;
}
function parseTerm() {
if (!tokens[i].term)
throw new Error(`not a term: ${s[i]}`);
return tokens[i++].term;
}
function parseExpr() {
let left;
if (peek('sep', '(')) {
consume('sep', '(');
left = parseExpr();
consume('sep', ')');
} else {
left = parseTerm();
}
if (i == tokens.length)
return left;
if (peek('sep', '(')) {
consume('sep', '(');
let children = parseChildren();
consume('sep', ')');
return {func: left, children}
}
if (peek('op')) {
let op = tokens[i++].op;
let right = parseExpr();
return {op, right, left};
}
return left;
}
return parseExpr();
}
test('works', (t) => {
assert.deepEqual(
tokenize('a (b c)'),
[{term: 'a'}, {sep: '('}, {term: 'b'},
{term: 'c'}, {sep: ')'}])
assert.deepEqual(parse('abc'), 'abc');
assert.deepEqual(
parse('plus(a, qqq)'),
{func: 'plus', children: ['a', 'qqq']});
assert.deepEqual(
parse('plus(a, (var - c) + x, d)'),
{func: 'plus',
children: [
'a',
{op: '+',
left: {op: '-', left: 'var', right: 'c'},
right: 'x'},
'd'
]});
assert.deepEqual(
parse('(a + b) - (c + x)'),
{op: '-',
left: {op: '+',
left: 'a',
right: 'b'},
right: {op: '+',
left: 'c',
right: 'x'}})
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment