Last active
December 29, 2015 01:48
-
-
Save gloryofrobots/7595448 to your computer and use it in GitHub Desktop.
Simple lisp interpreter written on js.
It supports js numbers, "lambda", "define", "if", "set!"
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
function _make_function(args, body) { | |
return new Function(args, body); | |
} | |
// helpers | |
var $ = _make_function("id" ,"return document.getElementById(id)") | |
var _isarray = _make_function("obj","return obj instanceof Array;") | |
var _error = _make_function("txt", "throw new Error(txt)") | |
var _is_undefined = _make_function("obj","return obj == undefined") | |
function _add_repr_to_object (o, r) { | |
o.toString = _make_function([],"return '" + r +"'"); | |
return o; | |
} | |
// Environment | |
// parent - parent environment | |
function Environment(parent) { | |
// try to return obj from all env list | |
this.get = function(obj) { | |
return this.lookup_env(obj)._get(obj); | |
} | |
this._get = function(obj) { | |
return this.data[obj]; | |
} | |
// set key only if it has already defined | |
this.set_defined = function(key,obj) { | |
this.lookup_env(key)._set(key,obj) | |
} | |
this.set = function(key,obj) { | |
this.data[key] = obj; | |
} | |
this.lookup_env = function(symbol) { | |
if (symbol in this.data) { | |
return this; | |
} else if (this.parent == undefined) { | |
error("Unknown binding "+symbol) | |
} else { | |
return this.parent.lookup_env(symbol) | |
} | |
} | |
this.data = {} | |
this.parent = parent; | |
} | |
// init default state | |
var NIL = _add_repr_to_object(new Object(),"nil"); | |
var GLOBALS = new Environment() | |
function _BIN_OP(op) { | |
return _make_function(["x","y"], "return x" + op +"y"); | |
} | |
// add operators | |
["+","-","/","*",">","<",">=","<=",["=","=="],"%"] | |
.forEach( | |
function(op) { | |
if(_isarray(op)) { | |
GLOBALS.set(op[0], _BIN_OP(op[1])) | |
} else { | |
GLOBALS.set(op, _BIN_OP(op)) | |
} | |
}); | |
// add constants | |
[["#t",true],['#f',false], ['nil',NIL]] | |
.forEach( | |
function(pair) { | |
GLOBALS.set(pair[0], pair[1]) | |
}); | |
// split string into array of lisp tokens | |
function _make_lexems(txt) { | |
return txt | |
.replace(/(;[\w\s\']*)/g, '') | |
.replace(/['(']/g, ' ( ') | |
.replace(/[')']/g, ' ) ') | |
.replace(/^\s+|\s+$/g, '') | |
.split(/\s+/g) | |
} | |
// create internal representation | |
function _parse(tokens) { | |
var data; | |
var token = tokens.shift() | |
if(token == '(') { | |
data = [] | |
while(tokens[0] != ')' && tokens.length > 0){ | |
data.push(_parse(tokens)) | |
} | |
tokens.shift() | |
return data | |
} else if(token == ')') { | |
_error("Unexpected )") | |
} else { | |
data = parseFloat(token) | |
if(!isNaN(data)) { | |
return data; | |
} | |
return token; | |
} | |
} | |
function _make_lambda(vars ,body, env) { | |
return _add_repr_to_object(function() { | |
var E = new Environment(env) | |
for(var i = 0; i < arguments.length; i++) { | |
E.set(vars[i],arguments[i]); | |
} | |
return _eval(body, E) | |
},"(function)"); | |
} | |
// main eval procedure | |
function _eval(expr, env) { | |
if (_isarray(expr)) { | |
var proc_name = expr[0]; | |
if(proc_name == "if") { | |
var cond = expr[1]; | |
var true_branch = expr[2]; | |
var false_branch = expr[3]; | |
if(_eval(cond,env)) { | |
var res = _eval(true_branch, env) | |
return res | |
} else { | |
return _eval(_is_undefined(false_branch) ? NIL : false_branch, env) | |
} | |
} else if(proc_name == "set!") { | |
var name = expr[1]; | |
var value = expr[2]; | |
env.set_defined(name, _eval(value, env)) | |
} else if(proc_name == "define") { | |
if(expr.length != 3) { | |
_error("Bad define syntax") | |
} | |
if(!_isarray(expr[1])) { | |
env.set(expr[1],_eval(expr[2],env)) | |
} else { | |
var name = expr[1][0]; | |
var vars = expr[1].slice(1); | |
var body = expr[2]; | |
env.set(name, _make_lambda(vars, body, env)) | |
} | |
} else if(proc_name == "lambda") { | |
var vars = expr[1] | |
var body = expr[2]; | |
return _make_lambda(vars, body, env) | |
} | |
else { | |
var evals = expr.map( | |
function(el) { | |
return _eval(el, env) | |
}); | |
var fn = evals[0]; | |
if(!_is_undefined(evals[0].apply)) { | |
var args = evals.slice(1); | |
return fn.apply(fn, args); | |
} else { | |
_error("Can`t applicate "+fn+" as procedure") | |
} | |
} | |
} else if((typeof expr) == "string") { | |
return env.get(expr) | |
} else { | |
return expr; | |
} | |
} | |
// entry point | |
// txt - program text | |
// out - stdout callback | |
// err - stderr callback | |
function lisp_exec(txt, out, err) { | |
var tokens = _make_lexems(txt); | |
try{ | |
while(tokens.length > 0) { | |
out(_eval(_parse(tokens), GLOBALS)); | |
} | |
} catch(e) { | |
err(e); | |
return; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment