Created
June 11, 2023 16:48
-
-
Save piranha/81544fca192032a6a461999c1cfdb625 to your computer and use it in GitHub Desktop.
Simple recursive descent in JavaScript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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