Anthony Del Ciotto's Website

Tiny Lisp Interpreter

2018-04-08

This is a temporary post... I think. Real content is coming soon! But, for now here is a tiny incomplete lisp interpreter I wrote in Javascript:

class Env{constructor(e,r={}){this.p=e,this.e=r}get(e){let r=this.e[e];if(void 0!==r)return r;
if(this.p)return this.p.get(e);throw`Identifier ${e} is not defined`}set(e,r){if(!!this.e[e])
throw`Identifier ${e} is already defined`;this.e[e]=r}}function Func(params,body,_env){
return x=>{let env=new Env(_env,params.reduce((e,r,t)=>(e[r.v]=x[t],e),{}));return eval(body,env)}}
let proc=process,con=console,cmpop=(e,r)=>e.slice(1).every(r),globals={"+":e=>e.reduce((e,r)=>e+r),
"-":e=>e.reduce((e,r)=>e-r),"/":e=>e.reduce((e,r)=>{if(0===r)throw"Cannot divide by 0";return e/r}),
"*":e=>e.reduce((e,r)=>e*r),"**":e=>e.reduce((e,r)=>Math.pow(e,r)),">":e=>cmpop(e,r=>e[0]>r),
">=":e=>cmpop(e,r=>e[0]>=r),"<":e=>cmpop(e,r=>e[0]<r),"<=":e=>cmpop(e,r=>e[0]<=r),
"==":e=>cmpop(e,r=>e[0]===r),car:e=>e[0],cdr:e=>e.slice(1),cons:e=>[e[0]].concat(e.slice(1))},
genv=new Env(null,globals),rl=require("readline").createInterface({input:proc.stdin,output:proc.stdout,
prompt:"Λ> "});function lex(e){return e.replace(/\(|\)/g," $& ").match(/".*?"|\S+/g)}
function parse(e){if(0===e.length)throw"Unexpected EOF";let r=e.shift();switch(r){
case"(":let t=[];for(;")"!==e[0];)t.push(parse(e));return e.shift(),{t:"expression",args:t};
case")":throw"Unmatched delimiter )";default:if('"'===r[0])return{t:"string",
v:r.substr(1,r.length-2)};let n=Number(r);return isNaN(n)?{t:"identifier",v:r}:{t:"number",v:n}}}
function eval(exp,env){switch(exp.t){case"expression":let args=exp.args,v=args[0].v;if("if"===v)
return eval(args[1],env)?eval(args[2],env):eval(args[3],env);if("define"!==v){if("lambda"===v)
return Func(args[1].args,args[2],env);if("quote"===v)return args[1];{let f=eval(args[0],env),
vals=args.slice(1).map(e=>eval(e,env));return f(vals)}}env.set(args[1].v,eval(args[2],env));
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);void 0!==r&&con.log(r)
}catch(e){con.error(`Error: ${e}`)}rl.prompt()}).on("close",()=>proc.exit(0));

You can save this to a file and run it at the command line with node:

> node lisp.min.js
Λ> (+ 1 2 3)
6
Λ> (define x 4)
Λ> (define sq (lambda (n) (* n n)))
Λ> (sq x)
16

Note: You will need to use a more recent version of Node. Target version used to write the script was v8.9.4.

View the full non-minified source here.