Skip to content

Instantly share code, notes, and snippets.

@adelciotto
Created April 14, 2018 06:23
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 adelciotto/47f3619a8eeed8f683b577294749b533 to your computer and use it in GitHub Desktop.
Save adelciotto/47f3619a8eeed8f683b577294749b533 to your computer and use it in GitHub Desktop.
Tiny and simple lisp implementation in node
class Env {
constructor(p, e = {}) {
this.p = p;
this.e = e;
}
get(name) {
let val = this.e[name];
if (typeof val !== 'undefined')
return val;
if (this.p)
return this.p.get(name);
throw `Identifier ${name} is not defined`;
}
set(name, val) {
let exists = !!this.e[name];
if (!exists)
this.e[name] = val;
else
throw `Identifier ${name} is already defined`;
}
}
function Func(params, body, _env) {
return (x) => {
let env = new Env(_env, params.reduce((acc, p, i) => {
acc[p.v] = x[i];
return acc;
}, {}));
return eval(body, env);
};
}
let proc = process,
con = console,
cmpop = (x, f) => x.slice(1).every(f),
globals = {
'+': (x) => x.reduce((acc, val) => acc + val),
'-': (x) => x.reduce((acc, val) => acc - val),
'/': (x) => x.reduce((acc, val) => { if (val === 0) throw "Cannot divide by 0"; return acc / val; }),
'*': (x) => x.reduce((acc, val) => acc * val),
'**': (x) => x.reduce((acc, val) => Math.pow(acc, val)),
'>': (x) => cmpop(x, (v) => x[0] > v),
'>=': (x) => cmpop(x, (v) => x[0] >= v),
'<': (x) => cmpop(x, (v) => x[0] < v),
'<=': (x) => cmpop(x, (v) => x[0] <= v),
'==': (x) => cmpop(x, (v) => x[0] === v),
'car': (x) => x[0],
'cdr': (x) => x.slice(1),
'cons': (x) => [x[0]].concat(x.slice(1))
},
genv = new Env(null, globals),
rl = require('readline').createInterface({
input: proc.stdin,
output: proc.stdout,
prompt: '\u039b> '
});
function lex(source) {
return source.replace(/\(|\)/g, ' $& ').match(/".*?"|\S+/g);
}
function parse(tokens) {
if (tokens.length === 0) throw 'Unexpected EOF';
let token = tokens.shift();
switch (token) {
case '(':
let list = [];
while (tokens[0] !== ')') list.push(parse(tokens));
tokens.shift();
return { t: 'expression', args: list };
case ')':
throw 'Unmatched delimiter )';
default:
if (token[0] === '"') return { t: 'string', v: token.substr(1, token.length-2) };
let numberVal = Number(token);
return isNaN(numberVal) ? { t: 'identifier', v: token } : { t: 'number', v: numberVal };
}
}
function eval(exp, env) {
switch (exp.t) {
case 'expression':
let args = exp.args,
v = args[0].v;
if (v === 'if') {
return eval(args[1], env) ? eval(args[2], env) : eval(args[3], env);
} else if (v === 'define') {
env.set(args[1].v, eval(args[2], env));
} else if (v === 'lambda') {
return Func(args[1].args, args[2], env);
} else if (v === 'quote') {
return args[1];
}
else {
let f = eval(args[0], env),
vals = args.slice(1).map((e) => eval(e, env));
return f(vals);
}
case 'string':
case 'number':
return exp.v;
case 'identifier':
return env.get(exp.v);
default:
return exp.v;
}
}
rl.prompt();
rl.on('line', (l) => {
if (l) {
try {
let r = eval(parse(lex(l.trim())), genv);
if (typeof r !== 'undefined') con.log(r);
} catch (e) {
con.error(`Error: ${e}`);
}
}
rl.prompt();
}).on('close', () => proc.exit(0));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment