elisp.js/elisp/evaluator.js

241 lines
6 KiB
JavaScript

////
// Emacs Lisp implementation in JavaScript.
//
// Copyright (c) 2009 Sami Samhuri - sami.samhuri@gmail.com
//
// Released under the terms of the MIT license. See the included file
// LICENSE.
elisp.Evaluator = function(exprs) {
this.expressions = exprs;
this.variables = new elisp.SymbolTable(elisp.PrimitiveVariables);
this.functions = new elisp.SymbolTable(elisp.PrimitiveFunctions);
};
elisp.Evaluator.Error = function(name, message, expr) {
this.evalError = true;
this.name = name;
this.message = message;
this.expression = expr;
};
elisp.Evaluator.Error.messages = {
'not-expr': "not an expression",
'undefined-func': "undefined function",
'undefined-var': "variable not defined"
};
elisp.Evaluator.prototype.error = function(name, expr) {
throw(new elisp.Evaluator.Error(name, elisp.Evaluator.Error.messages[name], expr));
};
elisp.Evaluator.prototype.evalExpressions = function(expressions) {
var exprs = expressions || this.expressions,
i = 0,
n = exprs.length,
result;
while (i < n) {
try {
result = this.eval(exprs[i++]);
} catch (e) {
if (e.evalError) {
print('[error] ' + e.message);
if (e.expression) {
print("got: " + e.expression);
}
result = elisp.nil;
break;
}
else {
throw(e);
}
}
}
// elisp.Util.pp(result);
return result;
};
elisp.Evaluator.prototype.lookupVar = function(symbol) {
return this.variables.lookup(symbol);
};
elisp.Evaluator.prototype.lookupFunc = function(symbol) {
return this.functions.lookup(symbol);
};
elisp.Evaluator.prototype.apply = function(func, args) {
var result;
if (func.type === 'primitive') {
// print('APPLY: ');
// elisp.print(func);
// print('WITH: ');
// elisp.print(args);
// print('------');
result = func.body.apply(this, args);
}
else {
this.functions.pushScope();
this.variables.pushScope(elisp.listMap(func.params, function(e, i){
var name = elisp.symbolName(e),
value = {
type: 'variable',
value: this.eval(args[i])
};
return [name, value];
}));
result = elisp.listLast(elisp.listMap(func.body,
function(e) {return this.eval(e); }));
this.functions.popScope();
this.variables.popScope();
}
return result;
};
elisp.Evaluator.prototype.eval = function(expr) {
// print("EVAL: " + elisp.typeOf(expr));
// elisp.print(expr);
var result, x,
tag = elisp.tag(expr);
if (elisp.isAtom(expr)) {
result = expr;
}
else if (elisp.isSymbol(expr)) {
var name = elisp.val(expr);
x = this.lookupVar(name);
if (x == null) this.error('undefined-var', name);
result = x.value;
}
///////////////////
// special forms //
///////////////////
// (many could be in lisp when there are macros) //
else if (elisp.isQuote(expr)) {
result = elisp.cdr(expr);
}
else if (elisp.isDefVar(expr)) {
var name = elisp.symbolName(elisp.cadr(expr)), // 2nd param
value = this.eval(elisp.caddr(expr)), // 3rd param
docstring = elisp.cadddr(expr); // 4th param
// TODO check for re-definitions
this.defineVar(name, value, docstring);
result = elisp.nil;
}
else if (elisp.isDefFunc(expr)) {
var name = elisp.symbolName(elisp.nth(1, expr)),
params = elisp.nth(2, expr),
d = elisp.nth(3, expr),
docstring = elisp.isString(d) && d,
body = elisp.nthcdr(docstring ? 3 : 2, expr);
this.defineFunc(name, params, body, docstring);
result = elisp.nil;
}
else if (elisp.isSet(expr)) {
var val = elisp.val(expr),
name = elisp.symbolName(val[1]),
value = this.eval(val[2]);
this.setVar(name, value);
result = value;
}
else if (elisp.isSetq(expr)) {
var i = 1,
n = elisp.listLength(expr),
e;
while (i < n && elisp.isSymbol((e=elisp.nth(i,expr)))) {
var name = elisp.symbolName(elisp.nth(i, expr)),
value = this.eval(elisp.nth(i+1, expr));
this.setVar(name, value, true);
result = value;
i += 2;
}
}
else if (elisp.isIf(expr)) {
var val = elisp.val(expr),
condition = this.eval(val[1]),
trueBlock = val[2],
nilBlock = val[3];
result = this.doIf(condition, trueBlock, nilBlock);
}
else if (elisp.isCond(expr)) {
var val = elisp.val(expr),
list = val[1],
condition = elisp.car(list),
body = elisp.cdr(list),
rest = val.slice(2);
result = this.doCond(exprs);
}
// function application
else if (elisp.isCons(expr)) {
var name = elisp.car(expr),
rest = elisp.cdr(expr),
func, args;
while (!elisp.isSymbol(name)) {
name = this.eval(name);
}
if ((func = this.lookupFunc(elisp.symbolName(name)))) {
var self = this;
args = elisp.listReduce(function(a,e){
a.push(self.eval(e));
return a;
}, [], rest);
result = this.apply(func, args);
}
else {
this.error('undefined-func', name);
}
}
else {
this.error('not-expr', expr);
}
// print('RESULT: ' + result);
return result;
};
elisp.Evaluator.prototype.defineVar = function(symbol, value, docstring) {
this.variables.define(symbol, {
type: 'variable',
value: value,
docstring: docstring || "(undocumented)"
});
};
elisp.Evaluator.prototype.setVar = function(symbol, value, create) {
var valueObject = this.lookupVar(symbol);
if (!valueObject) {
if (create) {
this.defineVar(symbol, elisp.nil);
valueObject = this.lookupVar(symbol);
}
else {
this.error('undefined-var', symbol);
}
}
valueObject.value = value;
this.variables.set(symbol, valueObject);
};
elisp.Evaluator.prototype.defineFunc = function(symbol, params, body, docstring) {
this.functions.define(symbol, {
type: 'lambda',
name: symbol,
params: params,
body: body,
docstring: docstring || "(undocumented)"
});
};
elisp.Evaluator.prototype.doIf = function(condition, trueBlock, nilBlock) {
return elisp.isNil(condition) ? this.eval(nilBlock) : this.eval(trueBlock);
};
elisp.Evaluator.prototype.doCond = function(exprs) {
print('----- COND (doCond) -----');
elisp.print(exprs);
return elisp.nil;
};