Skip to content

Instantly share code, notes, and snippets.

@gloryofrobots
Last active December 29, 2015 01:48
Show Gist options
  • Save gloryofrobots/7595448 to your computer and use it in GitHub Desktop.
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!"
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