Created
March 8, 2022 18:53
-
-
Save mtso/cdd28bb8b2d3834dfc3bc839386e85ff to your computer and use it in GitHub Desktop.
Lisp Parser - Write code that takes some Lisp code and returns an abstract syntax tree. The AST should represent the structure of the code and the meaning of each token. For example, if your code is given "(first (list 1 (+ 2 3) 9))", it could return a nested array like ["first", ["list", 1, ["+", 2, 3], 9]].
This file contains 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
// input "" -> null | |
// input "()" -> [] | |
// input "(1)" -> [1] | |
// 0123456 | |
// const input = "(first (list 1 (+ 2 3) 9))" | |
// example out: ["first", ["list", 1, ["+", 2, 3], 9]] | |
// (+ 2 3) -> ["+", 2, 3] | |
// (list 1 (+ 2 3) 9) -> ["list", 1, ["+", 2, 3], 9] | |
function parse(input) { | |
let offset = 0; | |
return parse_(); | |
function parse_() { | |
const t = nextToken(); | |
if (t === "(") { | |
const result = []; | |
let cursor; | |
while (true) { | |
cursor = parse_(); | |
if (cursor === null) { | |
throw new Error("Unexpected EOF offset:" + offset); | |
} | |
if (cursor === ")") { | |
break; | |
} | |
result.push(cursor); | |
} | |
return result; | |
} else { | |
return t; | |
} | |
} | |
// returns either | |
function nextToken() { | |
const ch = input[offset] | |
offset += 1; | |
if (ch === undefined) { | |
return null; | |
} | |
if (ch === "(") { | |
return "(" | |
} | |
if (ch === ")") { | |
return ")" | |
} | |
if (/\s/.test(ch)) { | |
return nextToken(); | |
} | |
// number | |
if (/\d/.test(ch)) { | |
const begin = offset - 1; | |
while (/\d/.test(current())) { | |
offset += 1; | |
} | |
const numberStr = input.slice(begin, offset); | |
return +numberStr; | |
} | |
// not (, ), " ", or number; so it's an ID | |
const begin = offset - 1; | |
while (current() !== null && !/\s|[()]/.test(current())) { | |
offset += 1; | |
} | |
const id = input.slice(begin, offset); | |
return id; | |
} | |
function current() { | |
return input[offset] || null; | |
} | |
} | |
function arrayEquals(a, b) { | |
if (!Array.isArray(a) || !Array.isArray(b)) { | |
return a === b; | |
} | |
if (a.length !== b.length) { | |
return false; | |
} | |
for (let i = 0; i < a.length; i++) { | |
if (!arrayEquals(a[i], b[i])) { | |
return false; | |
} | |
} | |
return true; | |
} | |
{ | |
// [input, expected output, expected exception] | |
const cases = [ | |
["(a 123)", ["a", 123]], | |
["(a b)", ["a", "b"]], | |
["(a 1)", ["a", 1]], | |
["(first (list 1 (+ 2 3) 9))", ["first", ["list", 1, ["+", 2, 3], 9]]], | |
["", null], | |
["()", []], | |
["id", "id"], | |
["(", undefined, "Unexpected EOF offset:2"] | |
] | |
for (const test of cases) { | |
try { | |
const input = test[0]; | |
const result = parse(input); | |
const pass = arrayEquals(result, test[1]); | |
console.log(pass, "Parsing " + input, pass ? "" : result); | |
} catch (err) { | |
const pass = test[2] == err.message | |
console.log(pass, err.message) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment