[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
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.
Latest version available on github:
@ -71,7 +71,7 @@ lines.
* symbol table
(functions & variables separate)
* lexical scope
* (broken) lexical scoping
* expression evaluator

14
TODO
View file

@ -3,11 +3,11 @@ TODO
* 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,
apply, eval,
@ -15,8 +15,10 @@ TODO
* macros
* dynamic scoping
* dynamic scoping (replace the current symbol table entirely)
* merge with Ymacs?
* look into CommonJS
* merge with Ymacs? (probably not going to happen, would have to
steal what we can from them)

706
el.js
View file

@ -1,47 +1,332 @@
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
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* 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
Array.prototype.each = function(fn) {
var i = 0,
n = this.length;
while (i < n) {
fn(this[i], i);
++i;
}
};
////
// Emacs Lisp implementation in JavaScript.
//
// Copyright (c) 2009 Sami Samhuri - sami.samhuri@gmail.com
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// 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.
//
///
// our namespace
var EL = function(){};
// 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,
n = this.length;
while (i < n) {
fn(this[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);
}
}
};
/****************************************************************
****************************************************************/
/****************************************************************
**** Parser ****************************************************
@ -172,8 +457,22 @@ EL.Parser.prototype.parseSymbol = function() {
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() {
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);
});
if (this.peek() == '.') {
@ -181,13 +480,13 @@ EL.Parser.prototype.parseNumber = function() {
var decimal = this.parseUntil(/[^\d]/, '', function(s,c) {return s + c;});
value = parseFloat('' + value + '.' + decimal);
}
return value;
return sign == '-' ? -1*value : value;
};
EL.Parser.prototype.lookingAtNumber = function() {
var pos = this.pos,
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);
};
@ -197,35 +496,46 @@ EL.Parser.prototype.lookingAtCons = function() {
_ = this.parseExpression(),
cdr = this.parseExpression();
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() {
var value,
c = this.peek();
if (c == '(' && this.lookingAtCons()) {
value = ['cons', this.parseCons()];
value = EL.consPair(this.parseCons());
}
else if (c == '(') {
var list = this.parseList();
value = (list.length > 0) ? ['list', list] : EL.nil;
value = (list.length > 0) ? EL.list(list) : EL.nil;
}
else if (c == ')') {
return this.consumeChar();
}
else if (c == "'") {
this.consumeChar();
value = ['list', [['symbol', 'quote']]];
value[1].push(this.parseExpression());
value = EL.cons(EL.symbol('quote'), this.parseExpression());
}
else if (c == '"') {
value = ['string', this.parseString()];
value = EL.string(this.parseString());
}
else if (c == '/') {
value = EL.regex(this.parseRegex());
}
else if (this.lookingAtNumber()) {
value = ['number', this.parseNumber()];
value = EL.number(this.parseNumber());
}
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();
return value;
@ -314,22 +624,44 @@ EL.SymbolTable.prototype.set = function(name, value) {
EL.PrimitiveVariables = [
['t', {
type: 'variable',
value: true,
value: ['symbol', 't'],
docstring: "true"
}],
['nil', {
type: 'variable',
value: null,
value: ['symbol', 'nil'],
docstring: "nil"
}]
];
// this is bound to the EL.Evaluator object
// 'this' is bound to the EL.Evaluator object
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',
name: '+',
params: [],
params: [/* ... */],
body: function() {
var args = EL.Util.shallowCopy(arguments),
initial = EL.inferType(args),
@ -343,33 +675,33 @@ EL.PrimitiveFunctions = [
['-', {
type: 'primitive',
name: '-',
params: [],
params: [/* ... */],
body: function() {
return EL.Util.foldr(function(diff, n) {
return ['number', EL.val(diff) - EL.val(n)];
}, ['number', 0], EL.Util.shallowCopy(arguments));
return EL.number(EL.val(diff) - EL.val(n));
}, EL.number(0), EL.Util.shallowCopy(arguments));
},
docstring: "subtract two numbers"
}],
['*', {
type: 'primitive',
name: '*',
params: [],
params: [/* ... */],
body: function() {
return EL.Util.reduce(function(prod, n) {
return ['number', EL.val(prod) * EL.val(n)];
}, ['number', 1], EL.Util.shallowCopy(arguments));
return EL.number(EL.val(prod) * EL.val(n));
}, EL.number(1), EL.Util.shallowCopy(arguments));
},
docstring: "multiply two numbers"
}],
['/', {
type: 'primitive',
name: '/',
params: [],
params: [/* ... */],
body: function() {
return EL.Util.foldr(function(quot, n) {
return ['number', EL.val(quot) / EL.val(n)];
}, ['number', 1], EL.Util.shallowCopy(arguments));
return EL.number(EL.val(quot) / EL.val(n));
}, EL.number(1), EL.Util.shallowCopy(arguments));
},
docstring: "divide two numbers"
}],
@ -387,7 +719,7 @@ EL.PrimitiveFunctions = [
if (tag == 'number' || tag == 'symbol' || tag == 'string') {
p(EL.val(x));
}
else if (tag == 'function') {
else if (tag == 'lambda') {
var fn = EL.val(x);
p('(lambda ' + fn.name + ' (' + fn.params + ')\n');
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);
if (!valueObject) {
if (create) {
this.defineVar(symbol, value);
}
else {
this.error('undefined-var', symbol);
}
return;
}
valueObject.value = value;
this.variables.set(symbol, valueObject);
};
EL.Evaluator.prototype.defineFunc = function(symbol, params, body, docstring) {
this.functions.define(symbol, {
type: 'function',
type: 'lambda',
name: symbol,
params: EL.val(params),
params: params,
body: body,
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;
if (func.type === 'primitive') {
// print('APPLY: ');
// EL.print(func);
// print('WITH: ');
// EL.print(args);
// print('------');
result = func.body.apply(this, args);
}
else {
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),
value = {
type: 'variable',
@ -511,7 +867,8 @@ EL.Evaluator.prototype.call = function(func, args) {
};
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.variables.popScope();
}
@ -519,21 +876,19 @@ EL.Evaluator.prototype.call = function(func, args) {
};
EL.Evaluator.prototype.eval = function(expr) {
// print("EVAL: " + EL.typeOf(expr));
// EL.print(expr);
var result, x,
tag = EL.tag(expr);
if (EL.isAtom(expr)) {
return expr;
result = expr;
}
else if (tag == 'symbol') {
else if (EL.isSymbol(expr)) {
var name = EL.val(expr);
x = this.lookupVar(name);
if (x == null) this.error('undefined-var', name);
result = x.value;
}
// else if (expr[0] == 'cons') {
// var cons = expr[1];
// result = [this.eval(cons[0]), this.eval(cons[1])];
// }
///////////////////
// special forms //
@ -541,24 +896,22 @@ EL.Evaluator.prototype.eval = function(expr) {
// (many could be in lisp when there are macros) //
else if (EL.isQuote(expr)) {
result = EL.val(EL.val(expr));
result = EL.cdr(expr);
}
else if (EL.isDefVar(expr)) {
var val = EL.val(expr),
name = EL.symbolName(val[1]),
value = this.eval(val[2]),
docstring = val[3];
var name = EL.symbolName(EL.cadr(expr)), // 2nd param
value = this.eval(EL.caddr(expr)), // 3rd param
docstring = EL.cadddr(expr); // 4th param
// TODO check for re-definitions
this.defineVar(name, value, docstring);
result = EL.nil;
}
else if (EL.isDefFunc(expr)) {
var val = EL.val(expr),
name = EL.symbolName(val[1]),
params = val[2],
docstring = EL.isString(val[3]) && val[3],
body = val.slice(docstring ? 4 : 3);
// TODO check for re-definitions
var name = EL.symbolName(EL.nth(1, expr)),
params = EL.nth(2, expr),
d = EL.nth(3, expr),
docstring = EL.isString(d) && d,
body = EL.nthcdr(docstring ? 3 : 2, expr);
this.defineFunc(name, params, body, docstring);
result = EL.nil;
}
@ -566,15 +919,18 @@ EL.Evaluator.prototype.eval = function(expr) {
var val = EL.val(expr),
name = EL.symbolName(val[1]),
value = this.eval(val[2]);
result = this.setVar(name, value);
this.setVar(name, value);
result = value;
}
else if (EL.isSetq(expr)) {
var val = EL.val(expr),
i = 0;
while (EL.isSymbol(val[i+1])) {
i = 0,
n = val.length;
while (i+1 < n && EL.isSymbol(val[i+1])) {
var name = EL.symbolName(val[i+1]),
value = this.eval(val[i+2]);
result = this.setVar(name, value);
this.setVar(name, value, true);
result = value;
i += 2;
}
}
@ -583,30 +939,38 @@ EL.Evaluator.prototype.eval = function(expr) {
condition = this.eval(val[1]),
trueBlock = val[2],
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
else if (EL.tag(expr) == 'list') {
var list = expr[1],
car = list[0],
cdr = list.slice(1),
else if (EL.isCons(expr)) {
var name = EL.car(expr),
rest = EL.cdr(expr),
func, args;
while (!(EL.isFunction(car) || EL.isSymbol(car))) {
car = this.eval(car);
while (!EL.isSymbol(name)) {
name = this.eval(name);
}
if ((func = this.lookupFunc(car[1]))) {
if ((func = this.lookupFunc(EL.symbolName(name)))) {
var self = this;
args = cdr.map(function(e){return self.eval(e);});
result = this.call(func, args);
args = EL.listMap(rest, function(e){return self.eval(e);});
result = this.apply(func, args);
}
else {
this.error('undefined-func', car);
this.error('undefined-func', name);
}
}
else {
this.error('not-expr', expr);
}
// print('RESULT: ' + 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 *************************************************
@ -817,12 +1056,13 @@ EL.Util.pp = function(x, indent, key, noprint) {
printB(space + key + ': {');
}
else {
printB(space + '{');
printB(space + ' {');
}
for (var a in x) {
printB(EL.Util.pp(x[a], 1+indent, a, true));
printB(', ');
}
printB(space + "}");
printB(space + "} ");
break;
case 'string':
@ -872,11 +1112,13 @@ EL.Util.pp = function(x, indent, key, noprint) {
}
return buffer;
}
var s = buffer;
if (!noprint) dumpBuffer();
return s;
if (noprint) return buffer;
else dumpBuffer();
};
EL.print = EL.Util.pp;
// everything is defined, initialize
EL.init();
// spidermonkey doesn't like a C-style comment at the end either
spidermonkeykludge = 1;