lisp.js
Version:
A tiny lisp interpreter
272 lines (239 loc) • 9.48 kB
JavaScript
var _ = require('underscore');
var Env = function(params, args, outer) {
var dict = {};
var that = this;
var outer = outer;
_.extend(dict, _.object(params, args));
this.find = function(name) {
if (_.has(dict, name)) return that;
return outer.find(name);
};
this.set = function(name, val) { dict[name] = val; };
this.update = function(new_dict) { _.extend(dict, new_dict); };
this.get = function(name) {
if (_.has(dict, name)) return dict[name];
};
};
var add_globals = function(env) {
var r_template = function(g) {
return function() { return _.reduce(Array.prototype.slice.apply(arguments), g); };
};
env.update({
"else": true,
"#t": true,
"#f": false,
"+": r_template(function(x, y) { return x + y; }),
"-": r_template(function(x, y) { return x - y; }),
"*": r_template(function(x, y) { return x * y; }),
"/": r_template(function(x, y) { return x / y; }),
"and": r_template(function(x, y) { return x && y; }),
"or": r_template(function(x, y) { return x || y; }),
"not": function(val) { return !val; },
">": function(x, y) { return x > y; },
"<": function(x, y) { return x < y; },
">=": function(x, y) { return x >= y; },
"<=": function(x, y) { return x <= y; },
"=": function(x, y) { return x === y; },
"equal?": function(x, y) { return x === y; },
"eq?": function(x, y) { return x == y; },
"length": function(x) { return x.length },
"cons": function(x, y) { return [x].concat(y); },
"car": function(x) { return x[0]; },
"cdr": function(x) { return x.slice(1); },
"append": function(x, y) { x.push(y); return x; },
"list": function() { return Array.prototype.slice.apply(arguments); },
"list?": function(x) { return isa(x, "list"); },
"null?": function(x) { return _.isEmpty(x); },
"symbol?": function(x) { return isa(x, "Symbol"); }
});
return env;
};
var eval = function(x, env) {
log(x);
if (env === void 0)
env = global_env;
if (isa(x, "Symbol")) { return env.find(x).get(x); }
else if (!isa(x, "list")) { return x; }
else if (x[0] === "quote") { return x[1]; }
else if (x[0] === "if") {
if (eval(x[1], env)) { return eval(x[2], env); }
else { return eval(x[3], env); }
}
else if (x[0] === "let") {
var o = _.object(x[1]);
return eval([eval(["lambda", _.keys(o), x[2]], env)].concat(_.values(o)), env);
}
else if (x[0] === "let*") {
var env1 = new Env(null, null, env);
var t = _.map(_.object(x[1]), function(item, key) {
var result = eval(item, env1);
env1.set(key, result);
return [key, result];
});
return eval(["let", t, x[2]], env);
}
else if (x[0] === "cond") {
return eval(_.find(x.slice(1), function(item) { return eval(item[0], env); })[1], env);
}
else if (x[0] === "set!") {
env.find(x[1]).set(x[1], eval(x[2], env));
}
else if (x[0] === "define") {
env.set(x[1], eval(x[2], env));
}
else if (x[0] === "lambda") {
return function() {
return eval(x[2], new Env(x[1], Array.prototype.slice.apply(arguments), env));
};
}
else if (x[0] === "begin") {
var end = x.pop();
_.each(x, function(ele) {
eval(ele, env);
});
return eval(end, env);
}
else {
var exps = _.map(x, function(exp) {
return eval(exp, env);
});
var proc = exps.shift();
return proc.apply(undefined, exps);
}
};
var isa = function(s, type) {
if (type === "list") {
return Array.isArray(s);
}
else if (type === "Symbol") {
return typeof s === "string";
}
};
var parse = function(s) { return read_from(tokenize(s)); };
var tokenize = function(s) {
return _.filter(s.replace(/[\r\t\n]/g, " ").replace(/\(/g, " ( ").replace(/\)/g, " ) ").split(" "),
function(item) { return !_.isEmpty(item); });
};
var read_from = function(tokens) {
if (tokens.length === 0)
throw new Error("Syntax Error: unexpected EOF while reading");
token = tokens.shift();
if ("(" === token) {
var t = [];
while (tokens[0] != ")") {
t.push(read_from(tokens));
}
tokens.shift();
return t;
}
else if (")" === token) {
throw new Error("Syntax Error: unexpected )");
}
else {
return atom(token);
}
};
var atom = function(token) {
if (isNaN(parseFloat(token))) {
if (isNaN(parseInt(token))) {
return token.toString();
}
return parseInt(token);
}
return parseFloat(token);
};
var to_string = function(exp) {
if (isa(exp, "list")) {
return "(" + exp.map(to_string).join(" ") + ")";
}
return exp.toString();
};
var global_env = add_globals(new Env());
var currying = function(x) {
if (x[0] == "lambda") {
if (x[1].length > 1) {
var res = [];
res.push("lambda", [x[1][0]]);
res.push(currying(["lambda", x[1].slice(1), x[2]]));
return res;
}
return x;
}
else {
//apply
return _.reduce(x, function(a, b) {
if (typeof a === 'object') {
a = currying(a);
}
if (typeof b === 'object') {
b = currying(b);
}
return [a, b];
});
}
};
var log = function(message, tag) {
if (typeof message !== 'string') {
message = JSON.stringify(message);
}
if (tag != undefined) {
console.log(tag + message);
}
console.log(message);
};
log(currying(parse("(lambda (a b) (+ a b))")));
log(currying(parse("(lambda (a) a)")));
log(currying(parse("((lambda (a b) (+ a b)) 1 2)")));
log(eval(currying(parse("((lambda (a b) (+ a b)) 1 2)"))));
log(parse("(((lambda (a) (lambda (b) (+ a b))) ((lambda (x) x) 1)) 2)"));
log(eval(parse("(((lambda (a) (lambda (b) (+ a b))) ((lambda (x) x) 1)) 2)")));
log(currying(parse("((lambda (a b) (+ a b)) ((lambda (x) x) 1) 2)")));
log(eval(currying(parse("((lambda (a b) (+ a b)) ((lambda (x) x) 1) 2)"))));
log(currying(parse("(lambda (a b c) (+ a b c))")));
log(currying(parse("((lambda (a b c) (+ a b c)) 4 5 6)")));
log(eval(currying(parse("((lambda (a b c) (+ a b c)) 4 5 6)"))));
log(eval(parse("((lambda (a) (lambda (b) (+ a b))) 1)")));
log(currying(parse("(define add (lambda (a b) (+ a b)))")));
log(currying(parse("(+ 4 5 6)")));
log(currying(parse("(do_sth a (add 1 2) c)")));
/*
console.log(eval(parse("(+ 1 2 3)"))) // => 6
console.log(eval(parse("(* 2 3 4)"))) // => 24
console.log(eval(parse("(car (list 1 2 3))"))); // => 1
console.log(eval(parse("(cdr (list 1 2 3))"))); // => [2, 3]
console.log(eval(parse("(cons 1 2)"))); // => [1, 2]
console.log(eval(parse("(cons 1 (list 2 3))"))); // => [1, 2, 3]
console.log(eval(parse("(length (list 9 9 9))"))); // => 3
console.log(eval(parse("(< 1 2)"))); // => true
console.log(eval(parse("(<= 1 2)"))); // => true
console.log(eval(parse("(= 2 2)"))); // => true
console.log(eval(parse("(list? (list 1 2 3))"))); // => true
console.log(eval(parse("(list? (quote abc))"))); // => false
console.log(eval(parse("(null? (list))"))); // => true
eval(parse("(define add (lambda (x y) (+ x y)))"));
console.log(eval(parse("(add 2 4)"))); // => 6
eval(parse("(define area (lambda (r) (* 3.141592653 (* r r))))"));
console.log(eval(parse("(area 3)"))); // => 28.2xxxxx
eval(parse("(define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))"));
console.log(eval(parse("(fact 10)"))); // => 3628800
console.log(eval(parse("(if (equal? 1 1) (quote conseq) (quote otherwise))"))); // => conseq
console.log(eval(parse("(if (equal? 1 2) (quote conseq) (quote otherwise))"))); // => otherwise
console.log(eval(parse("(not (equal? 1 1))"))); // => false
eval(parse("(define first car)"));
eval(parse("(define rest cdr)"));
console.log(eval(parse("(first (list 1 2 3))"))); // => 1
console.log(eval(parse("(rest (list 1 2 3))"))); // => [2, 3]
console.log(eval(parse("(equal? 1 (first (list 1 2 3)))"))); // => true
eval(parse("(define count (lambda (item L) (if (not (null? L)) (+ (equal? item (first L)) (count item (rest L))) 0)))"));
console.log(eval(parse("(count 2 (list 0 1 2 2 0 2))"))); // => 3
console.log(eval(parse("(count 0 (list 0 1 2 3 0 2))"))); // => 2
console.log(eval(parse("(and (equal? 1 2) (equal? 2 2))"))); // => false
console.log(eval(parse("(or (equal? 1 2) (equal? 2 2))"))); // => true
console.log(eval(parse("(cond ((> (area 3) 30) (quote should_not_happen)) ((equal? 1 2) (quote should_not_happen)) (else (quote else_happen)))")));
console.log(eval(parse("(let ((x 5)) x)"))); // => 5
console.log(eval(parse("(let ((x 5) (y 2)) (+ x y))"))); // => 7
console.log(eval(parse("(let* ((x 5) (y (+ x 2))) (+ x y))"))); // => 12
eval(parse("(define Y (lambda (le) ((lambda (f) (f f)) (lambda (f) (le (lambda (x) ((f f) x)))))))"));
eval(parse("(define FactY (Y (lambda (f) (lambda (n) (cond ((eq? n 0) 1) (else (* n (f (- n 1)))))))))"));
console.log(eval(parse("(FactY 10)"))); // => 3628800
*/