[NEW] if, negative numbers, regex type & literal, more...

Sorry for the massive commit. I'll try not to do this.

Several new features:

 * Proper conses and lists (probably slow, can optimize later)
 * Parse negative numbers
 * Regular expressions (piggyback off js regex, not emacs compatible)
 * string-match and symbol-name primitives
 * if special form
 * car, cdr, cadr, caddr, cadddr, nth, nthcdr, map, length, null,
   symbolp, listp, stringp, numberp, etc. only in JS now but I will
   expose them in lisp as primitives soon.

Fixed:

 * setq now silently defines undefined variables

some miscellaneous things:

 * simple init system to specify init code that loads after all defs
   have been read
 * String.camelize function stolen from Prototype
This commit is contained in:
Sami Samhuri 2009-12-06 20:50:46 -08:00
parent 3fae23b89d
commit 77f1a9eb93
3 changed files with 484 additions and 240 deletions

4
README
View file

@ -2,7 +2,7 @@ Emacs Lisp implementation in JavaScript.
Copyright (c) 2009 Sami Samhuri - sami.samhuri@gmail.com Copyright (c) 2009 Sami Samhuri - sami.samhuri@gmail.com
Released under the terms of the MIT license. See the include file Released under the terms of the MIT license. See the included file
LICENSE. LICENSE.
Latest version available on github: Latest version available on github:
@ -71,7 +71,7 @@ lines.
* symbol table * symbol table
(functions & variables separate) (functions & variables separate)
* lexical scope * (broken) lexical scoping
* expression evaluator * expression evaluator

14
TODO
View file

@ -3,11 +3,11 @@ TODO
* implement all Emacs Lisp types/objects * implement all Emacs Lisp types/objects
* relational operators: < > <= >= = * relational operators: < > <= >= = not
* boolean operator: not * special forms: lambda, if, cond, and, or, let, let*, letf, letf*
* special forms: if, cond, and, or, let, let*, letf, letf* * successfully interpret ~/config/emacs one sexp at a time
* primitives: list/cons functions, string functions, * primitives: list/cons functions, string functions,
apply, eval, apply, eval,
@ -15,8 +15,10 @@ TODO
* macros * macros
* dynamic scoping * dynamic scoping (replace the current symbol table entirely)
* merge with Ymacs?
* look into CommonJS * look into CommonJS
* merge with Ymacs? (probably not going to happen, would have to
steal what we can from them)

690
el.js
View file

@ -1,46 +1,331 @@
var spidermonkeykludge = 0; ////
// spidermonkey doesn't like a C-style comment at the beginning // Emacs Lisp implementation in JavaScript.
//
/* // Copyright (c) 2009 Sami Samhuri - sami.samhuri@gmail.com
* Emacs Lisp implementation in JavaScript. //
* // Permission is hereby granted, free of charge, to any person obtaining a copy
* Copyright (c) 2009 Sami Samhuri - sami.samhuri@gmail.com // of this software and associated documentation files (the "Software"), to deal
* // in the Software without restriction, including without limitation the rights
* Permission is hereby granted, free of charge, to any person obtaining a copy // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* of this software and associated documentation files (the "Software"), to deal // copies of the Software, and to permit persons to whom the Software is
* in the Software without restriction, including without limitation the rights // furnished to do so, subject to the following conditions:
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell //
* copies of the Software, and to permit persons to whom the Software is // The above copyright notice and this permission notice shall be included in
* furnished to do so, subject to the following conditions: // all copies or substantial portions of the Software.
* //
* The above copyright notice and this permission notice shall be included in // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* all copies or substantial portions of the Software. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // THE SOFTWARE.
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, //
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ///
* THE SOFTWARE.
*
*/
// can't live without the basics // our namespace
var EL = function(){};
Array.prototype.each = function(fn) { // Use initHook() to specify initialization routines at the very end
// of the file we call init when everything is defined, regardless of
// the order it appears in the file. The order of the hooks still
// matters though, it's not fool-proof.
EL._initHooks = [];
EL.initHook = function(hook) {
EL._initHooks.push(hook);
};
EL.init = function() {
var i = 0,
n = EL._initHooks.length;
while (i < n) {
EL._initHooks[i++].call();
}
};
// Just a little sugar
EL._jsExt = function() {
Array.prototype.each = function(fn) {
var i = 0, var i = 0,
n = this.length; n = this.length;
while (i < n) { while (i < n) {
fn(this[i], i); fn(this[i], i);
++i; ++i;
} }
};
// Thanks Prototype
String.prototype.camelize = function() {
var oStringList = this.split('_');
if (oStringList.length == 1)
return oStringList[0][0].toUpperCase() + oStringList[0].substring(1);
var camelizedString = oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1);
for (var i = 1, len = oStringList.length; i < len; i++) {
var s = oStringList[i];
camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
}
return camelizedString;
};
// A typeOf function that distinguishes between objects, arrays,
// and null.
EL.typeOf = function(value) {
var s = typeof value;
if (s === 'object') {
if (value) {
if (typeof value.length === 'number' &&
!(value.propertyIsEnumerable('length')) &&
typeof value.splice === 'function') {
s = 'array';
}
} else {
s = 'null';
}
}
return s;
};
// TODO throw something more informative
EL.assert = function(condition, message) {
if (!condition()) {
throw("assertion failed: " + condition + " (" + message + ")");
}
};
};
EL.initHook(EL._jsExt);
//****************************************************************
// **** Lisp Support **********************************************
// ****************************************************************/
// data types are simple tags
EL._defineTags = function() {
EL.tags = ['symbol', 'string', 'number', 'cons', 'lambda', 'regex'];
EL.tags.each = Array.prototype.each;
// define constructors for the primitive types (box values)
// e.g. EL.symbol('foo') => ['symbol', 'foo']
EL.tags.each(function(tag) {
// don't clobber custom constructors
if (EL[tag] === undefined) {
EL[tag] = function(value) {
return [tag, value];
};
}
// tag type tests
var isTag = function(expr) {
return (EL.tag(expr) == tag);
};
EL['is' + tag.camelize()] = isTag;
});
};
EL.initHook(EL._defineTags);
// shorthands to save my fingers
EL._defineConstants = function() {
EL.nil = EL.symbol('nil');
EL.t = EL.symbol('t');
};
EL.initHook(EL._defineConstants);
// retrieve the tag from a value
EL.tag = function(expr) {
EL.assert(function() { var f='tag'; return EL.typeOf(expr) == 'array'; }, expr);
return expr[0];
};
// unbox a value
EL.val = function(expr) {
EL.assert(function() { var f='val'; return EL.typeOf(expr) == 'array'; }, expr);
return expr[1];
};
EL.symbolName = function(symbol) {
// EL.Util.pp(symbol);
EL.assert(function(){ var f='symbolName'; return EL.isSymbol(symbol); }, symbol);
return EL.val(symbol);
};
EL.consPair = function(pair) {
var cons = ['cons', pair];
cons.isList = true;
return cons;
};
EL.cons = function(car, cdr) {
return EL.consPair([car, cdr]);
};
EL.car = function(cons) {
return EL.isNil(cons) ? cons : EL.val(cons)[0];
};
EL.cdr = function(cons) {
return EL.isNil(cons) ? cons : EL.val(cons)[1];
};
EL.cadr = function(cons) {
return EL.car(EL.cdr(cons));
};
EL.caddr = function(cons) {
return EL.car(EL.cdr(EL.cdr(cons)));
};
EL.cadddr = function(cons) {
return EL.car(EL.cdr(EL.cdr(EL.cdr(cons))));
};
EL.listLength = function(cons) {
var n = 0;
while (!EL.isNil(cons)) {
cons = EL.cdr(cons);
++n;
}
return n;
};
EL.listLast = function(cons) {
var last;
while (!EL.isNil(cons)) {
last = cons;
cons = EL.cdr(cons);
}
return EL.car(last);
};
EL.listMap = function(cons, fn) {
var list = [],
i = 0;
while (!EL.isNil(cons)) {
list.push(fn(EL.car(cons), i));
cons = EL.cdr(cons);
++i;
}
return list.length > 0 ? EL.list(list) : EL.nil;
};
EL.nth = function(n, cons) {
var i = 0,
e;
while (i <= n && !EL.isNil(cons)) {
e = EL.car(cons);
cons = EL.cdr(cons);
++i;
}
return n > --i ? EL.nil : e;
};
EL.nthcdr = function(n, cons) {
var e = EL.cdr(cons),
i = 0;
while (i < n && !EL.isNil(e)) {
e = EL.cdr(e);
++i;
}
return n > i ? EL.nil : e;
};
EL.isNilSymbol = function(expr) {
return (EL.isSymbol(expr) && EL.symbolName(expr) == 'nil');
};
EL.isNil = function(expr) {
return EL.isNilSymbol(expr);
};
EL.list = function(exprs) {
var list = EL.nil,
i = exprs.length;
while (--i >= 0) {
list = EL.cons(exprs[i], list);
}
list.isList = true;
return list;
};
EL.isList = function(expr) {
return (EL.isNil(expr) || EL.isCons(expr));
};
EL.isAtom = function(expr) {
return EL.isString(expr) || EL.isNumber(expr) || EL.isRegex(expr);
};
EL.inferType = function(exprs) {
var type = 'number',
initial = 0,
i = exprs.length-1;
while(i >= 0) {
if (EL.isString(exprs[i--])) {
type = 'string';
initial = '';
break;
}
}
return EL[type](initial);
};
EL.isSpecialForm = function(name, expr) {
var tag = EL.tag(expr),
car = EL.typeOf(expr) == 'array' && EL.val(expr)[0],
thisName = car && EL.symbolName(car);
return (tag == 'cons' && thisName == name);
};
EL.isQuote = function(expr){return EL.isSpecialForm('quote', expr);};
EL.isDefVar = function(expr){return EL.isSpecialForm('defvar', expr);};
EL.isDefFunc = function(expr){return EL.isSpecialForm('defun', expr);};
EL.isSet = function(expr){return EL.isSpecialForm('set', expr);};
EL.isSetq = function(expr){return EL.isSpecialForm('setq', expr);};
EL.isCond = function(expr){return EL.isSpecialForm('cond', expr);};
EL.isIf = function(expr){return EL.isSpecialForm('if', expr);};
EL.eval = function(exprs) {
var e = new EL.Evaluator();
return e.evalExpressions(exprs);
};
EL.parse = function(string) {
var p = new EL.Parser();
return p.parse(string);
};
EL.parseOne = function(string) {
return EL.parse(string)[0];
};
EL.read = EL.parseOne;
EL.rep = function(string) {
EL.print(EL.eval(EL.parse(string)));
};
EL.repl = function() {
var p = new EL.Parser(),
e = new EL.Evaluator();
while (true) {
print("elisp> "); // i don't want a newline, grrrr
try {
var line = readline();
if (line && line[0] && line[0].toLowerCase() == 'q') return;
EL.print(e.eval(p.parseOne(line)));
} catch (x) {
if (x.evalError) {
print('[error] ' + x.message + ': ' + x.expression);
EL.print(x);
}
else throw(x);
}
}
}; };
// our namespace /****************************************************************
var EL = function(){}; ****************************************************************/
/**************************************************************** /****************************************************************
@ -172,8 +457,22 @@ EL.Parser.prototype.parseSymbol = function() {
return symbol; return symbol;
}; };
// Probably easy to break
EL.Parser.prototype.parseRegex = function() {
// consume initial slash
this.consumeChar();
var self = this;
return new RegExp(this.parseUntil(/\//, '', function(s,c){
if (c == '\\') {
c = self.consumeChar();
}
return s + c;
}, true /* consume terminator */));
};
EL.Parser.prototype.parseNumber = function() { EL.Parser.prototype.parseNumber = function() {
var value = this.parseUntil(/[^\d]/, 0, function(n,c) { var sign = this.peek() == '-' ? this.consumeChar() : '+',
value = this.parseUntil(/[^\d]/, 0, function(n,c) {
return n*10 + parseInt(c); return n*10 + parseInt(c);
}); });
if (this.peek() == '.') { if (this.peek() == '.') {
@ -181,13 +480,13 @@ EL.Parser.prototype.parseNumber = function() {
var decimal = this.parseUntil(/[^\d]/, '', function(s,c) {return s + c;}); var decimal = this.parseUntil(/[^\d]/, '', function(s,c) {return s + c;});
value = parseFloat('' + value + '.' + decimal); value = parseFloat('' + value + '.' + decimal);
} }
return value; return sign == '-' ? -1*value : value;
}; };
EL.Parser.prototype.lookingAtNumber = function() { EL.Parser.prototype.lookingAtNumber = function() {
var pos = this.pos, var pos = this.pos,
rest = this.rest(), rest = this.rest(),
match = rest.match(/^\d+(\.\d+)?[\s)\n]/) || rest.match(/^\d+(\.\d+)?$/); match = rest.match(/^(-)?\d+(\.\d+)?[\s)\n]/) || rest.match(/^(-)?\d+(\.\d+)?$/);
return (match != null); return (match != null);
}; };
@ -197,35 +496,46 @@ EL.Parser.prototype.lookingAtCons = function() {
_ = this.parseExpression(), _ = this.parseExpression(),
cdr = this.parseExpression(); cdr = this.parseExpression();
this.pos = orig_pos; // rewind, like it never happened. this.pos = orig_pos; // rewind, like it never happened.
return EL.typeOf(cdr) == 'array' && EL.tag(cdr) == 'symbol' && EL.val(cdr) == '.'; return EL.typeOf(cdr) == 'array' && EL.isSymbol(cdr) && EL.val(cdr) == '.';
}; };
EL.Parser.prototype.parseExpression = function() { EL.Parser.prototype.parseExpression = function() {
var value, var value,
c = this.peek(); c = this.peek();
if (c == '(' && this.lookingAtCons()) { if (c == '(' && this.lookingAtCons()) {
value = ['cons', this.parseCons()]; value = EL.consPair(this.parseCons());
} }
else if (c == '(') { else if (c == '(') {
var list = this.parseList(); var list = this.parseList();
value = (list.length > 0) ? ['list', list] : EL.nil; value = (list.length > 0) ? EL.list(list) : EL.nil;
} }
else if (c == ')') { else if (c == ')') {
return this.consumeChar(); return this.consumeChar();
} }
else if (c == "'") { else if (c == "'") {
this.consumeChar(); this.consumeChar();
value = ['list', [['symbol', 'quote']]]; value = EL.cons(EL.symbol('quote'), this.parseExpression());
value[1].push(this.parseExpression());
} }
else if (c == '"') { else if (c == '"') {
value = ['string', this.parseString()]; value = EL.string(this.parseString());
}
else if (c == '/') {
value = EL.regex(this.parseRegex());
} }
else if (this.lookingAtNumber()) { else if (this.lookingAtNumber()) {
value = ['number', this.parseNumber()]; value = EL.number(this.parseNumber());
} }
else if (c) { else if (c) {
value = ['symbol', this.parseSymbol()]; value = EL.symbol(this.parseSymbol());
}
else {
if (this.pos == this.data.length) {
print('[error] no more input. unterminated string or list? (continuing anyway)');
}
print('[warning] in EL.Parser.parseExpression: unrecognized char "' + c + '"');
print('this.pos = ' + this.pos);
print('this.data.length = ' + this.data.length);
print('this.rest = ' + this.rest());
} }
this.consumeWhitespace(); this.consumeWhitespace();
return value; return value;
@ -314,22 +624,44 @@ EL.SymbolTable.prototype.set = function(name, value) {
EL.PrimitiveVariables = [ EL.PrimitiveVariables = [
['t', { ['t', {
type: 'variable', type: 'variable',
value: true, value: ['symbol', 't'],
docstring: "true" docstring: "true"
}], }],
['nil', { ['nil', {
type: 'variable', type: 'variable',
value: null, value: ['symbol', 'nil'],
docstring: "nil" docstring: "nil"
}] }]
]; ];
// this is bound to the EL.Evaluator object // 'this' is bound to the EL.Evaluator object
EL.PrimitiveFunctions = [ EL.PrimitiveFunctions = [
['symbol-name', {
type: 'primitive',
name: 'symbol-name',
params: ['symbol'],
body: function(symbol) { return EL.string(EL.val(symbol)); },
docstring: "Return a symbol's name, a string."
}],
['string-match', {
type: 'primitive',
name: 'string-match',
params: ['regex', 'string', '&optional', 'start'],
body: function(regex, string, start) {
var index = start ? EL.val(start) : 0,
s = EL.val(string).substring(index),
match = s.match(EL.val(regex)),
found = match ? EL.number(s.indexOf(match[0])) : EL.nil;
return found;
},
docstring: "Return the index of the char matching regex in string, beginning from start if available."
}],
['+', { ['+', {
type: 'primitive', type: 'primitive',
name: '+', name: '+',
params: [], params: [/* ... */],
body: function() { body: function() {
var args = EL.Util.shallowCopy(arguments), var args = EL.Util.shallowCopy(arguments),
initial = EL.inferType(args), initial = EL.inferType(args),
@ -343,33 +675,33 @@ EL.PrimitiveFunctions = [
['-', { ['-', {
type: 'primitive', type: 'primitive',
name: '-', name: '-',
params: [], params: [/* ... */],
body: function() { body: function() {
return EL.Util.foldr(function(diff, n) { return EL.Util.foldr(function(diff, n) {
return ['number', EL.val(diff) - EL.val(n)]; return EL.number(EL.val(diff) - EL.val(n));
}, ['number', 0], EL.Util.shallowCopy(arguments)); }, EL.number(0), EL.Util.shallowCopy(arguments));
}, },
docstring: "subtract two numbers" docstring: "subtract two numbers"
}], }],
['*', { ['*', {
type: 'primitive', type: 'primitive',
name: '*', name: '*',
params: [], params: [/* ... */],
body: function() { body: function() {
return EL.Util.reduce(function(prod, n) { return EL.Util.reduce(function(prod, n) {
return ['number', EL.val(prod) * EL.val(n)]; return EL.number(EL.val(prod) * EL.val(n));
}, ['number', 1], EL.Util.shallowCopy(arguments)); }, EL.number(1), EL.Util.shallowCopy(arguments));
}, },
docstring: "multiply two numbers" docstring: "multiply two numbers"
}], }],
['/', { ['/', {
type: 'primitive', type: 'primitive',
name: '/', name: '/',
params: [], params: [/* ... */],
body: function() { body: function() {
return EL.Util.foldr(function(quot, n) { return EL.Util.foldr(function(quot, n) {
return ['number', EL.val(quot) / EL.val(n)]; return EL.number(EL.val(quot) / EL.val(n));
}, ['number', 1], EL.Util.shallowCopy(arguments)); }, EL.number(1), EL.Util.shallowCopy(arguments));
}, },
docstring: "divide two numbers" docstring: "divide two numbers"
}], }],
@ -387,7 +719,7 @@ EL.PrimitiveFunctions = [
if (tag == 'number' || tag == 'symbol' || tag == 'string') { if (tag == 'number' || tag == 'symbol' || tag == 'string') {
p(EL.val(x)); p(EL.val(x));
} }
else if (tag == 'function') { else if (tag == 'lambda') {
var fn = EL.val(x); var fn = EL.val(x);
p('(lambda ' + fn.name + ' (' + fn.params + ')\n'); p('(lambda ' + fn.name + ' (' + fn.params + ')\n');
p(fn.body); // TODO lisp pretty print p(fn.body); // TODO lisp pretty print
@ -480,30 +812,54 @@ EL.Evaluator.prototype.defineVar = function(symbol, value, docstring) {
}); });
}; };
EL.Evaluator.prototype.setVar = function(symbol, value) { EL.Evaluator.prototype.setVar = function(symbol, value, create) {
var valueObject = this.lookupVar(symbol); var valueObject = this.lookupVar(symbol);
if (!valueObject) {
if (create) {
this.defineVar(symbol, value);
}
else {
this.error('undefined-var', symbol);
}
return;
}
valueObject.value = value; valueObject.value = value;
this.variables.set(symbol, valueObject); this.variables.set(symbol, valueObject);
}; };
EL.Evaluator.prototype.defineFunc = function(symbol, params, body, docstring) { EL.Evaluator.prototype.defineFunc = function(symbol, params, body, docstring) {
this.functions.define(symbol, { this.functions.define(symbol, {
type: 'function', type: 'lambda',
name: symbol, name: symbol,
params: EL.val(params), params: params,
body: body, body: body,
docstring: docstring || "(undocumented)" docstring: docstring || "(undocumented)"
}); });
}; };
EL.Evaluator.prototype.call = function(func, args) { EL.Evaluator.prototype.doIf = function(condition, trueBlock, nilBlock) {
return EL.isNil(condition) ? this.eval(nilBlock) : this.eval(trueBlock);
};
EL.Evaluator.prototype.doCond = function(exprs) {
print('----- COND (doCond) -----');
EL.print(exprs);
return EL.nil;
};
EL.Evaluator.prototype.apply = function(func, args) {
var result; var result;
if (func.type === 'primitive') { if (func.type === 'primitive') {
// print('APPLY: ');
// EL.print(func);
// print('WITH: ');
// EL.print(args);
// print('------');
result = func.body.apply(this, args); result = func.body.apply(this, args);
} }
else { else {
this.functions.pushScope(); this.functions.pushScope();
this.variables.pushScope(func.params.map(function(e, i){ this.variables.pushScope(EL.listMap(func.params, function(e, i){
var name = EL.symbolName(e), var name = EL.symbolName(e),
value = { value = {
type: 'variable', type: 'variable',
@ -511,7 +867,8 @@ EL.Evaluator.prototype.call = function(func, args) {
}; };
return [name, value]; return [name, value];
})); }));
result = this.evalExpressions(func.body); result = EL.listLast(EL.listMap(func.body,
function(e) {return this.eval(e); }));
this.functions.popScope(); this.functions.popScope();
this.variables.popScope(); this.variables.popScope();
} }
@ -519,21 +876,19 @@ EL.Evaluator.prototype.call = function(func, args) {
}; };
EL.Evaluator.prototype.eval = function(expr) { EL.Evaluator.prototype.eval = function(expr) {
// print("EVAL: " + EL.typeOf(expr));
// EL.print(expr);
var result, x, var result, x,
tag = EL.tag(expr); tag = EL.tag(expr);
if (EL.isAtom(expr)) { if (EL.isAtom(expr)) {
return expr; result = expr;
} }
else if (tag == 'symbol') { else if (EL.isSymbol(expr)) {
var name = EL.val(expr); var name = EL.val(expr);
x = this.lookupVar(name); x = this.lookupVar(name);
if (x == null) this.error('undefined-var', name); if (x == null) this.error('undefined-var', name);
result = x.value; result = x.value;
} }
// else if (expr[0] == 'cons') {
// var cons = expr[1];
// result = [this.eval(cons[0]), this.eval(cons[1])];
// }
/////////////////// ///////////////////
// special forms // // special forms //
@ -541,24 +896,22 @@ EL.Evaluator.prototype.eval = function(expr) {
// (many could be in lisp when there are macros) // // (many could be in lisp when there are macros) //
else if (EL.isQuote(expr)) { else if (EL.isQuote(expr)) {
result = EL.val(EL.val(expr)); result = EL.cdr(expr);
} }
else if (EL.isDefVar(expr)) { else if (EL.isDefVar(expr)) {
var val = EL.val(expr), var name = EL.symbolName(EL.cadr(expr)), // 2nd param
name = EL.symbolName(val[1]), value = this.eval(EL.caddr(expr)), // 3rd param
value = this.eval(val[2]), docstring = EL.cadddr(expr); // 4th param
docstring = val[3];
// TODO check for re-definitions // TODO check for re-definitions
this.defineVar(name, value, docstring); this.defineVar(name, value, docstring);
result = EL.nil; result = EL.nil;
} }
else if (EL.isDefFunc(expr)) { else if (EL.isDefFunc(expr)) {
var val = EL.val(expr), var name = EL.symbolName(EL.nth(1, expr)),
name = EL.symbolName(val[1]), params = EL.nth(2, expr),
params = val[2], d = EL.nth(3, expr),
docstring = EL.isString(val[3]) && val[3], docstring = EL.isString(d) && d,
body = val.slice(docstring ? 4 : 3); body = EL.nthcdr(docstring ? 3 : 2, expr);
// TODO check for re-definitions
this.defineFunc(name, params, body, docstring); this.defineFunc(name, params, body, docstring);
result = EL.nil; result = EL.nil;
} }
@ -566,15 +919,18 @@ EL.Evaluator.prototype.eval = function(expr) {
var val = EL.val(expr), var val = EL.val(expr),
name = EL.symbolName(val[1]), name = EL.symbolName(val[1]),
value = this.eval(val[2]); value = this.eval(val[2]);
result = this.setVar(name, value); this.setVar(name, value);
result = value;
} }
else if (EL.isSetq(expr)) { else if (EL.isSetq(expr)) {
var val = EL.val(expr), var val = EL.val(expr),
i = 0; i = 0,
while (EL.isSymbol(val[i+1])) { n = val.length;
while (i+1 < n && EL.isSymbol(val[i+1])) {
var name = EL.symbolName(val[i+1]), var name = EL.symbolName(val[i+1]),
value = this.eval(val[i+2]); value = this.eval(val[i+2]);
result = this.setVar(name, value); this.setVar(name, value, true);
result = value;
i += 2; i += 2;
} }
} }
@ -583,30 +939,38 @@ EL.Evaluator.prototype.eval = function(expr) {
condition = this.eval(val[1]), condition = this.eval(val[1]),
trueBlock = val[2], trueBlock = val[2],
nilBlock = val[3]; nilBlock = val[3];
result = this.primitiveIf(condition, trueBlock, nilBlock); result = this.doIf(condition, trueBlock, nilBlock);
}
else if (EL.isCond(expr)) {
var val = EL.val(expr),
list = val[1],
condition = EL.car(list),
body = EL.cdr(list),
rest = val.slice(2);
result = this.doCond(exprs);
} }
// function application // function application
else if (EL.tag(expr) == 'list') { else if (EL.isCons(expr)) {
var list = expr[1], var name = EL.car(expr),
car = list[0], rest = EL.cdr(expr),
cdr = list.slice(1),
func, args; func, args;
while (!(EL.isFunction(car) || EL.isSymbol(car))) { while (!EL.isSymbol(name)) {
car = this.eval(car); name = this.eval(name);
} }
if ((func = this.lookupFunc(car[1]))) { if ((func = this.lookupFunc(EL.symbolName(name)))) {
var self = this; var self = this;
args = cdr.map(function(e){return self.eval(e);}); args = EL.listMap(rest, function(e){return self.eval(e);});
result = this.call(func, args); result = this.apply(func, args);
} }
else { else {
this.error('undefined-func', car); this.error('undefined-func', name);
} }
} }
else { else {
this.error('not-expr', expr); this.error('not-expr', expr);
} }
// print('RESULT: ' + result);
return result; return result;
}; };
@ -615,131 +979,6 @@ EL.Evaluator.prototype.eval = function(expr) {
****************************************************************/ ****************************************************************/
/****************************************************************
**** Lisp Support **********************************************
****************************************************************/
EL.nil = ['symbol', 'nil'];
EL.t = ['symbol', 't'];
EL.typeOf = function(value) {
var s = typeof value;
if (s === 'object') {
if (value) {
if (typeof value.length === 'number' &&
!(value.propertyIsEnumerable('length')) &&
typeof value.splice === 'function') {
s = 'array';
}
} else {
s = 'null';
}
}
return s;
};
EL.assert = function(condition, message) {
if (!condition()) {
throw("assertion failed: " + condition + " (" + message + ")");
}
};
EL.tag = function(expr) {
EL.assert(function() { var f='tag'; return EL.typeOf(expr) == 'array'; }, expr);
return expr[0];
};
EL.val = function(expr) {
EL.assert(function() { var f='val'; return EL.typeOf(expr) == 'array'; }, expr);
return expr[1];
};
EL.symbolName = function(symbol) {
// EL.Util.pp(symbol);
EL.assert(function(){ var f='symbolName'; return EL.isSymbol(symbol); }, symbol);
return symbol[1];
};
EL.isSymbol = function(expr) {
return (EL.tag(expr) == 'symbol');
};
EL.isString = function(expr) {
return (EL.tag(expr) == 'string');
};
EL.isList = function(expr) {
return (EL.tag(expr) == 'list');
};
EL.isFunction = function(expr) {
return (EL.tag(expr) == 'function');
};
EL.isAtom = function(expr) {
return EL.tag(expr) == 'string' || EL.tag(expr) == 'number';
};
EL.inferType = function(exprs) {
var type = 'number',
initial = 0,
i = exprs.length-1;
while(i >= 0) {
if (EL.isString(exprs[i--])) {
type = 'string';
initial = '';
break;
}
}
return [type, initial];
};
EL.isSpecialForm = function(name, expr) {
var tag = EL.tag(expr),
car = EL.typeOf(expr) == 'array' && EL.val(expr)[0],
thisName = car && EL.symbolName(car);
return (tag == 'list' && thisName == name);
};
EL.isQuote = function(expr){return EL.isSpecialForm('quote', expr);};
EL.isDefVar = function(expr){return EL.isSpecialForm('defvar', expr);};
EL.isDefFunc = function(expr){return EL.isSpecialForm('defun', expr);};
EL.isSet = function(expr){return EL.isSpecialForm('set', expr);};
EL.isSetq = function(expr){return EL.isSpecialForm('setq', expr);};
EL.isCond = function(expr){return EL.isSpecialForm('cond', expr);};
EL.isIf = function(expr){return EL.isSpecialForm('if', expr);};
EL.eval = function(exprs) {
var e = new EL.Evaluator();
return e.evalExpressions(exprs);
};
EL.parse = function(string) {
var p = new EL.Parser();
return p.parse(string);
};
EL.parseOne = function(string) {
return EL.parse(string)[0];
};
EL.read = EL.parseOne;
EL.rep = function(string) {
EL.print(EL.eval(EL.read(string)));
};
EL.repl = function() {
var p = new EL.Parser(),
e = new EL.Evaluator();
while (true) {
print("elisp> ");
EL.print(e.eval(p.parseOne(readline())));
}
};
/****************************************************************
****************************************************************/
/**************************************************************** /****************************************************************
**** Utilities ************************************************* **** Utilities *************************************************
@ -817,12 +1056,13 @@ EL.Util.pp = function(x, indent, key, noprint) {
printB(space + key + ': {'); printB(space + key + ': {');
} }
else { else {
printB(space + '{'); printB(space + ' {');
} }
for (var a in x) { for (var a in x) {
printB(EL.Util.pp(x[a], 1+indent, a, true)); printB(EL.Util.pp(x[a], 1+indent, a, true));
printB(', ');
} }
printB(space + "}"); printB(space + "} ");
break; break;
case 'string': case 'string':
@ -872,11 +1112,13 @@ EL.Util.pp = function(x, indent, key, noprint) {
} }
return buffer; return buffer;
} }
var s = buffer; if (noprint) return buffer;
if (!noprint) dumpBuffer(); else dumpBuffer();
return s;
}; };
EL.print = EL.Util.pp; EL.print = EL.Util.pp;
// everything is defined, initialize
EL.init();
// spidermonkey doesn't like a C-style comment at the end either // spidermonkey doesn't like a C-style comment at the end either
spidermonkeykludge = 1; spidermonkeykludge = 1;