diff --git a/README b/README index 6ef58d9..152746a 100644 --- a/README +++ b/README @@ -14,10 +14,9 @@ Latest version available on github: (or "You must be kidding") ========================== -I'm not 100% sure why I think this might be useful to somebody. The -idea of editing code directly on github or bitbucket in a web browser -is pretty cool though, and if you're going to do such a thing why not -use the best tools available. IMO those tools are written in Emacs +The idea of editing code directly on github or bitbucket in a web +browser is pretty cool, and if you're going to do such a thing why not +use the best tools available? IMO those tools are written in Emacs Lisp so I would like to use them in the browser. Maybe with some HTML5 offline goodness thrown in. @@ -25,84 +24,77 @@ Seeing Ymacs[1] in action[2] was also an inspiration to start this project as I've had it on my TODO list for several months now. Emacs in the browser could be a reality; Ymacs is proof. +With Palm's release of "Project Ares"[3] the need for such tools is +beginning to be a reality. I'm no longer that uncertain why such a +beast would be useful. The question now is whether using something +like Google's Native Client and original C version of Emacs is the way +to go, or if a slower implementation in JavaScript is feasible for +real use. + [1] http://www.ymacs.org/ [2] http://www.ymacs.org/demo/ +[3] http://pdnblog.palm.com/2009/12/project-ares-open-beta/ +[4] http://code.google.com/p/nativeclient/ Getting Started =============== -Command line ------------- +I'm currently using CommonJS via Narwhal[1] with the JavaScriptCore +engine. I've run the tests under v8 as well and they all pass, but +not everything is tested by any means. -If you have rlwrap and js then just run el.sh. If not install a -JavaScript shell of your choice, and I recommend you install rlwrap -(readline wrap). Edit el.sh to reflect whether or not you use rlwrap -and your shell's name or path. Then run el.sh. I've only run this -under SpiderMonkey so I have no idea if it works under any other -implementations. Glad to accept info and/or patches. +[1] http://narwhaljs.org/ + +If you have rlwrap and narwhal then just run elisp.sh. If not install +a JavaScript shell of your choice, and I recommend you install rlwrap +(readline wrap). Edit elisp.sh to reflect whether or not you use +rlwrap and your shell's name or path, and then run elisp.sh. Type a 'Q' or 'q' to quit the repl. Or anything that starts with Q, like 'quickly quit lisp!'. (If lisp freaks out because you entered ^D or something ^C or ^Z should still get you out.) - -Emacs ------ - -I use Emacs and js-comint.el[3] with inferior-js set to -/opt/local/bin/js, which is SpiderMonkey[4] from MacPorts[5] on Snow -Leopard. - -[3] http://js-comint-el.sourceforge.net/ -[4] http://www.mozilla.org/js/spidermonkey/ -[5] http://www.macports.org/ - -For loading code C-c b sends the buffer to the JS repl. I also use -the EL.rep (read-eval-print), EL.parse, EL.eval, and EL.print -functions defined in el/repl.js - Here's an example: % cd Projects/elisp.js -% rlwrap js -js> load('el.js') -elisp> +% ./elisp.sh +elisp> (defvar *directory* "/Users/sjs" "My home directory") +nil elisp> *directory* -["string", "/Users/sjs"] +"/Users/sjs" elisp> (setq foo 1 bar 2 baz 3) -["number", 3] +3 elisp> (/ (+ foo bar baz) 3) -["number", 2] +2 elisp> (string-match "[a-z]lurgh" (symbol-name 'foo-blurgh)) -["number", 4] +4 elisp> q -js> +% -(If you know how to print without a trailing newline in SpiderMonkey +(If you know how to print without a trailing newline in JavaScript please let me know.) There are other interfaces into the parser and evaluator besides the -shortcuts. new EL.Parser([input]) returns a parser object and likewise -new EL.Evaluator([exprs]) returns an evaluator object. +shortcuts. new elisp.parser.Parser([input]) returns a parser object +and likewise new elisp.evaluator.Evaluator([exprs]) returns an +evaluator object. Mainly toy functions that do extremely simple operations on numbers and strings can be implemented right now. Stay tuned, or better yet -hack away and submit a pull request on github[6]. - -[6] http://github.com/samsonjs/elisp.js +hack away and submit a pull request on github. What's here? ============ -Not much compared to the real thing but it's a decent start for < 1000 +Not much compared to the real thing but it's a decent start for 1500 lines. * parser/reader. there's no lexing to tokens we go straight to tagged @@ -115,7 +107,7 @@ lines. * expression evaluator - * simple tagged primitive types + * primitive types (string, symbol, lambda, number, cons) * special forms for defvar, defun, set, setq, if, and quote @@ -124,6 +116,3 @@ lines. * a few primitive math ops (thanks to JS' overloading + works on strings too) - - * 2 horrible print functions - (JS "pretty" printer & a half-assed Lisp print) diff --git a/elisp/evaluator.js b/elisp/evaluator.js index 21563fb..d4f3a9e 100644 --- a/elisp/evaluator.js +++ b/elisp/evaluator.js @@ -58,7 +58,6 @@ Evaluator.prototype.evalExpressions = function(expressions) { } } } -// Utils.pp(result); return result; }; @@ -84,13 +83,10 @@ Evaluator.prototype.apply = function(func, args) { this.functions.pushScope(); 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 = func.body.map(function(e) {return this.eval(e); }).last(); + value = this.eval(args[i]); + return [name, {type:'variable', value:value}]; + }).unlist()); + result = this.evalExpressions(func.body); this.functions.popScope(); this.variables.popScope(); } @@ -102,7 +98,7 @@ Evaluator.prototype.eval = function(expr) { //utils.pp(expr); var result, x, tag = expr.tag(); - if (expr.isAtom()) { + if (expr.isString() || expr.isNumber()) { result = expr; } else if (expr.isSymbol()) { @@ -121,9 +117,9 @@ Evaluator.prototype.eval = function(expr) { result = expr.cdr(); } else if (expr.isDefvar()) { - var name = expr.cadr().symbolName(), // 2nd param - value = this.eval(expr.caddr()), // 3rd param - docstring = expr.cadddr(); // 4th param + var name = expr.nth(1).symbolName(), + value = this.eval(expr.nth(2)), + docstring = expr.nth(3); // TODO check for re-definitions this.defineVar(name, value, docstring); result = type.NIL; @@ -138,8 +134,8 @@ Evaluator.prototype.eval = function(expr) { result = type.NIL; } else if (expr.isSet()) { - var name = expr.car().symbolName(), - value = this.eval(expr.cdr()); + var name = expr.nth(1).symbolName(), + value = this.eval(expr.nth(2)); this.setVar(name, value); result = value; } @@ -188,10 +184,12 @@ Evaluator.prototype.eval = function(expr) { result = this.apply(func, args); } else { + result = type.NIL; this.error('undefined-func', name); } } else { + result = type.NIL; this.error('not-expr', expr); } // print('RESULT: ' + result); diff --git a/elisp/list.js b/elisp/list.js index f5f3d5e..42582f9 100644 --- a/elisp/list.js +++ b/elisp/list.js @@ -30,15 +30,15 @@ LispCons.prototype.cdr = function() { }; LispCons.prototype.cadr = function() { - return this.cdr().car(); + return this.nth(1); }; LispCons.prototype.caddr = function() { - return this.cdr().cdr().car(); + return this.nth(2); }; LispCons.prototype.cadddr = function() { - return this.cdr().cdr().cdr().car(); + return this.nth(3); }; LispCons.prototype.length = function() { @@ -60,7 +60,6 @@ LispCons.prototype.map = function(fn) { var list = [], 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(); @@ -79,7 +78,7 @@ LispCons.prototype.reduce = function(accum, fn) { }; LispCons.prototype.unlist = function() { - return this.reduce([], function(x){return x;}); + return this.reduce([], function(acc, x){acc.push(x); return acc;}); }; LispCons.prototype.nth = function(n) { @@ -92,7 +91,7 @@ LispCons.prototype.nth = function(n) { cons = cons.cdr(); ++i; } - return n > --i ? type.NIL : e; + return n > (i-1) ? type.NIL : e; }; LispCons.prototype.nthcdr = function(n) { diff --git a/elisp/repl.js b/elisp/repl.js index 5c4e39a..069ea0d 100644 --- a/elisp/repl.js +++ b/elisp/repl.js @@ -60,7 +60,6 @@ var repl = function() { } catch (x) { if (x.evalError) { print('[error] ' + x.message + ': ' + x.expression); - utils.pp(x); } else throw(x); } diff --git a/elisp/symtab.js b/elisp/symtab.js index a7d0973..a653860 100644 --- a/elisp/symtab.js +++ b/elisp/symtab.js @@ -39,6 +39,7 @@ SymbolTable.prototype.lookup = function(name) { // store the given symbol/value pair in the symbol table at the current level. SymbolTable.prototype.define = function(name, value) { // print('###### REAL DEFINE: ' + name + ' = ' + value); +// print(' (TYPES): ' + utils.typeOf(name) + ' = ' + utils.typeOf(value)); if (value === undefined && utils.typeOf(name) == 'array') { var bindings = name, i = 0, diff --git a/elisp/types.js b/elisp/types.js index d94c3c3..f46f98b 100644 --- a/elisp/types.js +++ b/elisp/types.js @@ -218,7 +218,7 @@ var inferType = function(exprs) { var type_name = 'number', initial = 0, i = exprs.length-1; - while(i >= 0) { + while (i >= 0) { if (!exprs[i--].isNumber()) { type_name = 'string'; initial = ''; diff --git a/elisp/utils.js b/elisp/utils.js index 348616c..2c5c57d 100644 --- a/elisp/utils.js +++ b/elisp/utils.js @@ -69,7 +69,11 @@ var pp = function(x, toString) { s = ''; } else if (!x.repr) { - s = '[UNKNOWN VALUE: ' + x + ']'; // what are you?! + s = '[UNKNOWN VALUE: ' + x + ' = {\n'; // what are you?! + for (var y in x) { + s += ' ' + y + ': ' + x[y] + "\n"; + } + s += '}'; } else { s = x.repr();