Created
June 14, 2019 23:08
-
-
Save rsalgado/8a106da3d61b16ad58ab7bdd7dca3052 to your computer and use it in GitHub Desktop.
Small attempt at a lisp parser
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
/* | |
Small and bare-bones Lisp parser and evaluator inspired by the respective problem posed at: https://www.recurse.com/pairing-tasks. | |
NOTE: This was my first time, as I had no previous experience with Lisp languages and only had some vague notions of them | |
besides the fact that their notation is prefix-based, so this implementation might not be totally correct. | |
This was just for the sake of learning and experimenting. And only implements four function: +, *, list and first. | |
*/ | |
function tokenize(input) { | |
let validTokensRegExp = /(\w+|\(|\)|\+|\*)/g; | |
let tokens = input.match(validTokensRegExp); | |
if (tokens) | |
return tokens; | |
else | |
return []; | |
} | |
function parse(tokens) { | |
if (tokens.length === 0) return null; | |
let token = tokens.shift(); | |
if (token === "(") { | |
let result = []; | |
while (tokens.length > 0 && tokens[0] !== ")") { | |
let term = parse(tokens); | |
result.push(term); | |
} | |
match(tokens, ")"); | |
return result; | |
} | |
else if (/\d+/.test(token)) return +token; | |
else if (/[a-z]+/.test(token)) return token; | |
else return token; | |
} | |
function match(tokens, expectedToken) { | |
if (tokens.length > 0 && tokens[0] === expectedToken) | |
return tokens.shift(); | |
throw Error(`Expected ${expectedToken}`); | |
} | |
function evaluate(tree) { | |
if (tree instanceof Array) { | |
let operator = tree[0]; | |
let operands = tree.slice(1); | |
return apply(operator, operands); | |
} | |
else | |
return tree; | |
} | |
function apply(operator, operands) { | |
switch (operator) { | |
case "+": | |
return operands.reduce((accum, term) => accum + evaluate(term), 0); | |
case "*": | |
return operands.reduce((accum, term) => accum * evaluate(term), 1); | |
case "list": | |
return operands.reduce((accum, term) => { | |
accum.push(evaluate(term)); | |
return accum; | |
}, []); | |
case "first": | |
let operand = operands[0]; | |
return evaluate(operand)[0]; | |
default: | |
throw Error(`Operator "${operator}" not defined`); | |
} | |
} | |
function interpret(input) { | |
let tokens = tokenize(input); | |
let tree = parse(tokens); | |
let result = evaluate(tree); | |
return result; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment