From 77f1a9eb93d291ab49684eed4fbf694b299a47d2 Mon Sep 17 00:00:00 2001 From: Sami Samhuri Date: Sun, 6 Dec 2009 20:50:46 -0800 Subject: [PATCH] [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 --- README | 4 +- TODO | 14 +- el.js | 706 ++++++++++++++++++++++++++++++++++++++------------------- 3 files changed, 484 insertions(+), 240 deletions(-) diff --git a/README b/README index 9aabe16..84ccf3d 100644 --- a/README +++ b/README @@ -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 diff --git a/TODO b/TODO index 895d0f3..a10fa10 100644 --- a/TODO +++ b/TODO @@ -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) + diff --git a/el.js b/el.js index 1b1b8ab..9b07937 100644 --- a/el.js +++ b/el.js @@ -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;