From bfa09931e9f9f15e3e4a648fd45d92638dbea9ee Mon Sep 17 00:00:00 2001 From: Sami Samhuri Date: Sun, 20 Dec 2009 15:54:19 -0800 Subject: [PATCH] Migrated from plain Spidermonkey to CommonJS (narwhal), hence to real modules as well. --- elisp.js | 51 ++++++--- elisp.sh | 2 +- elisp/evaluator.js | 170 ++++++++++++++-------------- elisp/init.js | 16 ++- elisp/jsExt.js | 57 +--------- elisp/list.js | 135 +++++++++++++--------- elisp/parser.js | 124 ++++++++++---------- elisp/primitives.js | 196 ++++++++++++++++---------------- elisp/repl.js | 59 ++++++---- elisp/settings.js | 10 ++ elisp/symtab.js | 28 ++--- elisp/trace.js | 46 ++++++++ elisp/types.js | 270 +++++++++++++++++++++++++++++++++----------- elisp/util.js | 143 ----------------------- elisp/utils.js | 101 +++++++++++++++++ repl.js | 3 + 16 files changed, 796 insertions(+), 615 deletions(-) create mode 100644 elisp/settings.js create mode 100644 elisp/trace.js delete mode 100644 elisp/util.js create mode 100644 elisp/utils.js create mode 100644 repl.js diff --git a/elisp.js b/elisp.js index bc42005..a11bb60 100644 --- a/elisp.js +++ b/elisp.js @@ -23,26 +23,47 @@ // /// +if (require.paths[0] != '.') require.paths.unshift('.'); +//require.paths.unshift('elisp'); -// our namespace -var elisp = function(){}; +//print('[elisp starting]'); +var init = require('elisp/init'); // simple init system +exports.init = init; +//print('* init'); -load('elisp/init.js'); // simple init system -load('elisp/jsExt.js'); // a few extensions to native types -load('elisp/util.js'); // utilities +require('elisp/jsExt'); // extensions to native types +//print('* jsExt'); + +exports.utils = require('elisp/utils'); +//print('* utils'); // main lisp system -load('elisp/types.js'); -load('elisp/list.js'); -load('elisp/symtab.js'); -load('elisp/parser.js'); -load('elisp/evaluator.js'); -load('elisp/primitives.js'); -load('elisp/repl.js'); +exports.type = require('elisp/types'); +exports.T = exports.type.T; +exports.NIL = exports.type.NIL; +//print('* type'); + +exports.list = require('elisp/list'); +//print('* list'); + +exports.symtab = require('elisp/symtab'); +//print('* symtab'); + +exports.parser = require('elisp/parser'); +//print('* parser'); + +exports.primitives = require('elisp/primitives'); +//print('* primitives'); + +exports.evaluator = require('elisp/evaluator'); +//print('* evaluator'); + +var repl = require('elisp/repl'); +exports.repl = repl; +//print('* repl'); // everything is defined, initialize -elisp.init(); +init.initialize(); -// q to quit -elisp.repl(); \ No newline at end of file +//print('[elisp ready]'); diff --git a/elisp.sh b/elisp.sh index 82efbff..41d14ee 100755 --- a/elisp.sh +++ b/elisp.sh @@ -1,3 +1,3 @@ #!/bin/sh -rlwrap /opt/local/bin/js elisp.js +NARWHAL_ENGINE=jsc rlwrap js repl.js diff --git a/elisp/evaluator.js b/elisp/evaluator.js index 208a71f..21563fb 100644 --- a/elisp/evaluator.js +++ b/elisp/evaluator.js @@ -6,45 +6,51 @@ // Released under the terms of the MIT license. See the included file // LICENSE. +var type = require('elisp/types'), + utils = require('elisp/utils'), + primitives = require('elisp/primitives'), + symtab = require('elisp/symtab'); -elisp.Evaluator = function(exprs) { +Evaluator = function(exprs) { this.expressions = exprs; - this.variables = new elisp.SymbolTable(elisp.PrimitiveVariables); - this.functions = new elisp.SymbolTable(elisp.PrimitiveFunctions); + this.variables = new symtab.SymbolTable(primitives.PrimitiveVariables); + this.functions = new symtab.SymbolTable(primitives.PrimitiveFunctions); }; -elisp.Evaluator.Error = function(name, message, expr) { +Evaluator.Error = function(name, message, expr) { this.evalError = true; this.name = name; this.message = message; - this.expression = expr; + this.expression = utils.pp(expr, true); }; -elisp.Evaluator.Error.messages = { +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)); +Evaluator.prototype.error = function(name, expr) { + throw(new Evaluator.Error(name, Evaluator.Error.messages[name], expr)); }; -elisp.Evaluator.prototype.evalExpressions = function(expressions) { +Evaluator.prototype.evalExpressions = function(expressions) { + // TODO this should use lisp's map or reduce for (some) efficiency var exprs = expressions || this.expressions, i = 0, - n = exprs.length, - result; + n = exprs.length(), + result, expr; while (i < n) { try { - result = this.eval(exprs[i++]); + expr = exprs.nth(i++); + result = this.eval(expr); } catch (e) { if (e.evalError) { print('[error] ' + e.message); if (e.expression) { print("got: " + e.expression); } - result = elisp.nil; + result = type.NIL; break; } else { @@ -52,58 +58,57 @@ elisp.Evaluator.prototype.evalExpressions = function(expressions) { } } } -// elisp.Util.pp(result); +// Utils.pp(result); return result; }; -elisp.Evaluator.prototype.lookupVar = function(symbol) { +Evaluator.prototype.lookupVar = function(symbol) { return this.variables.lookup(symbol); }; -elisp.Evaluator.prototype.lookupFunc = function(symbol) { +Evaluator.prototype.lookupFunc = function(symbol) { return this.functions.lookup(symbol); }; -elisp.Evaluator.prototype.apply = function(func, args) { +Evaluator.prototype.apply = function(func, args) { var result; if (func.type === 'primitive') { // print('APPLY: '); -// elisp.print(func); +// print(func); // print('WITH: '); -// elisp.print(args); +// 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), + this.variables.pushScope(func.params.map(function(e, i){ + var name = e.symbolName(), value = { type: 'variable', value: this.eval(args[i]) }; return [name, value]; })); - result = elisp.listLast(elisp.listMap(func.body, - function(e) {return this.eval(e); })); + result = func.body.map(function(e) {return this.eval(e); }).last(); this.functions.popScope(); this.variables.popScope(); } return result; }; -elisp.Evaluator.prototype.eval = function(expr) { -// print("EVAL: " + elisp.typeOf(expr)); -// elisp.print(expr); +Evaluator.prototype.eval = function(expr) { +// print('[Evaluator.eval]'); + //utils.pp(expr); var result, x, - tag = elisp.tag(expr); - if (elisp.isAtom(expr)) { + tag = expr.tag(); + if (expr.isAtom()) { result = expr; } - else if (elisp.isSymbol(expr)) { - var name = elisp.val(expr); + else if (expr.isSymbol()) { + var name = expr.symbolName(); x = this.lookupVar(name); - if (x == null) this.error('undefined-var', name); + if (!x) this.error('undefined-var', name); result = x.value; } @@ -112,75 +117,74 @@ elisp.Evaluator.prototype.eval = function(expr) { /////////////////// // (many could be in lisp when there are macros) // - else if (elisp.isQuote(expr)) { - result = elisp.cdr(expr); + else if (expr.isQuote()) { + result = expr.cdr(); } - 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 + else if (expr.isDefvar()) { + var name = expr.cadr().symbolName(), // 2nd param + value = this.eval(expr.caddr()), // 3rd param + docstring = expr.cadddr(); // 4th param // TODO check for re-definitions this.defineVar(name, value, docstring); - result = elisp.nil; + result = type.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); + else if (expr.isDefun()) { + var name = expr.nth(1).symbolName(), + params = expr.nth(2), + d = expr.nth(3), + docstring = d.isString() && d, + body = expr.nthcdr(docstring ? 3 : 2); this.defineFunc(name, params, body, docstring); - result = elisp.nil; + result = type.NIL; } - else if (elisp.isSet(expr)) { - var val = elisp.val(expr), - name = elisp.symbolName(val[1]), - value = this.eval(val[2]); + else if (expr.isSet()) { + var name = expr.car().symbolName(), + value = this.eval(expr.cdr()); this.setVar(name, value); result = value; } - else if (elisp.isSetq(expr)) { + else if (expr.isSetq()) { var i = 1, - n = elisp.listLength(expr), + n = expr.length(), 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)); + while (i < n && (e=expr.nth(i)).isSymbol()) { + var name = e.symbolName(), + value = this.eval(expr.nth(i+1)); 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]; + else if (expr.isIf()) { + var condition = this.eval(expr.nth(1)), + trueBlock = expr.nth(2), + nilBlock = expr.nth(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); + else if (expr.isCond()) { + // TODO implement me + result = type.NIL; +// var list = expr.nth(1), +// condition = list.car(), +// body = list.cdr(), +// rest = val.slice(2); +// result = this.doCond(exprs); } // function application - else if (elisp.isCons(expr)) { - var name = elisp.car(expr), - rest = elisp.cdr(expr), + else if (expr.isCons()) { + var name = expr.car(), + rest = expr.cdr(), func, args; - while (!elisp.isSymbol(name)) { + while (!name.isSymbol()) { name = this.eval(name); } - if ((func = this.lookupFunc(elisp.symbolName(name)))) { + if ((func = this.lookupFunc(name.symbolName()))) { var self = this; - args = elisp.listReduce(function(a,e){ + args = rest.reduce([], function(a,e){ a.push(self.eval(e)); return a; - }, [], rest); + }); result = this.apply(func, args); } else { @@ -195,7 +199,7 @@ elisp.Evaluator.prototype.eval = function(expr) { }; -elisp.Evaluator.prototype.defineVar = function(symbol, value, docstring) { +Evaluator.prototype.defineVar = function(symbol, value, docstring) { this.variables.define(symbol, { type: 'variable', value: value, @@ -203,11 +207,11 @@ elisp.Evaluator.prototype.defineVar = function(symbol, value, docstring) { }); }; -elisp.Evaluator.prototype.setVar = function(symbol, value, create) { +Evaluator.prototype.setVar = function(symbol, value, create) { var valueObject = this.lookupVar(symbol); if (!valueObject) { if (create) { - this.defineVar(symbol, elisp.nil); + this.defineVar(symbol, type.NIL); valueObject = this.lookupVar(symbol); } else { @@ -218,7 +222,7 @@ elisp.Evaluator.prototype.setVar = function(symbol, value, create) { this.variables.set(symbol, valueObject); }; -elisp.Evaluator.prototype.defineFunc = function(symbol, params, body, docstring) { +Evaluator.prototype.defineFunc = function(symbol, params, body, docstring) { this.functions.define(symbol, { type: 'lambda', name: symbol, @@ -228,14 +232,14 @@ elisp.Evaluator.prototype.defineFunc = function(symbol, params, body, docstring) }); }; -elisp.Evaluator.prototype.doIf = function(condition, trueBlock, nilBlock) { - return elisp.isNil(condition) ? this.eval(nilBlock) : this.eval(trueBlock); +Evaluator.prototype.doIf = function(condition, trueBlock, nilBlock) { + return condition.isNil() ? this.eval(nilBlock) : this.eval(trueBlock); }; -elisp.Evaluator.prototype.doCond = function(exprs) { +Evaluator.prototype.doCond = function(exprs) { print('----- COND (doCond) -----'); - elisp.print(exprs); - return elisp.nil; + utils.pp(exprs); + return type.NIL; }; - +exports.Evaluator = Evaluator; \ No newline at end of file diff --git a/elisp/init.js b/elisp/init.js index d76547c..b1a7079 100644 --- a/elisp/init.js +++ b/elisp/init.js @@ -10,14 +10,18 @@ // 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. -elisp._initHooks = []; -elisp.initHook = function(hook) { - elisp._initHooks.push(hook); + +var hooks = []; + +exports.hook = function(name, hook) { + hooks.push({hook: hook, name: name}); }; -elisp.init = function() { + +exports.initialize = function() { var i = 0, - n = elisp._initHooks.length; + n = hooks.length; while (i < n) { - elisp._initHooks[i++].call(); +// print('**** INIT HOOK: ' + hooks[i].name + ' *****'); + hooks[i++].hook.call(); } }; diff --git a/elisp/jsExt.js b/elisp/jsExt.js index eba99a8..b31cb6b 100644 --- a/elisp/jsExt.js +++ b/elisp/jsExt.js @@ -6,55 +6,8 @@ // Released under the terms of the MIT license. See the included file // LICENSE. -// Just a little sugar -elisp.initHook(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. - elisp.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 - elisp.assert = function(condition, message) { - if (!condition()) { - throw("assertion failed: " + condition + " (" + message + ")"); - } - }; -}); +// Simple inheritance. e.g. ChildObject.extend(ParentObject) +Function.prototype.extend = function(superclass) { + this.prototype = new superclass; + this.prototype.constructor = this; +}; diff --git a/elisp/list.js b/elisp/list.js index 8c9d161..f5f3d5e 100644 --- a/elisp/list.js +++ b/elisp/list.js @@ -6,98 +6,121 @@ // Released under the terms of the MIT license. See the included file // LICENSE. -elisp.consPair = function(pair) { - var cons = ['cons', pair]; - cons.isList = true; - return cons; +var type = require('elisp/types'), + utils = require('elisp/utils'), + LispCons = type.LispCons, + NIL = type.NIL; + +LispCons.prototype.car = function() { +// print('[LispCons.car] calling this.isNil - this: ' + this + ' - _car: ' + this._car); +// print('------'); +// print('' + this._tag); +// print('------'); +// print('car: ' + this._car._tag); +// print('------'); +// print('cdr: ' + this._cdr._tag); +// print('------'); + //utils.pp(this); + return this.isNil() ? this : this._car; }; -elisp.cons = function(car, cdr) { - return elisp.consPair([car, cdr]); +LispCons.prototype.cdr = function() { +// print('[LispCons.cdr] calling this.isNil - this: ' + this + ' - _cdr: ' + this._cdr); + return this.isNil() ? this : this._cdr; }; -elisp.car = function(cons) { - return elisp.isNil(cons) ? cons : elisp.val(cons)[0]; +LispCons.prototype.cadr = function() { + return this.cdr().car(); }; -elisp.cdr = function(cons) { - return elisp.isNil(cons) ? cons : elisp.val(cons)[1]; +LispCons.prototype.caddr = function() { + return this.cdr().cdr().car(); }; -elisp.cadr = function(cons) { - return elisp.car(elisp.cdr(cons)); +LispCons.prototype.cadddr = function() { + return this.cdr().cdr().cdr().car(); }; -elisp.caddr = function(cons) { - return elisp.car(elisp.cdr(elisp.cdr(cons))); +LispCons.prototype.length = function() { + return this._length; }; -elisp.cadddr = function(cons) { - return elisp.car(elisp.cdr(elisp.cdr(elisp.cdr(cons)))); -}; - -elisp.listLength = function(cons) { - var n = 0; - while (!elisp.isNil(cons)) { - cons = elisp.cdr(cons); - ++n; - } - return n; -}; - -elisp.listLast = function(cons) { - var last; - while (!elisp.isNil(cons)) { +LispCons.prototype.last = function() { + var last, + cons = this; +// print('[LispCons.last] calling cons.isNil - cons: ' + cons + ' - _cdr: ' + cons._cdr); + while (!cons.isNil()) { last = cons; - cons = elisp.cdr(cons); + cons = cons.cdr(); } - return elisp.car(last); + return last.car(); }; -elisp.listMap = function(cons, fn) { +LispCons.prototype.map = function(fn) { var list = [], - i = 0; - while (!elisp.isNil(cons)) { - list.push(fn(elisp.car(cons), i)); - cons = elisp.cdr(cons); + i = 0, + cons = this; +// print('[LispCons.map] calling cons.isNil - cons: ' + cons + ' - _car: ' + cons._car + ' _cdr: ' + this._cdr); + while (!cons.isNil()) { + list.push(fn(cons.car(), i)); + cons = cons.cdr(); ++i; } - return list.length > 0 ? elisp.list(list) : elisp.nil; + return list.length > 0 ? type.mkList(list) : type.NIL; }; -elisp.listReduce = function(fn, accum, cons) { +LispCons.prototype.reduce = function(accum, fn) { var i = 0, - n = elisp.listLength(cons); + n = this._length; while (i < n) { - accum = fn(accum, elisp.nth(i++, cons)); + accum = fn(accum, this.nth(i++)); } return accum; }; -elisp.idFunction = function(x){return x;}; - -elisp.unlist = function(cons) { - return elisp.listReduce(elisp.idFunction, [], cons); +LispCons.prototype.unlist = function() { + return this.reduce([], function(x){return x;}); }; -elisp.nth = function(n, cons) { +LispCons.prototype.nth = function(n) { var i = 0, - e; - while (i <= n && !elisp.isNil(cons)) { - e = elisp.car(cons); - cons = elisp.cdr(cons); + e, + cons = this; +// print('[LispCons.nth] calling cons.isNil - cons: ' + cons + ' - _cdr: ' + cons._cdr); + while (i <= n && !cons.isNil()) { + e = cons.car(); + cons = cons.cdr(); ++i; } - return n > --i ? elisp.nil : e; + return n > --i ? type.NIL : e; }; -elisp.nthcdr = function(n, cons) { - var e = elisp.cdr(cons), +LispCons.prototype.nthcdr = function(n) { + var e = this.cdr(), i = 0; - while (i < n && !elisp.isNil(e)) { - e = elisp.cdr(e); +// print('[LispCons.nthcdr] calling e.isNil - e: ' + e + ' - _cdr: ' + e._cdr); + while (i < n && !e.isNil()) { + e = e.cdr(); ++i; } - return n > i ? elisp.nil : e; + return n > i ? type.NIL : e; }; + +// Make NIL act like a list ... there's got to be a better way + +NIL.unlist = function(){return [];}; + +NIL._length = 0; +NIL.length = function(){return 0;}; + +NIL.reduce = function(accum){return accum;}; + +nilMethods = ['car', 'cdr', 'cadr', 'caddr', 'cadddr', + 'last', 'map', 'nthcdr', 'nth']; + +var nilFn = function(){return NIL;}; + +for (var method in nilMethods) { + NIL[method] = nilFn; +} \ No newline at end of file diff --git a/elisp/parser.js b/elisp/parser.js index b3307db..31faa56 100644 --- a/elisp/parser.js +++ b/elisp/parser.js @@ -6,54 +6,56 @@ // Released under the terms of the MIT license. See the included file // LICENSE. +var utils = require('elisp/utils'), + type = require('elisp/types'); -elisp.Parser = function(data) { +var Parser = function(data) { this.data = data || ''; }; -elisp.Parser.Error = function(name, message) { +Parser.Error = function(name, message) { this.parserError = true; this.name = name; this.message = message; }; -elisp.Parser.Error.messages = { +Parser.Error.messages = { 'eof': "no more input" }; -elisp.Parser.prototype.error = function(name) { - throw(new elisp.Parser.Error(name, elisp.Parser.Error.messages[name])); +Parser.prototype.error = function(name) { + throw(new Parser.Error(name, Parser.Error.messages[name])); }; -elisp.Parser.prototype.peek = function() { +Parser.prototype.peek = function() { return this.data[this.pos]; }; -elisp.Parser.prototype.consumeChar = function() { +Parser.prototype.consumeChar = function() { if (this.pos >= this.data.length) this.error('eof'); return this.data[this.pos++]; }; -elisp.Parser.prototype.consumeWhitespace = function() { +Parser.prototype.consumeWhitespace = function() { var c; while ((c = this.peek()) && c.match(/[\s\n]/)) { this.consumeChar(); } }; -elisp.Parser.prototype.rewind = function() { +Parser.prototype.rewind = function() { this.pos = 0; }; -elisp.Parser.prototype.rest = function() { +Parser.prototype.rest = function() { return this.data.substring(this.pos); }; -elisp.Parser.prototype.moreInput = function() { +Parser.prototype.moreInput = function() { return (this.pos < this.data.length); }; -elisp.Parser.prototype.parse = function(string) { +Parser.prototype.parse = function(string) { if (string) this.data = string; this.rewind(); var exprs = []; @@ -72,19 +74,19 @@ elisp.Parser.prototype.parse = function(string) { } this.expressions = exprs; // print(''); -// elisp.Util.pp(exprs); +// Utils.pp(exprs); // print(''); - return exprs; + return type.mkList(exprs); }; -elisp.Parser.prototype.parseOne = function(string) { - return this.parse(string)[0]; +Parser.prototype.parseOne = function(string) { + return this.parse(string).car(); }; -elisp.Parser.prototype.parseUntil = function(regex, initial, next, consumeTerminator) { +Parser.prototype.parseUntil = function(regex, initial, next, consumeTerminator) { var c, token = initial, - condition = function(c){ return c.match(regex) == null; }; + condition = function(c){ return c.match(regex) === null; }; while ((c = this.peek()) && condition(c)) { token = next(token, this.consumeChar()); } @@ -92,7 +94,7 @@ elisp.Parser.prototype.parseUntil = function(regex, initial, next, consumeTermin return token; }; -elisp.Parser.prototype.parseList = function() { +Parser.prototype.parseList = function() { var list = [], expr; // consume initial paren '(' @@ -100,40 +102,43 @@ elisp.Parser.prototype.parseList = function() { while ((expr = this.parseExpression()) && expr != ')') { list.push(expr); } - return list; + return type.mkList(list); }; -elisp.Parser.prototype.parseCons = function() { - var cons = [], - expr; +Parser.prototype.parseCons = function() { + var car, cdr, expr; + // consume initial paren '(' this.consumeChar(); - cons.push(this.parseExpression()); + + car = this.parseExpression(); + // ignore . this.parseExpression(); - cons.push(this.parseExpression()); - return cons; + + cdr = this.parseExpression(); + return new type.LispCons(car, cdr); }; -elisp.Parser.prototype.parseString = function() { +Parser.prototype.parseString = function() { // consume initial quotation mark this.consumeChar(); var self = this; - return this.parseUntil(/"/, '', function(s,c){ + return new type.LispString(this.parseUntil(/"/, '', function(s,c){ if (c == '\\') { c = self.consumeChar(); } return s + c; - }, true /* consume terminator */); + }, true /* consume terminator */)); }; -elisp.Parser.prototype.parseSymbol = function() { +Parser.prototype.parseSymbol = function() { var symbol = this.parseUntil(/[\s()]/, '', function(t,c){return t + c;}); - return symbol; + return new type.LispSymbol(symbol); }; // Probably easy to break -elisp.Parser.prototype.parseRegex = function() { +Parser.prototype.parseRegex = function() { // consume initial slash this.consumeChar(); var self = this; @@ -159,9 +164,9 @@ elisp.Parser.prototype.parseRegex = function() { // // Binary, octal, hex, or arbitrary radix integers not yet parsed. // (e.g. #x100 == #o400 == #b100000000 == #24rag -elisp.Parser.prototype.parseNumber = function() { +Parser.prototype.parseNumber = function() { var value = this.parseIntOrFloat(), - exponentAllowed = value === parseInt(value), + exponentAllowed = value === parseInt(value, 10), exp; // now check for an exponent @@ -175,11 +180,11 @@ elisp.Parser.prototype.parseNumber = function() { value *= Math.pow(10, exp); } - return value; + return new type.LispNumber(value); }; // Pack int and float parsing together for simplicity's sake. -elisp.Parser.prototype.parseIntOrFloat = function() { +Parser.prototype.parseIntOrFloat = function() { this.exponentAllowed = true; var sign = this.peek() == '-' || this.peek() == '+' ? this.consumeChar() : '+', value; @@ -187,7 +192,7 @@ elisp.Parser.prototype.parseIntOrFloat = function() { // There may or may not be an integer part of the number. if (this.peek() != '.') { value = this.parseUntil(/[^\d]/, 0, function(n,c) { - return n*10 + parseInt(c); + return n*10 + parseInt(c, 10); }); } @@ -217,58 +222,61 @@ elisp.Parser.prototype.parseIntOrFloat = function() { // string or some chars in the same regex. // // TODO: pick up Friedl and find a way to consolidate these. -elisp.Parser.prototype.lookingAtNumber = function() { +Parser.prototype.lookingAtNumber = function() { var pos = this.pos, rest = this.rest(), - match = rest.match(/^[+-]?\d+(\.\d*)?[)\s\n]/) - || rest.match(/^[+-]?\d+(\.\d*)?$/) - || rest.match(/^[+-]?\d+(\.\d+)?([eE][+-]?\d+)?[)\s\n]/) - || rest.match(/^[+-]?\d+(\.\d+)?([eE][+-]?\d+)?$/) - || rest.match(/^[+-]?(\d+)?\.\d+([eE][+-]?\d+)?[)\s\n]/) - || rest.match(/^[+-]?(\d+)?\.\d+([eE][+-]?\d+)?$/); - return (match != null); + match = rest.match(/^[+-]?\d+(\.\d*)?[)\s\n]/) || + rest.match(/^[+-]?\d+(\.\d*)?$/) || + rest.match(/^[+-]?\d+(\.\d+)?([eE][+-]?\d+)?[)\s\n]/) || + rest.match(/^[+-]?\d+(\.\d+)?([eE][+-]?\d+)?$/) || + rest.match(/^[+-]?(\d+)?\.\d+([eE][+-]?\d+)?[)\s\n]/) || + rest.match(/^[+-]?(\d+)?\.\d+([eE][+-]?\d+)?$/); + return (match !== null); }; -elisp.Parser.prototype.lookingAtCons = function() { +Parser.prototype.lookingAtCons = function() { + // FIXME + return false; + var orig_pos = this.pos, _ = this.consumeChar(), __ = _ && this.peek() && this.parseExpression(), cdr = __ && this.peek() &&this.parseExpression(); this.pos = orig_pos; // rewind, like it never happened. - return _ == ')' || cdr && elisp.typeOf(cdr) == 'array' && elisp.isSymbol(cdr) && elisp.val(cdr) == '.'; +// print('[Parser.lookingAtCons]'); + return _ == ')' || cdr.isCons() && cdr.isSymbol() && cdr.symbolName() == '.'; }; -elisp.Parser.prototype.parseExpression = function() { +Parser.prototype.parseExpression = function() { var value, c = this.peek(); if (c == '(' && this.lookingAtCons()) { - value = elisp.consPair(this.parseCons()); + value = this.parseCons(); } else if (c == '(') { - var list = this.parseList(); - value = (list.length > 0) ? elisp.list(list) : elisp.nil; + value = this.parseList(); } else if (c == ')') { return this.consumeChar(); } else if (c == "'") { this.consumeChar(); - value = elisp.cons(elisp.symbol('quote'), this.parseExpression()); + value = new type.LispCons(new type.LispSymbol('quote'), this.parseExpression()); } else if (c == '"') { - value = elisp.string(this.parseString()); + value = this.parseString(); } else if (this.lookingAtNumber()) { - value = elisp.number(this.parseNumber()); + value = this.parseNumber(); } else if (c) { - value = elisp.symbol(this.parseSymbol()); + value = this.parseSymbol(); } else { if (this.pos == this.data.length) { print('[error] no more input. unterminated string or list? (continuing anyway)'); } - print('[warning] in elisp.Parser.parseExpression: unrecognized char "' + c + '"'); + print('[warning] in Parser.parseExpression: unrecognized char "' + c + '"'); print('this.pos = ' + this.pos); print('this.data.length = ' + this.data.length); print('this.rest = ' + this.rest()); @@ -277,6 +285,4 @@ elisp.Parser.prototype.parseExpression = function() { return value; }; -/**************************************************************** - ****************************************************************/ - +exports.Parser = Parser; diff --git a/elisp/primitives.js b/elisp/primitives.js index 58687ad..1e87d48 100644 --- a/elisp/primitives.js +++ b/elisp/primitives.js @@ -6,25 +6,32 @@ // Released under the terms of the MIT license. See the included file // LICENSE. +var init = require('elisp/init'), + type = require('elisp/types'), + utils = require('elisp/utils'); -elisp.PrimitiveVariables = [ +var PrimitiveVariables = [ ['t', { type: 'variable', - value: ['symbol', 't'], + value: type.T, docstring: "true" }], ['nil', { type: 'variable', - value: ['symbol', 'nil'], + value: type.NIL, docstring: "nil" }] ]; +exports.PrimitiveVariables = PrimitiveVariables; -elisp.PrimitiveFunctions = []; -// 'this' is bound to the elisp.Evaluator object when executing primitve functions -elisp.definePrimitive = function(name, params, body, docstring) { - elisp.PrimitiveFunctions.push([name, { +var PrimitiveFunctions = []; +exports.PrimitiveFunctions = PrimitiveFunctions; + +// 'this' is bound to the evaluator.Evaluator object when executing primitve functions +var definePrimitive = function(name, params, body, docstring) { +// print('*** DEFINING: ' + name); + PrimitiveFunctions.push([name, { type: 'primitive', name: name, params: params, // unused right now but should be checked @@ -32,100 +39,95 @@ elisp.definePrimitive = function(name, params, body, docstring) { body: body }]); }; +exports.definePrimitive = definePrimitive; -elisp.notFunc = function(fn) { +var notFunc = function(fn) { return function(x){ return !fn(x); }; }; +exports.notFunc = notFunc; -elisp.makePrimitiveBooleanFunc = function(fn) { - return function(x){ return fn(x) ? elisp.t : elisp.nil; }; +var makePrimitiveBooleanFunc = function(fn) { + return function(x){ return x[fn]() ? type.T : type.NIL; }; }; +exports.makePrimitiveBooleanFunc = makePrimitiveBooleanFunc; -elisp._definePrimitives = function() { -elisp.definePrimitive('consp', ['symbol'], elisp.makePrimitiveBooleanFunc(elisp.isCons), - "Return T if symbol is a cons, nil otherwise."); +init.hook('Define Primitive Variables and Functions', function() { + var type = require('elisp/types'); + definePrimitive('consp', ['symbol'], makePrimitiveBooleanFunc('isCons'), + "Return T if symbol is a cons, nil otherwise."); + + definePrimitive('atom', ['symbol'], makePrimitiveBooleanFunc('isAtom'), + "Return T if symbol is not a cons or is nil, nil otherwise."); + + definePrimitive('symbol-name', ['symbol'], + function(symbol) { return new type.LispString(symbol.symbolName()); }, + "Return a symbol's name, a string."); + + definePrimitive('string-match', ['regex', 'string', '&optional', 'start'], + function(regex, string, start) { + var index = start ? start.value() : 0, + s = string.value().substring(index), + match = s.match(new RegExp(regex.value())), + found = match ? new type.LispNumber(s.indexOf(match[0])) : type.NIL; + return found;}, + "Return the index of the char matching regex in string, beginning from start if available."); + + // Right now a single string in the arg list will cause all the arguments + // to be converted to strings similar to JavaScript. These + // semantics suck and should change, not only for real emacs lisp compatibility. + // ... for now it's the only way to catenate strings. + definePrimitive('+', [/*...*/], + function() { + var args = utils.shallowCopy(arguments), + initial = type.inferType(args), + result = utils.reduce(function(sum, n) { + return sum + n.value(); + }, initial.value(), args); + return type.construct(initial.tag(), result); + }, "add two numbers"); + + definePrimitive('-', [/*...*/], + function() { + if (arguments.length == 1) { + return new type.LispNumber(0 - arguments[0].value()); + } + var initial = arguments.length > 1 ? arguments[0].value() : 0, + args = utils.shallowCopy(arguments).slice(1), + result = utils.reduce(function(diff, n) { + return diff - n.value(); + }, initial, args); + return new type.LispNumber(result); + }, "negate a number, or subtract two or more numbers"); + + definePrimitive('*', [/*...*/], + function() { + var initial = arguments.length >= 1 ? arguments[0].value() : 1, + args = utils.shallowCopy(arguments).slice(1), + result = utils.reduce(function(prod, n){ + return prod * n.value(); + }, initial, args); + return new type.LispNumber(result); + }, "multiply one or more numbers"); + + definePrimitive('/', [/*...*/], + function() { + // TODO signal a real error for < 2 arguments + if (arguments.length < 2) { + print("[error] invalid division, need 2 or more params"); + return type.NIL; + } + var initial = arguments[0].value(), + args = utils.shallowCopy(arguments).slice(1), + result = utils.foldr(function(quot, n) { + return quot / n.value(); + }, initial, args); + return new type.LispNumber(result); + }, "divide two or more numbers"); + + definePrimitive('print', ['x'], utils.pp, "print an expression"); -elisp.definePrimitive('atom', ['symbol'], elisp.makePrimitiveBooleanFunc(elisp.isAtom), - "Return T if symbol is not a cons or is nil, nil otherwise."); - -elisp.definePrimitive('symbol-name', ['symbol'], - function(symbol) { return elisp.string(elisp.val(symbol)); }, - "Return a symbol's name, a string."); - -elisp.definePrimitive('string-match', ['regex', 'string', '&optional', 'start'], - function(regex, string, start) { - var index = start ? elisp.val(start) : 0, - s = elisp.val(string).substring(index), - match = s.match(new RegExp(elisp.val(regex))), - found = match ? elisp.number(s.indexOf(match[0])) : elisp.nil; - return found;}, - "Return the index of the char matching regex in string, beginning from start if available."); - -// Right now a single string in the arg list will cause all the arguments -// to be converted to strings similar to JavaScript. These -// semantics suck and should change, not only for real emacs lisp compatibility. -elisp.definePrimitive('+', [/*...*/], - function() { - var args = elisp.Util.shallowCopy(arguments), - initial = elisp.inferType(args), - type = initial[0]; - return elisp.Util.reduce(function(sum, n) { - return [type, elisp.val(sum) + elisp.val(n)]; - }, initial, args);}, - "add two numbers"); - -elisp.definePrimitive('-', [/*...*/], - function() { - return elisp.Util.foldr(function(diff, n) { - return elisp.number(elisp.val(diff) - elisp.val(n)); - }, elisp.number(0), elisp.Util.shallowCopy(arguments));}, - "subtract two numbers"); - -elisp.definePrimitive('*', [/*...*/], - function() { - return elisp.Util.reduce(function(prod, n) { - return elisp.number(elisp.val(prod) * elisp.val(n)); - }, elisp.number(1), elisp.Util.shallowCopy(arguments));}, - "multiply two numbers"); - -elisp.definePrimitive('/', [/*...*/], - function() { - return elisp.Util.foldr(function(quot, n) { - return elisp.number(elisp.val(quot) / elisp.val(n)); - }, elisp.number(1), elisp.Util.shallowCopy(arguments)); - }, - "divide two numbers"); - -elisp.definePrimitive('print', ['x'], - function(x, tostring) { - var buffer = "", - tag = elisp.tag(x); - function p(s) { - if (tostring) buffer += s; - else print(s); - } - if (tag == 'number' || tag == 'symbol' || tag == 'string') { - p(elisp.val(x)); - } - else if (tag == 'lambda') { - var fn = elisp.val(x); - p('(lambda ' + fn.name + ' (' + fn.params + ')\n'); - p(fn.body); // TODO lisp pretty print - p(')'); - } - else if (tag == 'list') { - var recurse = arguments.callee; // far easier to remember than Y - print('(', El.val(x).map(function(e){return (recurse(e, true) + ' ');}), ")"); - } - else { - print('unknown type: ' + x); - } - return elisp.nil; - }, - "print an expression"); - -elisp.definePrimitive('hide-prompt', ['yes-or-no'], - function(bool){ elisp.hidePrompt = !elisp.isNil(bool); }, - "Call with T to hide the prompt or nil to show it."); -}; -elisp.initHook(elisp._definePrimitives); + var settings = require('elisp/settings'); + definePrimitive('hide-prompt', ['yes-or-no'], + function(bool){ settings.hidePrompt = !bool.isNil(); }, + "Call with T to hide the prompt or nil to show it."); +}); diff --git a/elisp/repl.js b/elisp/repl.js index b04b8da..5c4e39a 100644 --- a/elisp/repl.js +++ b/elisp/repl.js @@ -6,48 +6,65 @@ // Released under the terms of the MIT license. See the included file // LICENSE. -elisp.eval = function(exprs) { - var e = new elisp.Evaluator(); - return e.evalExpressions(exprs); -}; +var parser = require('elisp/parser'), + evaluator = require('elisp/evaluator'), + utils = require('elisp/utils'), + Parser = parser.Parser, + Evaluator = evaluator.Evaluator; -elisp.parse = function(string) { - var p = new elisp.Parser(); +// this should probably be renamed to avoid confusion +var eval = function(string) { + var p = new Parser(), + e = new Evaluator(); + return e.evalExpressions(p.parse(string)); +}; +exports.eval = eval; + +var parse = function(string) { + var p = new Parser(); return p.parse(string); }; +exports.parse = parse; -elisp.parseOne = function(string) { - return elisp.parse(string)[0]; +var parseOne = function(string) { + return parse(string).car(); }; +exports.parseOne = parseOne; +exports.read = parseOne; -elisp.read = elisp.parseOne; -elisp.print = elisp.Util.pp; +exports.pp = utils.pp; -elisp.rep = function(string) { - elisp.print(elisp.eval(elisp.parse(string))); +var rep = function(string) { + var p = new Parser(), + e = new Evaluator(); + utils.pp(e.eval(p.read(string))); }; +exports.rep = rep; -elisp.repl = function() { - var p = new elisp.Parser(), - e = new elisp.Evaluator(); +var repl = function() { + var p = new Parser(), + e = new Evaluator(), + sys = require('system'), + settings = require('elisp/settings'); while (true) { - if (!elisp.hidePrompt) { - print("elisp> "); // i don't want a newline, grrrr + if (!settings.hidePrompt) { + sys.stdout.print("elisp> "); // i don't want a newline, grrrr } try { - var line = readline(); + var line = sys.stdin.readLine(); while (!line) { - line = readline(); + line = sys.stdin.readLine(); } if (line.substring(0,1).toLowerCase() == 'q') return; - elisp.print(e.eval(p.parseOne(line))); + utils.pp(e.eval(p.parseOne(line))); } catch (x) { if (x.evalError) { print('[error] ' + x.message + ': ' + x.expression); - elisp.print(x); + utils.pp(x); } else throw(x); } } }; +exports.repl = repl; diff --git a/elisp/settings.js b/elisp/settings.js new file mode 100644 index 0000000..4e6dd60 --- /dev/null +++ b/elisp/settings.js @@ -0,0 +1,10 @@ +//// +// 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. + +var settings = {}; +exports.settings = settings; \ No newline at end of file diff --git a/elisp/symtab.js b/elisp/symtab.js index 6b2b493..a7d0973 100644 --- a/elisp/symtab.js +++ b/elisp/symtab.js @@ -6,17 +6,16 @@ // Released under the terms of the MIT license. See the included file // LICENSE. -/**************************************************************** - **** Symbol Table ********************************************** - ****************************************************************/ +var utils = require('elisp/utils'), + type = require('elisp/types'); -elisp.SymbolTable = function(bindings) { +var SymbolTable = function(bindings) { this.symbols = [[]]; this.level = 0; if (bindings) this.define(bindings); }; -elisp.SymbolTable.prototype.lookupWithScope = function(name) { +SymbolTable.prototype.lookupWithScope = function(name) { var i = this.level, symbol; while (i >= 0) { @@ -29,14 +28,18 @@ elisp.SymbolTable.prototype.lookupWithScope = function(name) { return null; }; -elisp.SymbolTable.prototype.lookup = function(name) { +SymbolTable.prototype.lookup = function(name) { +// print('name: ' + name); var pair = this.lookupWithScope(name); +// print('pair: ' + pair); +// print('------'); return pair && pair[1]; }; // store the given symbol/value pair in the symbol table at the current level. -elisp.SymbolTable.prototype.define = function(name, value) { - if (value === undefined && elisp.typeOf(name) == 'array') { +SymbolTable.prototype.define = function(name, value) { +// print('###### REAL DEFINE: ' + name + ' = ' + value); + if (value === undefined && utils.typeOf(name) == 'array') { var bindings = name, i = 0, n = bindings.length, @@ -55,7 +58,7 @@ elisp.SymbolTable.prototype.define = function(name, value) { } }; -elisp.SymbolTable.prototype.pushScope = function(bindings) { +SymbolTable.prototype.pushScope = function(bindings) { // print('>>> pushing scope <<<'); // print('>>> level going from ' + this.level + ' to ' + (1+this.level)); // print(bindings); @@ -63,16 +66,15 @@ elisp.SymbolTable.prototype.pushScope = function(bindings) { if (bindings) this.define(bindings); }; -elisp.SymbolTable.prototype.popScope = function() { +SymbolTable.prototype.popScope = function() { --this.level; }; -elisp.SymbolTable.prototype.set = function(name, value) { +SymbolTable.prototype.set = function(name, value) { var pair = this.lookupWithScope(name), level = pair[0]; this.symbols[level][name] = value; }; -/**************************************************************** - ****************************************************************/ +exports.SymbolTable = SymbolTable; diff --git a/elisp/trace.js b/elisp/trace.js new file mode 100644 index 0000000..7ceab96 --- /dev/null +++ b/elisp/trace.js @@ -0,0 +1,46 @@ +exports.printStackTrace = function() { + var callstack = []; + var isCallstackPopulated = false; + try { + i.dont.exist+=0; //doesn't exist- that's the point + } catch(e) { + if (e.stack) { //Firefox + var lines = e.stack.split("\n"); + for (var i=0, len=lines.length; i ['symbol', 'foo'] - elisp.tags.each(function(tag) { - // don't clobber custom constructors - if (elisp[tag] === undefined) { - elisp[tag] = function(value) { - return [tag, value]; - }; - } - // tag type tests - var isTag = function(expr) { - return (elisp.tag(expr) == tag); - }; - elisp['is' + tag.camelize()] = isTag; - }); -}; -elisp.initHook(elisp._defineTags); - -// shorthands to save my fingers -elisp._defineConstants = function() { - elisp.nil = elisp.symbol('nil'); - elisp.t = elisp.symbol('t'); -}; -elisp.initHook(elisp._defineConstants); +var tags = ['symbol', 'string', 'number', 'cons', 'lambda']; +exports.tags = tags; -// retrieve the tag from a value -elisp.tag = function(expr) { - elisp.assert(function() { var f='tag'; return elisp.typeOf(expr) == 'array'; }, expr); - return expr[0]; +// All types descend from this object +var LispObject = function(value) { + this._tag = 'object'; + this._value = value; }; -// unbox a value -elisp.val = function(expr) { - elisp.assert(function() { var f='val'; return elisp.typeOf(expr) == 'array'; }, expr); - return expr[1]; +LispObject.prototype.tag = function() { + return this._tag; }; -elisp.symbolName = function(symbol) { -// elisp.Util.pp(symbol); - elisp.assert(function(){ var f='symbolName'; return elisp.isSymbol(symbol); }, symbol); - return elisp.val(symbol); +LispObject.prototype.value = function() { + return this._value; }; -elisp.isNilSymbol = function(expr) { - return (elisp.isSymbol(expr) && elisp.symbolName(expr) == 'nil'); +LispObject.prototype.repr = function() { + return '#'; }; -elisp.isNil = function(expr) { - return elisp.isNilSymbol(expr); +LispObject.prototype.eq = function(other) { + return other && this._tag == other._tag && this._value == other._value; }; -elisp.list = function(exprs) { - var list = elisp.nil, +LispObject.prototype.isA = function(what) { + return this._tag == what; +}; + +LispObject.prototype.isSymbol = function() { + return this.isA('symbol'); +}; + +LispObject.prototype.isString = function() { + return this.isA('string'); +}; + +LispObject.prototype.isNumber = function() { + return this.isA('number'); +}; + +LispObject.prototype.isCons = function() { + return this.isA('cons'); +}; + +LispObject.prototype.isLambda = function() { + return this.isA('lambda'); +}; + +LispObject.prototype.isNilSymbol = function() { + return (this.isSymbol() && this.symbolName() == 'nil'); +}; + +LispObject.prototype.isNilList = function() { + return (this.isCons() && this.car().isNil() && this.cdr().isNil()); +}; + +LispObject.prototype.isNil = function() { + return this.isNilSymbol() || this.isEmptyList; +}; + +LispObject.prototype.isList = function() { + return (this.isNil() || this.isCons()); +}; + +LispObject.prototype.isAtom = function() { + return !this.isCons(); +}; + +exports.LispObject = LispObject; + + +// Symbols + +var LispSymbol = function(value) { + this.prototype = new LispObject().prototype; + //this.prototype.constructor = this; + this._tag = 'symbol'; + this._value = value; +}; +LispSymbol.extend(LispObject); + +LispSymbol.prototype.symbolName = function() { + return this._value; +}; + +LispSymbol.prototype.repr = function() { + // TODO escape symbol name for real reading + return "'" + this._value; +}; + +exports.LispSymbol = LispSymbol; + + +// Strings + +var LispString = function(value) { + this._tag = 'string'; + this._value = value; +}; +LispString.extend(LispObject); + +LispString.prototype.repr = function() { + // TODO escape string for real reading + return '"' + this._value + '"'; +}; + +exports.LispString = LispString; + + +// Numbers + +var LispNumber = function(value) { + this._tag = 'number'; + this._value = value; +}; +LispNumber.extend(LispObject); + +LispNumber.prototype.repr = function() { + return '' + this._value; +}; + +exports.LispNumber = LispNumber; + + +// Constants + +var T = new LispSymbol('t'), + NIL = new LispSymbol('nil'); +exports.T = T; +exports.NIL = NIL; + +T.repr = function(){return 't';}; +NIL.repr = function(){return 'nil';}; + + +// Conses + +var LispCons = function(car, cdr) { + this._tag = 'cons'; + this._car = car || NIL; + this._cdr = cdr || NIL; + this._length = 1 + (cdr._length || 0); +}; +LispCons.extend(LispObject); + +LispCons.prototype.repr = function() { + // TODO pretty print lists as a special case + // (only dotted pairs should look like this) + return '(' + this._car.repr() + ' . ' + this._cdr.repr() + ')'; +}; + +LispCons.prototype.eq = function(other) { + return other.isCons() && this.car().eq(other.car()) && + this.cdr().eq(other.cdr()); +}; + +exports.LispCons = LispCons; + + +// Lambdas + +var LispLambda = function(value) { + this._tag = 'lambda'; + this._value = value; +}; +LispLambda.extend(LispObject); + +LispLambda.prototype.repr = function() { + // TODO implement me + return '(lambda (...) [body:' + this._value + '] )'; +}; + +exports.LispLambda = LispLambda; + + + +// JavaScript Array -> linked list +var mkList = function(exprs) { + var list = NIL, i = exprs.length; while (--i >= 0) { - list = elisp.cons(exprs[i], list); + list = new LispCons(exprs[i], list); } return list; }; +exports.mkList = mkList; -elisp.isList = function(expr) { - return (elisp.isNil(expr) || elisp.isCons(expr)); + + +var tagToTypeMap = { + symbol: LispSymbol, + string: LispString, + number: LispNumber, + cons: LispCons, + lambda: LispLambda }; -elisp.isAtom = function(expr) { - return !elisp.isCons(expr); +var construct = function(typeName, value) { + baseObject = tagToTypeMap[typeName] || LispObject; + return new baseObject(value); }; +exports.construct = construct; -elisp.inferType = function(exprs) { - var type = 'number', + + +// should probably be called guessType or LCDType +var inferType = function(exprs) { + var type_name = 'number', initial = 0, i = exprs.length-1; while(i >= 0) { - if (elisp.isString(exprs[i--])) { - type = 'string'; + if (!exprs[i--].isNumber()) { + type_name = 'string'; initial = ''; break; } } - return elisp[type](initial); + return construct(type_name, initial); }; +exports.inferType = inferType; // special forms -elisp.isSpecialForm = function(name, expr) { - var tag = elisp.tag(expr), - car = elisp.typeOf(expr) == 'array' && elisp.val(expr)[0], - thisName = car && elisp.symbolName(car); - return (tag == 'cons' && thisName == name); +LispObject.prototype._isSpecialForm = function(name) { + return (this.isCons() && this.car().isSymbol() && this.car().symbolName() == name); }; -elisp.isQuote = function(expr){return elisp.isSpecialForm('quote', expr);}; -elisp.isDefVar = function(expr){return elisp.isSpecialForm('defvar', expr);}; -elisp.isDefFunc = function(expr){return elisp.isSpecialForm('defun', expr);}; -elisp.isSet = function(expr){return elisp.isSpecialForm('set', expr);}; -elisp.isSetq = function(expr){return elisp.isSpecialForm('setq', expr);}; -elisp.isCond = function(expr){return elisp.isSpecialForm('cond', expr);}; -elisp.isIf = function(expr){return elisp.isSpecialForm('if', expr);}; +LispObject.prototype.isQuote = function(){return this._isSpecialForm('quote');}; +LispObject.prototype.isDefvar = function(){return this._isSpecialForm('defvar');}; +LispObject.prototype.isDefun = function(){return this._isSpecialForm('defun');}; +LispObject.prototype.isSet = function(){return this._isSpecialForm('set');}; +LispObject.prototype.isSetq = function(){return this._isSpecialForm('setq');}; +LispObject.prototype.isCond = function(){return this._isSpecialForm('cond');}; +LispObject.prototype.isIf = function(){return this._isSpecialForm('if');}; diff --git a/elisp/util.js b/elisp/util.js deleted file mode 100644 index 23d2dd0..0000000 --- a/elisp/util.js +++ /dev/null @@ -1,143 +0,0 @@ -//// -// 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. - -/**************************************************************** - **** Utilities ************************************************* - ****************************************************************/ - -elisp.Util = function(){}; - -// aka foldl -elisp.Util.reduce = function(fn, accum, list) { - var i = 0, - n = list.length; - while (i < n) { - accum = fn(accum, list[i++]); - } - return accum; -}; - -elisp.Util.foldr = function(fn, end, list) { - var i = list.length-1; - while (i >= 0) { - end = fn(list[i--], end); - } - return end; -}; - -elisp.Util.shallowCopy = function(list) { - var i = 0, - n = list.length, - result = []; - while (i < n) { - result.push(list[i++]); - } - return result; -}; - - -// i'm sorry -elisp.Util.pp = function(x, indent, key, noprint) { - if (indent === undefined) { - indent = 0; - } - - /* string to print at the end, the only way to print 2 strings in - * succession without a newline is to buffer them - */ - var buffer = ""; - var printB = function(s, newline) { - buffer += s; - if (newline) buffer += "\n"; - }; - var dumpBuffer = function(b) { - if (b === undefined) b = buffer; - if (buffer.length <= 72) { - print(buffer); - } - else { - var split = 72; - var c; - while ((c = b[split]) && c.match(/[a-zA-Z0-9"'\[\]_-]/)) --split; - print(b.substring(0, split)); - var next_chunk = b.substring(split); - if (next_chunk) dumpBuffer(next_chunk); - } - buffer = ""; - }; - - var space = ""; -// for (var j = 0; j < indent; j++) { -// space += " "; -// } - - switch (elisp.typeOf(x)) { - case 'object': - if (key) { - printB(space + key + ': {'); - } - else { - printB(space + ' {'); - } - for (var a in x) { - printB(elisp.Util.pp(x[a], 1+indent, a, true)); - printB(', '); - } - printB(space + "} "); - break; - - case 'string': - if (key) { - printB(space + key + ': "' + x + '"'); - } - else { - printB(space + '"' + x + '"'); - } - return buffer; - - case 'array': - // nil special case - if (x.length == 2 && elisp.tag(x) == 'symbol' && elisp.val(x) == 'nil') { - return elisp.Util.pp(null, indent, key); - } - - if (key) { - printB(space + key + ': ['); - } - else { - printB(space + '['); - } - var n = x.length, i = 0; - while (i < n) { - if (i > 0) printB(', '); - printB(elisp.Util.pp(x[i++], 1+indent, undefined, true)); - } - printB(space + ']'); - break; - - case 'null': - if (key) { - printB(space + key + ': (null)'); - } - else { - printB(space + '(null)'); - } - return buffer; - - default: - if (key) { - printB(space + key + ": " + x); - } - else { - printB(space + x); - } - return buffer; - } - if (noprint) return buffer; - else dumpBuffer(); -}; diff --git a/elisp/utils.js b/elisp/utils.js new file mode 100644 index 0000000..348616c --- /dev/null +++ b/elisp/utils.js @@ -0,0 +1,101 @@ +//// +// 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. + +// A typeOf function that distinguishes between objects, arrays, +// and null. +var 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; +}; +exports.typeOf = typeOf; + +// TODO throw something more informative +exports.assert = function(condition, message) { + if (!condition()) { + throw("assertion failed: " + condition + " (" + message + ")"); + } +}; + +// aka foldl +exports.reduce = function(fn, accum, list) { + var i = 0, + n = list.length; + while (i < n) { + accum = fn(accum, list[i++]); + } + return accum; +}; + +exports.foldr = function(fn, end, list) { + var i = list.length-1; + while (i >= 0) { + end = fn(end, list[i--]); + } + return end; +}; + +exports.shallowCopy = function(list) { + var i = 0, + n = list.length, + result = []; + while (i < n) { + result.push(list[i++]); + } + return result; +}; + +var pp = function(x, toString) { + var s, recurse = arguments.callee; + +// print('pp, x = ' + x); + + if (x === null || x === undefined) { + s = ''; + } + else if (!x.repr) { + s = '[UNKNOWN VALUE: ' + x + ']'; // what are you?! + } + else { + s = x.repr(); + } + + if (toString) { + return(s); + } + else if (s.length > 0) { + print(s); + } +}; +exports.pp = pp; + +var pp_v = function(x) { + print('-------- START --------'); + print('tag: ' + x.tag()); + + if (x.isCons()) { + print('car: ' + x.car()); + print('cdr: ' + x.cdr()); + } + else { + print('value: ' + x.value()); + } + + print('-------- END --------'); +}; +exports.pp_v = pp_v; diff --git a/repl.js b/repl.js new file mode 100644 index 0000000..33944fe --- /dev/null +++ b/repl.js @@ -0,0 +1,3 @@ +require.paths.unshift('.'); +var elisp = require('elisp'); +elisp.repl.repl();