mirror of
https://github.com/samsonjs/elisp.js.git
synced 2026-04-27 15:07:47 +00:00
Migrated from plain Spidermonkey to CommonJS (narwhal), hence to real modules as well.
This commit is contained in:
parent
a50011ae04
commit
bfa09931e9
16 changed files with 796 additions and 615 deletions
51
elisp.js
51
elisp.js
|
|
@ -23,26 +23,47 @@
|
||||||
//
|
//
|
||||||
///
|
///
|
||||||
|
|
||||||
|
if (require.paths[0] != '.') require.paths.unshift('.');
|
||||||
|
//require.paths.unshift('elisp');
|
||||||
|
|
||||||
// our namespace
|
//print('[elisp starting]');
|
||||||
var elisp = function(){};
|
|
||||||
|
|
||||||
|
var init = require('elisp/init'); // simple init system
|
||||||
|
exports.init = init;
|
||||||
|
//print('* init');
|
||||||
|
|
||||||
load('elisp/init.js'); // simple init system
|
require('elisp/jsExt'); // extensions to native types
|
||||||
load('elisp/jsExt.js'); // a few extensions to native types
|
//print('* jsExt');
|
||||||
load('elisp/util.js'); // utilities
|
|
||||||
|
exports.utils = require('elisp/utils');
|
||||||
|
//print('* utils');
|
||||||
|
|
||||||
// main lisp system
|
// main lisp system
|
||||||
load('elisp/types.js');
|
exports.type = require('elisp/types');
|
||||||
load('elisp/list.js');
|
exports.T = exports.type.T;
|
||||||
load('elisp/symtab.js');
|
exports.NIL = exports.type.NIL;
|
||||||
load('elisp/parser.js');
|
//print('* type');
|
||||||
load('elisp/evaluator.js');
|
|
||||||
load('elisp/primitives.js');
|
exports.list = require('elisp/list');
|
||||||
load('elisp/repl.js');
|
//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
|
// everything is defined, initialize
|
||||||
elisp.init();
|
init.initialize();
|
||||||
|
|
||||||
// q to quit
|
//print('[elisp ready]');
|
||||||
elisp.repl();
|
|
||||||
|
|
|
||||||
2
elisp.sh
2
elisp.sh
|
|
@ -1,3 +1,3 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
rlwrap /opt/local/bin/js elisp.js
|
NARWHAL_ENGINE=jsc rlwrap js repl.js
|
||||||
|
|
|
||||||
|
|
@ -6,45 +6,51 @@
|
||||||
// Released under the terms of the MIT license. See the included file
|
// Released under the terms of the MIT license. See the included file
|
||||||
// LICENSE.
|
// 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.expressions = exprs;
|
||||||
this.variables = new elisp.SymbolTable(elisp.PrimitiveVariables);
|
this.variables = new symtab.SymbolTable(primitives.PrimitiveVariables);
|
||||||
this.functions = new elisp.SymbolTable(elisp.PrimitiveFunctions);
|
this.functions = new symtab.SymbolTable(primitives.PrimitiveFunctions);
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Evaluator.Error = function(name, message, expr) {
|
Evaluator.Error = function(name, message, expr) {
|
||||||
this.evalError = true;
|
this.evalError = true;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
this.expression = expr;
|
this.expression = utils.pp(expr, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Evaluator.Error.messages = {
|
Evaluator.Error.messages = {
|
||||||
'not-expr': "not an expression",
|
'not-expr': "not an expression",
|
||||||
'undefined-func': "undefined function",
|
'undefined-func': "undefined function",
|
||||||
'undefined-var': "variable not defined"
|
'undefined-var': "variable not defined"
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Evaluator.prototype.error = function(name, expr) {
|
Evaluator.prototype.error = function(name, expr) {
|
||||||
throw(new elisp.Evaluator.Error(name, elisp.Evaluator.Error.messages[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,
|
var exprs = expressions || this.expressions,
|
||||||
i = 0,
|
i = 0,
|
||||||
n = exprs.length,
|
n = exprs.length(),
|
||||||
result;
|
result, expr;
|
||||||
while (i < n) {
|
while (i < n) {
|
||||||
try {
|
try {
|
||||||
result = this.eval(exprs[i++]);
|
expr = exprs.nth(i++);
|
||||||
|
result = this.eval(expr);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.evalError) {
|
if (e.evalError) {
|
||||||
print('[error] ' + e.message);
|
print('[error] ' + e.message);
|
||||||
if (e.expression) {
|
if (e.expression) {
|
||||||
print("got: " + e.expression);
|
print("got: " + e.expression);
|
||||||
}
|
}
|
||||||
result = elisp.nil;
|
result = type.NIL;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -52,58 +58,57 @@ elisp.Evaluator.prototype.evalExpressions = function(expressions) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// elisp.Util.pp(result);
|
// Utils.pp(result);
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Evaluator.prototype.lookupVar = function(symbol) {
|
Evaluator.prototype.lookupVar = function(symbol) {
|
||||||
return this.variables.lookup(symbol);
|
return this.variables.lookup(symbol);
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Evaluator.prototype.lookupFunc = function(symbol) {
|
Evaluator.prototype.lookupFunc = function(symbol) {
|
||||||
return this.functions.lookup(symbol);
|
return this.functions.lookup(symbol);
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Evaluator.prototype.apply = function(func, args) {
|
Evaluator.prototype.apply = function(func, args) {
|
||||||
var result;
|
var result;
|
||||||
if (func.type === 'primitive') {
|
if (func.type === 'primitive') {
|
||||||
// print('APPLY: ');
|
// print('APPLY: ');
|
||||||
// elisp.print(func);
|
// print(func);
|
||||||
// print('WITH: ');
|
// print('WITH: ');
|
||||||
// elisp.print(args);
|
// print(args);
|
||||||
// print('------');
|
// print('------');
|
||||||
result = func.body.apply(this, args);
|
result = func.body.apply(this, args);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.functions.pushScope();
|
this.functions.pushScope();
|
||||||
this.variables.pushScope(elisp.listMap(func.params, function(e, i){
|
this.variables.pushScope(func.params.map(function(e, i){
|
||||||
var name = elisp.symbolName(e),
|
var name = e.symbolName(),
|
||||||
value = {
|
value = {
|
||||||
type: 'variable',
|
type: 'variable',
|
||||||
value: this.eval(args[i])
|
value: this.eval(args[i])
|
||||||
};
|
};
|
||||||
return [name, value];
|
return [name, value];
|
||||||
}));
|
}));
|
||||||
result = elisp.listLast(elisp.listMap(func.body,
|
result = func.body.map(function(e) {return this.eval(e); }).last();
|
||||||
function(e) {return this.eval(e); }));
|
|
||||||
this.functions.popScope();
|
this.functions.popScope();
|
||||||
this.variables.popScope();
|
this.variables.popScope();
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Evaluator.prototype.eval = function(expr) {
|
Evaluator.prototype.eval = function(expr) {
|
||||||
// print("EVAL: " + elisp.typeOf(expr));
|
// print('[Evaluator.eval]');
|
||||||
// elisp.print(expr);
|
//utils.pp(expr);
|
||||||
var result, x,
|
var result, x,
|
||||||
tag = elisp.tag(expr);
|
tag = expr.tag();
|
||||||
if (elisp.isAtom(expr)) {
|
if (expr.isAtom()) {
|
||||||
result = expr;
|
result = expr;
|
||||||
}
|
}
|
||||||
else if (elisp.isSymbol(expr)) {
|
else if (expr.isSymbol()) {
|
||||||
var name = elisp.val(expr);
|
var name = expr.symbolName();
|
||||||
x = this.lookupVar(name);
|
x = this.lookupVar(name);
|
||||||
if (x == null) this.error('undefined-var', name);
|
if (!x) this.error('undefined-var', name);
|
||||||
result = x.value;
|
result = x.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,75 +117,74 @@ elisp.Evaluator.prototype.eval = function(expr) {
|
||||||
///////////////////
|
///////////////////
|
||||||
// (many could be in lisp when there are macros) //
|
// (many could be in lisp when there are macros) //
|
||||||
|
|
||||||
else if (elisp.isQuote(expr)) {
|
else if (expr.isQuote()) {
|
||||||
result = elisp.cdr(expr);
|
result = expr.cdr();
|
||||||
}
|
}
|
||||||
else if (elisp.isDefVar(expr)) {
|
else if (expr.isDefvar()) {
|
||||||
var name = elisp.symbolName(elisp.cadr(expr)), // 2nd param
|
var name = expr.cadr().symbolName(), // 2nd param
|
||||||
value = this.eval(elisp.caddr(expr)), // 3rd param
|
value = this.eval(expr.caddr()), // 3rd param
|
||||||
docstring = elisp.cadddr(expr); // 4th param
|
docstring = expr.cadddr(); // 4th param
|
||||||
// TODO check for re-definitions
|
// TODO check for re-definitions
|
||||||
this.defineVar(name, value, docstring);
|
this.defineVar(name, value, docstring);
|
||||||
result = elisp.nil;
|
result = type.NIL;
|
||||||
}
|
}
|
||||||
else if (elisp.isDefFunc(expr)) {
|
else if (expr.isDefun()) {
|
||||||
var name = elisp.symbolName(elisp.nth(1, expr)),
|
var name = expr.nth(1).symbolName(),
|
||||||
params = elisp.nth(2, expr),
|
params = expr.nth(2),
|
||||||
d = elisp.nth(3, expr),
|
d = expr.nth(3),
|
||||||
docstring = elisp.isString(d) && d,
|
docstring = d.isString() && d,
|
||||||
body = elisp.nthcdr(docstring ? 3 : 2, expr);
|
body = expr.nthcdr(docstring ? 3 : 2);
|
||||||
this.defineFunc(name, params, body, docstring);
|
this.defineFunc(name, params, body, docstring);
|
||||||
result = elisp.nil;
|
result = type.NIL;
|
||||||
}
|
}
|
||||||
else if (elisp.isSet(expr)) {
|
else if (expr.isSet()) {
|
||||||
var val = elisp.val(expr),
|
var name = expr.car().symbolName(),
|
||||||
name = elisp.symbolName(val[1]),
|
value = this.eval(expr.cdr());
|
||||||
value = this.eval(val[2]);
|
|
||||||
this.setVar(name, value);
|
this.setVar(name, value);
|
||||||
result = value;
|
result = value;
|
||||||
}
|
}
|
||||||
else if (elisp.isSetq(expr)) {
|
else if (expr.isSetq()) {
|
||||||
var i = 1,
|
var i = 1,
|
||||||
n = elisp.listLength(expr),
|
n = expr.length(),
|
||||||
e;
|
e;
|
||||||
while (i < n && elisp.isSymbol((e=elisp.nth(i,expr)))) {
|
while (i < n && (e=expr.nth(i)).isSymbol()) {
|
||||||
var name = elisp.symbolName(elisp.nth(i, expr)),
|
var name = e.symbolName(),
|
||||||
value = this.eval(elisp.nth(i+1, expr));
|
value = this.eval(expr.nth(i+1));
|
||||||
this.setVar(name, value, true);
|
this.setVar(name, value, true);
|
||||||
result = value;
|
result = value;
|
||||||
i += 2;
|
i += 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (elisp.isIf(expr)) {
|
else if (expr.isIf()) {
|
||||||
var val = elisp.val(expr),
|
var condition = this.eval(expr.nth(1)),
|
||||||
condition = this.eval(val[1]),
|
trueBlock = expr.nth(2),
|
||||||
trueBlock = val[2],
|
nilBlock = expr.nth(3);
|
||||||
nilBlock = val[3];
|
|
||||||
result = this.doIf(condition, trueBlock, nilBlock);
|
result = this.doIf(condition, trueBlock, nilBlock);
|
||||||
}
|
}
|
||||||
else if (elisp.isCond(expr)) {
|
else if (expr.isCond()) {
|
||||||
var val = elisp.val(expr),
|
// TODO implement me
|
||||||
list = val[1],
|
result = type.NIL;
|
||||||
condition = elisp.car(list),
|
// var list = expr.nth(1),
|
||||||
body = elisp.cdr(list),
|
// condition = list.car(),
|
||||||
rest = val.slice(2);
|
// body = list.cdr(),
|
||||||
result = this.doCond(exprs);
|
// rest = val.slice(2);
|
||||||
|
// result = this.doCond(exprs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// function application
|
// function application
|
||||||
else if (elisp.isCons(expr)) {
|
else if (expr.isCons()) {
|
||||||
var name = elisp.car(expr),
|
var name = expr.car(),
|
||||||
rest = elisp.cdr(expr),
|
rest = expr.cdr(),
|
||||||
func, args;
|
func, args;
|
||||||
while (!elisp.isSymbol(name)) {
|
while (!name.isSymbol()) {
|
||||||
name = this.eval(name);
|
name = this.eval(name);
|
||||||
}
|
}
|
||||||
if ((func = this.lookupFunc(elisp.symbolName(name)))) {
|
if ((func = this.lookupFunc(name.symbolName()))) {
|
||||||
var self = this;
|
var self = this;
|
||||||
args = elisp.listReduce(function(a,e){
|
args = rest.reduce([], function(a,e){
|
||||||
a.push(self.eval(e));
|
a.push(self.eval(e));
|
||||||
return a;
|
return a;
|
||||||
}, [], rest);
|
});
|
||||||
result = this.apply(func, args);
|
result = this.apply(func, args);
|
||||||
}
|
}
|
||||||
else {
|
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, {
|
this.variables.define(symbol, {
|
||||||
type: 'variable',
|
type: 'variable',
|
||||||
value: value,
|
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);
|
var valueObject = this.lookupVar(symbol);
|
||||||
if (!valueObject) {
|
if (!valueObject) {
|
||||||
if (create) {
|
if (create) {
|
||||||
this.defineVar(symbol, elisp.nil);
|
this.defineVar(symbol, type.NIL);
|
||||||
valueObject = this.lookupVar(symbol);
|
valueObject = this.lookupVar(symbol);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -218,7 +222,7 @@ elisp.Evaluator.prototype.setVar = function(symbol, value, create) {
|
||||||
this.variables.set(symbol, valueObject);
|
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, {
|
this.functions.define(symbol, {
|
||||||
type: 'lambda',
|
type: 'lambda',
|
||||||
name: symbol,
|
name: symbol,
|
||||||
|
|
@ -228,14 +232,14 @@ elisp.Evaluator.prototype.defineFunc = function(symbol, params, body, docstring)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Evaluator.prototype.doIf = function(condition, trueBlock, nilBlock) {
|
Evaluator.prototype.doIf = function(condition, trueBlock, nilBlock) {
|
||||||
return elisp.isNil(condition) ? this.eval(nilBlock) : this.eval(trueBlock);
|
return condition.isNil() ? this.eval(nilBlock) : this.eval(trueBlock);
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Evaluator.prototype.doCond = function(exprs) {
|
Evaluator.prototype.doCond = function(exprs) {
|
||||||
print('----- COND (doCond) -----');
|
print('----- COND (doCond) -----');
|
||||||
elisp.print(exprs);
|
utils.pp(exprs);
|
||||||
return elisp.nil;
|
return type.NIL;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.Evaluator = Evaluator;
|
||||||
|
|
@ -10,14 +10,18 @@
|
||||||
// of the file we call init when everything is defined, regardless of
|
// 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
|
// the order it appears in the file. The order of the hooks still
|
||||||
// matters though, it's not fool-proof.
|
// matters though, it's not fool-proof.
|
||||||
elisp._initHooks = [];
|
|
||||||
elisp.initHook = function(hook) {
|
var hooks = [];
|
||||||
elisp._initHooks.push(hook);
|
|
||||||
|
exports.hook = function(name, hook) {
|
||||||
|
hooks.push({hook: hook, name: name});
|
||||||
};
|
};
|
||||||
elisp.init = function() {
|
|
||||||
|
exports.initialize = function() {
|
||||||
var i = 0,
|
var i = 0,
|
||||||
n = elisp._initHooks.length;
|
n = hooks.length;
|
||||||
while (i < n) {
|
while (i < n) {
|
||||||
elisp._initHooks[i++].call();
|
// print('**** INIT HOOK: ' + hooks[i].name + ' *****');
|
||||||
|
hooks[i++].hook.call();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,55 +6,8 @@
|
||||||
// Released under the terms of the MIT license. See the included file
|
// Released under the terms of the MIT license. See the included file
|
||||||
// LICENSE.
|
// LICENSE.
|
||||||
|
|
||||||
// Just a little sugar
|
// Simple inheritance. e.g. ChildObject.extend(ParentObject)
|
||||||
elisp.initHook(function() {
|
Function.prototype.extend = function(superclass) {
|
||||||
Array.prototype.each = function(fn) {
|
this.prototype = new superclass;
|
||||||
var i = 0,
|
this.prototype.constructor = this;
|
||||||
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 + ")");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
|
||||||
135
elisp/list.js
135
elisp/list.js
|
|
@ -6,98 +6,121 @@
|
||||||
// Released under the terms of the MIT license. See the included file
|
// Released under the terms of the MIT license. See the included file
|
||||||
// LICENSE.
|
// LICENSE.
|
||||||
|
|
||||||
elisp.consPair = function(pair) {
|
var type = require('elisp/types'),
|
||||||
var cons = ['cons', pair];
|
utils = require('elisp/utils'),
|
||||||
cons.isList = true;
|
LispCons = type.LispCons,
|
||||||
return cons;
|
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) {
|
LispCons.prototype.cdr = function() {
|
||||||
return elisp.consPair([car, cdr]);
|
// print('[LispCons.cdr] calling this.isNil - this: ' + this + ' - _cdr: ' + this._cdr);
|
||||||
|
return this.isNil() ? this : this._cdr;
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.car = function(cons) {
|
LispCons.prototype.cadr = function() {
|
||||||
return elisp.isNil(cons) ? cons : elisp.val(cons)[0];
|
return this.cdr().car();
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.cdr = function(cons) {
|
LispCons.prototype.caddr = function() {
|
||||||
return elisp.isNil(cons) ? cons : elisp.val(cons)[1];
|
return this.cdr().cdr().car();
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.cadr = function(cons) {
|
LispCons.prototype.cadddr = function() {
|
||||||
return elisp.car(elisp.cdr(cons));
|
return this.cdr().cdr().cdr().car();
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.caddr = function(cons) {
|
LispCons.prototype.length = function() {
|
||||||
return elisp.car(elisp.cdr(elisp.cdr(cons)));
|
return this._length;
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.cadddr = function(cons) {
|
LispCons.prototype.last = function() {
|
||||||
return elisp.car(elisp.cdr(elisp.cdr(elisp.cdr(cons))));
|
var last,
|
||||||
};
|
cons = this;
|
||||||
|
// print('[LispCons.last] calling cons.isNil - cons: ' + cons + ' - _cdr: ' + cons._cdr);
|
||||||
elisp.listLength = function(cons) {
|
while (!cons.isNil()) {
|
||||||
var n = 0;
|
|
||||||
while (!elisp.isNil(cons)) {
|
|
||||||
cons = elisp.cdr(cons);
|
|
||||||
++n;
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
};
|
|
||||||
|
|
||||||
elisp.listLast = function(cons) {
|
|
||||||
var last;
|
|
||||||
while (!elisp.isNil(cons)) {
|
|
||||||
last = cons;
|
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 = [],
|
var list = [],
|
||||||
i = 0;
|
i = 0,
|
||||||
while (!elisp.isNil(cons)) {
|
cons = this;
|
||||||
list.push(fn(elisp.car(cons), i));
|
// print('[LispCons.map] calling cons.isNil - cons: ' + cons + ' - _car: ' + cons._car + ' _cdr: ' + this._cdr);
|
||||||
cons = elisp.cdr(cons);
|
while (!cons.isNil()) {
|
||||||
|
list.push(fn(cons.car(), i));
|
||||||
|
cons = cons.cdr();
|
||||||
++i;
|
++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,
|
var i = 0,
|
||||||
n = elisp.listLength(cons);
|
n = this._length;
|
||||||
while (i < n) {
|
while (i < n) {
|
||||||
accum = fn(accum, elisp.nth(i++, cons));
|
accum = fn(accum, this.nth(i++));
|
||||||
}
|
}
|
||||||
return accum;
|
return accum;
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.idFunction = function(x){return x;};
|
LispCons.prototype.unlist = function() {
|
||||||
|
return this.reduce([], function(x){return x;});
|
||||||
elisp.unlist = function(cons) {
|
|
||||||
return elisp.listReduce(elisp.idFunction, [], cons);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.nth = function(n, cons) {
|
LispCons.prototype.nth = function(n) {
|
||||||
var i = 0,
|
var i = 0,
|
||||||
e;
|
e,
|
||||||
while (i <= n && !elisp.isNil(cons)) {
|
cons = this;
|
||||||
e = elisp.car(cons);
|
// print('[LispCons.nth] calling cons.isNil - cons: ' + cons + ' - _cdr: ' + cons._cdr);
|
||||||
cons = elisp.cdr(cons);
|
while (i <= n && !cons.isNil()) {
|
||||||
|
e = cons.car();
|
||||||
|
cons = cons.cdr();
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
return n > --i ? elisp.nil : e;
|
return n > --i ? type.NIL : e;
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.nthcdr = function(n, cons) {
|
LispCons.prototype.nthcdr = function(n) {
|
||||||
var e = elisp.cdr(cons),
|
var e = this.cdr(),
|
||||||
i = 0;
|
i = 0;
|
||||||
while (i < n && !elisp.isNil(e)) {
|
// print('[LispCons.nthcdr] calling e.isNil - e: ' + e + ' - _cdr: ' + e._cdr);
|
||||||
e = elisp.cdr(e);
|
while (i < n && !e.isNil()) {
|
||||||
|
e = e.cdr();
|
||||||
++i;
|
++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;
|
||||||
|
}
|
||||||
124
elisp/parser.js
124
elisp/parser.js
|
|
@ -6,54 +6,56 @@
|
||||||
// Released under the terms of the MIT license. See the included file
|
// Released under the terms of the MIT license. See the included file
|
||||||
// LICENSE.
|
// LICENSE.
|
||||||
|
|
||||||
|
var utils = require('elisp/utils'),
|
||||||
|
type = require('elisp/types');
|
||||||
|
|
||||||
elisp.Parser = function(data) {
|
var Parser = function(data) {
|
||||||
this.data = data || '';
|
this.data = data || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Parser.Error = function(name, message) {
|
Parser.Error = function(name, message) {
|
||||||
this.parserError = true;
|
this.parserError = true;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Parser.Error.messages = {
|
Parser.Error.messages = {
|
||||||
'eof': "no more input"
|
'eof': "no more input"
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Parser.prototype.error = function(name) {
|
Parser.prototype.error = function(name) {
|
||||||
throw(new elisp.Parser.Error(name, elisp.Parser.Error.messages[name]));
|
throw(new Parser.Error(name, Parser.Error.messages[name]));
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Parser.prototype.peek = function() {
|
Parser.prototype.peek = function() {
|
||||||
return this.data[this.pos];
|
return this.data[this.pos];
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Parser.prototype.consumeChar = function() {
|
Parser.prototype.consumeChar = function() {
|
||||||
if (this.pos >= this.data.length) this.error('eof');
|
if (this.pos >= this.data.length) this.error('eof');
|
||||||
return this.data[this.pos++];
|
return this.data[this.pos++];
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Parser.prototype.consumeWhitespace = function() {
|
Parser.prototype.consumeWhitespace = function() {
|
||||||
var c;
|
var c;
|
||||||
while ((c = this.peek()) && c.match(/[\s\n]/)) {
|
while ((c = this.peek()) && c.match(/[\s\n]/)) {
|
||||||
this.consumeChar();
|
this.consumeChar();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Parser.prototype.rewind = function() {
|
Parser.prototype.rewind = function() {
|
||||||
this.pos = 0;
|
this.pos = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Parser.prototype.rest = function() {
|
Parser.prototype.rest = function() {
|
||||||
return this.data.substring(this.pos);
|
return this.data.substring(this.pos);
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Parser.prototype.moreInput = function() {
|
Parser.prototype.moreInput = function() {
|
||||||
return (this.pos < this.data.length);
|
return (this.pos < this.data.length);
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Parser.prototype.parse = function(string) {
|
Parser.prototype.parse = function(string) {
|
||||||
if (string) this.data = string;
|
if (string) this.data = string;
|
||||||
this.rewind();
|
this.rewind();
|
||||||
var exprs = [];
|
var exprs = [];
|
||||||
|
|
@ -72,19 +74,19 @@ elisp.Parser.prototype.parse = function(string) {
|
||||||
}
|
}
|
||||||
this.expressions = exprs;
|
this.expressions = exprs;
|
||||||
// print('');
|
// print('');
|
||||||
// elisp.Util.pp(exprs);
|
// Utils.pp(exprs);
|
||||||
// print('');
|
// print('');
|
||||||
return exprs;
|
return type.mkList(exprs);
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Parser.prototype.parseOne = function(string) {
|
Parser.prototype.parseOne = function(string) {
|
||||||
return this.parse(string)[0];
|
return this.parse(string).car();
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Parser.prototype.parseUntil = function(regex, initial, next, consumeTerminator) {
|
Parser.prototype.parseUntil = function(regex, initial, next, consumeTerminator) {
|
||||||
var c,
|
var c,
|
||||||
token = initial,
|
token = initial,
|
||||||
condition = function(c){ return c.match(regex) == null; };
|
condition = function(c){ return c.match(regex) === null; };
|
||||||
while ((c = this.peek()) && condition(c)) {
|
while ((c = this.peek()) && condition(c)) {
|
||||||
token = next(token, this.consumeChar());
|
token = next(token, this.consumeChar());
|
||||||
}
|
}
|
||||||
|
|
@ -92,7 +94,7 @@ elisp.Parser.prototype.parseUntil = function(regex, initial, next, consumeTermin
|
||||||
return token;
|
return token;
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Parser.prototype.parseList = function() {
|
Parser.prototype.parseList = function() {
|
||||||
var list = [],
|
var list = [],
|
||||||
expr;
|
expr;
|
||||||
// consume initial paren '('
|
// consume initial paren '('
|
||||||
|
|
@ -100,40 +102,43 @@ elisp.Parser.prototype.parseList = function() {
|
||||||
while ((expr = this.parseExpression()) && expr != ')') {
|
while ((expr = this.parseExpression()) && expr != ')') {
|
||||||
list.push(expr);
|
list.push(expr);
|
||||||
}
|
}
|
||||||
return list;
|
return type.mkList(list);
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Parser.prototype.parseCons = function() {
|
Parser.prototype.parseCons = function() {
|
||||||
var cons = [],
|
var car, cdr, expr;
|
||||||
expr;
|
|
||||||
// consume initial paren '('
|
// consume initial paren '('
|
||||||
this.consumeChar();
|
this.consumeChar();
|
||||||
cons.push(this.parseExpression());
|
|
||||||
|
car = this.parseExpression();
|
||||||
|
|
||||||
// ignore .
|
// ignore .
|
||||||
this.parseExpression();
|
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
|
// consume initial quotation mark
|
||||||
this.consumeChar();
|
this.consumeChar();
|
||||||
var self = this;
|
var self = this;
|
||||||
return this.parseUntil(/"/, '', function(s,c){
|
return new type.LispString(this.parseUntil(/"/, '', function(s,c){
|
||||||
if (c == '\\') {
|
if (c == '\\') {
|
||||||
c = self.consumeChar();
|
c = self.consumeChar();
|
||||||
}
|
}
|
||||||
return s + c;
|
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;});
|
var symbol = this.parseUntil(/[\s()]/, '', function(t,c){return t + c;});
|
||||||
return symbol;
|
return new type.LispSymbol(symbol);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Probably easy to break
|
// Probably easy to break
|
||||||
elisp.Parser.prototype.parseRegex = function() {
|
Parser.prototype.parseRegex = function() {
|
||||||
// consume initial slash
|
// consume initial slash
|
||||||
this.consumeChar();
|
this.consumeChar();
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
@ -159,9 +164,9 @@ elisp.Parser.prototype.parseRegex = function() {
|
||||||
//
|
//
|
||||||
// Binary, octal, hex, or arbitrary radix integers not yet parsed.
|
// Binary, octal, hex, or arbitrary radix integers not yet parsed.
|
||||||
// (e.g. #x100 == #o400 == #b100000000 == #24rag
|
// (e.g. #x100 == #o400 == #b100000000 == #24rag
|
||||||
elisp.Parser.prototype.parseNumber = function() {
|
Parser.prototype.parseNumber = function() {
|
||||||
var value = this.parseIntOrFloat(),
|
var value = this.parseIntOrFloat(),
|
||||||
exponentAllowed = value === parseInt(value),
|
exponentAllowed = value === parseInt(value, 10),
|
||||||
exp;
|
exp;
|
||||||
|
|
||||||
// now check for an exponent
|
// now check for an exponent
|
||||||
|
|
@ -175,11 +180,11 @@ elisp.Parser.prototype.parseNumber = function() {
|
||||||
value *= Math.pow(10, exp);
|
value *= Math.pow(10, exp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return new type.LispNumber(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pack int and float parsing together for simplicity's sake.
|
// Pack int and float parsing together for simplicity's sake.
|
||||||
elisp.Parser.prototype.parseIntOrFloat = function() {
|
Parser.prototype.parseIntOrFloat = function() {
|
||||||
this.exponentAllowed = true;
|
this.exponentAllowed = true;
|
||||||
var sign = this.peek() == '-' || this.peek() == '+' ? this.consumeChar() : '+',
|
var sign = this.peek() == '-' || this.peek() == '+' ? this.consumeChar() : '+',
|
||||||
value;
|
value;
|
||||||
|
|
@ -187,7 +192,7 @@ elisp.Parser.prototype.parseIntOrFloat = function() {
|
||||||
// There may or may not be an integer part of the number.
|
// There may or may not be an integer part of the number.
|
||||||
if (this.peek() != '.') {
|
if (this.peek() != '.') {
|
||||||
value = this.parseUntil(/[^\d]/, 0, function(n,c) {
|
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.
|
// string or some chars in the same regex.
|
||||||
//
|
//
|
||||||
// TODO: pick up Friedl and find a way to consolidate these.
|
// TODO: pick up Friedl and find a way to consolidate these.
|
||||||
elisp.Parser.prototype.lookingAtNumber = function() {
|
Parser.prototype.lookingAtNumber = function() {
|
||||||
var pos = this.pos,
|
var pos = this.pos,
|
||||||
rest = this.rest(),
|
rest = this.rest(),
|
||||||
match = rest.match(/^[+-]?\d+(\.\d*)?[)\s\n]/)
|
match = rest.match(/^[+-]?\d+(\.\d*)?[)\s\n]/) ||
|
||||||
|| rest.match(/^[+-]?\d+(\.\d*)?$/)
|
rest.match(/^[+-]?\d+(\.\d*)?$/) ||
|
||||||
|| rest.match(/^[+-]?\d+(\.\d+)?([eE][+-]?\d+)?[)\s\n]/)
|
rest.match(/^[+-]?\d+(\.\d+)?([eE][+-]?\d+)?[)\s\n]/) ||
|
||||||
|| rest.match(/^[+-]?\d+(\.\d+)?([eE][+-]?\d+)?$/)
|
rest.match(/^[+-]?\d+(\.\d+)?([eE][+-]?\d+)?$/) ||
|
||||||
|| rest.match(/^[+-]?(\d+)?\.\d+([eE][+-]?\d+)?[)\s\n]/)
|
rest.match(/^[+-]?(\d+)?\.\d+([eE][+-]?\d+)?[)\s\n]/) ||
|
||||||
|| rest.match(/^[+-]?(\d+)?\.\d+([eE][+-]?\d+)?$/);
|
rest.match(/^[+-]?(\d+)?\.\d+([eE][+-]?\d+)?$/);
|
||||||
return (match != null);
|
return (match !== null);
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.Parser.prototype.lookingAtCons = function() {
|
Parser.prototype.lookingAtCons = function() {
|
||||||
|
// FIXME
|
||||||
|
return false;
|
||||||
|
|
||||||
var orig_pos = this.pos,
|
var orig_pos = this.pos,
|
||||||
_ = this.consumeChar(),
|
_ = this.consumeChar(),
|
||||||
__ = _ && this.peek() && this.parseExpression(),
|
__ = _ && this.peek() && this.parseExpression(),
|
||||||
cdr = __ && this.peek() &&this.parseExpression();
|
cdr = __ && this.peek() &&this.parseExpression();
|
||||||
this.pos = orig_pos; // rewind, like it never happened.
|
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,
|
var value,
|
||||||
c = this.peek();
|
c = this.peek();
|
||||||
if (c == '(' && this.lookingAtCons()) {
|
if (c == '(' && this.lookingAtCons()) {
|
||||||
value = elisp.consPair(this.parseCons());
|
value = this.parseCons();
|
||||||
}
|
}
|
||||||
else if (c == '(') {
|
else if (c == '(') {
|
||||||
var list = this.parseList();
|
value = this.parseList();
|
||||||
value = (list.length > 0) ? elisp.list(list) : elisp.nil;
|
|
||||||
}
|
}
|
||||||
else if (c == ')') {
|
else if (c == ')') {
|
||||||
return this.consumeChar();
|
return this.consumeChar();
|
||||||
}
|
}
|
||||||
else if (c == "'") {
|
else if (c == "'") {
|
||||||
this.consumeChar();
|
this.consumeChar();
|
||||||
value = elisp.cons(elisp.symbol('quote'), this.parseExpression());
|
value = new type.LispCons(new type.LispSymbol('quote'), this.parseExpression());
|
||||||
}
|
}
|
||||||
else if (c == '"') {
|
else if (c == '"') {
|
||||||
value = elisp.string(this.parseString());
|
value = this.parseString();
|
||||||
}
|
}
|
||||||
else if (this.lookingAtNumber()) {
|
else if (this.lookingAtNumber()) {
|
||||||
value = elisp.number(this.parseNumber());
|
value = this.parseNumber();
|
||||||
}
|
}
|
||||||
else if (c) {
|
else if (c) {
|
||||||
value = elisp.symbol(this.parseSymbol());
|
value = this.parseSymbol();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (this.pos == this.data.length) {
|
if (this.pos == this.data.length) {
|
||||||
print('[error] no more input. unterminated string or list? (continuing anyway)');
|
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.pos = ' + this.pos);
|
||||||
print('this.data.length = ' + this.data.length);
|
print('this.data.length = ' + this.data.length);
|
||||||
print('this.rest = ' + this.rest());
|
print('this.rest = ' + this.rest());
|
||||||
|
|
@ -277,6 +285,4 @@ elisp.Parser.prototype.parseExpression = function() {
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
/****************************************************************
|
exports.Parser = Parser;
|
||||||
****************************************************************/
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,25 +6,32 @@
|
||||||
// Released under the terms of the MIT license. See the included file
|
// Released under the terms of the MIT license. See the included file
|
||||||
// LICENSE.
|
// LICENSE.
|
||||||
|
|
||||||
|
var init = require('elisp/init'),
|
||||||
|
type = require('elisp/types'),
|
||||||
|
utils = require('elisp/utils');
|
||||||
|
|
||||||
elisp.PrimitiveVariables = [
|
var PrimitiveVariables = [
|
||||||
['t', {
|
['t', {
|
||||||
type: 'variable',
|
type: 'variable',
|
||||||
value: ['symbol', 't'],
|
value: type.T,
|
||||||
docstring: "true"
|
docstring: "true"
|
||||||
}],
|
}],
|
||||||
['nil', {
|
['nil', {
|
||||||
type: 'variable',
|
type: 'variable',
|
||||||
value: ['symbol', 'nil'],
|
value: type.NIL,
|
||||||
docstring: "nil"
|
docstring: "nil"
|
||||||
}]
|
}]
|
||||||
];
|
];
|
||||||
|
exports.PrimitiveVariables = PrimitiveVariables;
|
||||||
|
|
||||||
elisp.PrimitiveFunctions = [];
|
|
||||||
|
|
||||||
// 'this' is bound to the elisp.Evaluator object when executing primitve functions
|
var PrimitiveFunctions = [];
|
||||||
elisp.definePrimitive = function(name, params, body, docstring) {
|
exports.PrimitiveFunctions = PrimitiveFunctions;
|
||||||
elisp.PrimitiveFunctions.push([name, {
|
|
||||||
|
// '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',
|
type: 'primitive',
|
||||||
name: name,
|
name: name,
|
||||||
params: params, // unused right now but should be checked
|
params: params, // unused right now but should be checked
|
||||||
|
|
@ -32,100 +39,95 @@ elisp.definePrimitive = function(name, params, body, docstring) {
|
||||||
body: body
|
body: body
|
||||||
}]);
|
}]);
|
||||||
};
|
};
|
||||||
|
exports.definePrimitive = definePrimitive;
|
||||||
|
|
||||||
elisp.notFunc = function(fn) {
|
var notFunc = function(fn) {
|
||||||
return function(x){ return !fn(x); };
|
return function(x){ return !fn(x); };
|
||||||
};
|
};
|
||||||
|
exports.notFunc = notFunc;
|
||||||
|
|
||||||
elisp.makePrimitiveBooleanFunc = function(fn) {
|
var makePrimitiveBooleanFunc = function(fn) {
|
||||||
return function(x){ return fn(x) ? elisp.t : elisp.nil; };
|
return function(x){ return x[fn]() ? type.T : type.NIL; };
|
||||||
};
|
};
|
||||||
|
exports.makePrimitiveBooleanFunc = makePrimitiveBooleanFunc;
|
||||||
|
|
||||||
elisp._definePrimitives = function() {
|
init.hook('Define Primitive Variables and Functions', function() {
|
||||||
elisp.definePrimitive('consp', ['symbol'], elisp.makePrimitiveBooleanFunc(elisp.isCons),
|
var type = require('elisp/types');
|
||||||
"Return T if symbol is a cons, nil otherwise.");
|
definePrimitive('consp', ['symbol'], makePrimitiveBooleanFunc('isCons'),
|
||||||
|
"Return T if symbol is a cons, nil otherwise.");
|
||||||
|
|
||||||
elisp.definePrimitive('atom', ['symbol'], elisp.makePrimitiveBooleanFunc(elisp.isAtom),
|
definePrimitive('atom', ['symbol'], makePrimitiveBooleanFunc('isAtom'),
|
||||||
"Return T if symbol is not a cons or is nil, nil otherwise.");
|
"Return T if symbol is not a cons or is nil, nil otherwise.");
|
||||||
|
|
||||||
elisp.definePrimitive('symbol-name', ['symbol'],
|
definePrimitive('symbol-name', ['symbol'],
|
||||||
function(symbol) { return elisp.string(elisp.val(symbol)); },
|
function(symbol) { return new type.LispString(symbol.symbolName()); },
|
||||||
"Return a symbol's name, a string.");
|
"Return a symbol's name, a string.");
|
||||||
|
|
||||||
elisp.definePrimitive('string-match', ['regex', 'string', '&optional', 'start'],
|
definePrimitive('string-match', ['regex', 'string', '&optional', 'start'],
|
||||||
function(regex, string, start) {
|
function(regex, string, start) {
|
||||||
var index = start ? elisp.val(start) : 0,
|
var index = start ? start.value() : 0,
|
||||||
s = elisp.val(string).substring(index),
|
s = string.value().substring(index),
|
||||||
match = s.match(new RegExp(elisp.val(regex))),
|
match = s.match(new RegExp(regex.value())),
|
||||||
found = match ? elisp.number(s.indexOf(match[0])) : elisp.nil;
|
found = match ? new type.LispNumber(s.indexOf(match[0])) : type.NIL;
|
||||||
return found;},
|
return found;},
|
||||||
"Return the index of the char matching regex in string, beginning from start if available.");
|
"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
|
// Right now a single string in the arg list will cause all the arguments
|
||||||
// to be converted to strings similar to JavaScript. These
|
// to be converted to strings similar to JavaScript. These
|
||||||
// semantics suck and should change, not only for real emacs lisp compatibility.
|
// semantics suck and should change, not only for real emacs lisp compatibility.
|
||||||
elisp.definePrimitive('+', [/*...*/],
|
// ... for now it's the only way to catenate strings.
|
||||||
function() {
|
definePrimitive('+', [/*...*/],
|
||||||
var args = elisp.Util.shallowCopy(arguments),
|
function() {
|
||||||
initial = elisp.inferType(args),
|
var args = utils.shallowCopy(arguments),
|
||||||
type = initial[0];
|
initial = type.inferType(args),
|
||||||
return elisp.Util.reduce(function(sum, n) {
|
result = utils.reduce(function(sum, n) {
|
||||||
return [type, elisp.val(sum) + elisp.val(n)];
|
return sum + n.value();
|
||||||
}, initial, args);},
|
}, initial.value(), args);
|
||||||
"add two numbers");
|
return type.construct(initial.tag(), result);
|
||||||
|
}, "add two numbers");
|
||||||
|
|
||||||
elisp.definePrimitive('-', [/*...*/],
|
definePrimitive('-', [/*...*/],
|
||||||
function() {
|
function() {
|
||||||
return elisp.Util.foldr(function(diff, n) {
|
if (arguments.length == 1) {
|
||||||
return elisp.number(elisp.val(diff) - elisp.val(n));
|
return new type.LispNumber(0 - arguments[0].value());
|
||||||
}, elisp.number(0), elisp.Util.shallowCopy(arguments));},
|
}
|
||||||
"subtract two numbers");
|
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");
|
||||||
|
|
||||||
elisp.definePrimitive('*', [/*...*/],
|
definePrimitive('*', [/*...*/],
|
||||||
function() {
|
function() {
|
||||||
return elisp.Util.reduce(function(prod, n) {
|
var initial = arguments.length >= 1 ? arguments[0].value() : 1,
|
||||||
return elisp.number(elisp.val(prod) * elisp.val(n));
|
args = utils.shallowCopy(arguments).slice(1),
|
||||||
}, elisp.number(1), elisp.Util.shallowCopy(arguments));},
|
result = utils.reduce(function(prod, n){
|
||||||
"multiply two numbers");
|
return prod * n.value();
|
||||||
|
}, initial, args);
|
||||||
|
return new type.LispNumber(result);
|
||||||
|
}, "multiply one or more numbers");
|
||||||
|
|
||||||
elisp.definePrimitive('/', [/*...*/],
|
definePrimitive('/', [/*...*/],
|
||||||
function() {
|
function() {
|
||||||
return elisp.Util.foldr(function(quot, n) {
|
// TODO signal a real error for < 2 arguments
|
||||||
return elisp.number(elisp.val(quot) / elisp.val(n));
|
if (arguments.length < 2) {
|
||||||
}, elisp.number(1), elisp.Util.shallowCopy(arguments));
|
print("[error] invalid division, need 2 or more params");
|
||||||
},
|
return type.NIL;
|
||||||
"divide two numbers");
|
}
|
||||||
|
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");
|
||||||
|
|
||||||
elisp.definePrimitive('print', ['x'],
|
definePrimitive('print', ['x'], utils.pp, "print an expression");
|
||||||
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'],
|
var settings = require('elisp/settings');
|
||||||
function(bool){ elisp.hidePrompt = !elisp.isNil(bool); },
|
definePrimitive('hide-prompt', ['yes-or-no'],
|
||||||
"Call with T to hide the prompt or nil to show it.");
|
function(bool){ settings.hidePrompt = !bool.isNil(); },
|
||||||
};
|
"Call with T to hide the prompt or nil to show it.");
|
||||||
elisp.initHook(elisp._definePrimitives);
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,48 +6,65 @@
|
||||||
// Released under the terms of the MIT license. See the included file
|
// Released under the terms of the MIT license. See the included file
|
||||||
// LICENSE.
|
// LICENSE.
|
||||||
|
|
||||||
elisp.eval = function(exprs) {
|
var parser = require('elisp/parser'),
|
||||||
var e = new elisp.Evaluator();
|
evaluator = require('elisp/evaluator'),
|
||||||
return e.evalExpressions(exprs);
|
utils = require('elisp/utils'),
|
||||||
};
|
Parser = parser.Parser,
|
||||||
|
Evaluator = evaluator.Evaluator;
|
||||||
|
|
||||||
elisp.parse = function(string) {
|
// this should probably be renamed to avoid confusion
|
||||||
var p = new elisp.Parser();
|
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);
|
return p.parse(string);
|
||||||
};
|
};
|
||||||
|
exports.parse = parse;
|
||||||
|
|
||||||
elisp.parseOne = function(string) {
|
var parseOne = function(string) {
|
||||||
return elisp.parse(string)[0];
|
return parse(string).car();
|
||||||
};
|
};
|
||||||
|
exports.parseOne = parseOne;
|
||||||
|
exports.read = parseOne;
|
||||||
|
|
||||||
elisp.read = elisp.parseOne;
|
exports.pp = utils.pp;
|
||||||
elisp.print = elisp.Util.pp;
|
|
||||||
|
|
||||||
elisp.rep = function(string) {
|
var rep = function(string) {
|
||||||
elisp.print(elisp.eval(elisp.parse(string)));
|
var p = new Parser(),
|
||||||
|
e = new Evaluator();
|
||||||
|
utils.pp(e.eval(p.read(string)));
|
||||||
};
|
};
|
||||||
|
exports.rep = rep;
|
||||||
|
|
||||||
elisp.repl = function() {
|
var repl = function() {
|
||||||
var p = new elisp.Parser(),
|
var p = new Parser(),
|
||||||
e = new elisp.Evaluator();
|
e = new Evaluator(),
|
||||||
|
sys = require('system'),
|
||||||
|
settings = require('elisp/settings');
|
||||||
while (true) {
|
while (true) {
|
||||||
if (!elisp.hidePrompt) {
|
if (!settings.hidePrompt) {
|
||||||
print("elisp> "); // i don't want a newline, grrrr
|
sys.stdout.print("elisp> "); // i don't want a newline, grrrr
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
var line = readline();
|
var line = sys.stdin.readLine();
|
||||||
while (!line) {
|
while (!line) {
|
||||||
line = readline();
|
line = sys.stdin.readLine();
|
||||||
}
|
}
|
||||||
if (line.substring(0,1).toLowerCase() == 'q') return;
|
if (line.substring(0,1).toLowerCase() == 'q') return;
|
||||||
elisp.print(e.eval(p.parseOne(line)));
|
utils.pp(e.eval(p.parseOne(line)));
|
||||||
} catch (x) {
|
} catch (x) {
|
||||||
if (x.evalError) {
|
if (x.evalError) {
|
||||||
print('[error] ' + x.message + ': ' + x.expression);
|
print('[error] ' + x.message + ': ' + x.expression);
|
||||||
elisp.print(x);
|
utils.pp(x);
|
||||||
}
|
}
|
||||||
else throw(x);
|
else throw(x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
exports.repl = repl;
|
||||||
|
|
||||||
|
|
|
||||||
10
elisp/settings.js
Normal file
10
elisp/settings.js
Normal file
|
|
@ -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;
|
||||||
|
|
@ -6,17 +6,16 @@
|
||||||
// Released under the terms of the MIT license. See the included file
|
// Released under the terms of the MIT license. See the included file
|
||||||
// LICENSE.
|
// LICENSE.
|
||||||
|
|
||||||
/****************************************************************
|
var utils = require('elisp/utils'),
|
||||||
**** Symbol Table **********************************************
|
type = require('elisp/types');
|
||||||
****************************************************************/
|
|
||||||
|
|
||||||
elisp.SymbolTable = function(bindings) {
|
var SymbolTable = function(bindings) {
|
||||||
this.symbols = [[]];
|
this.symbols = [[]];
|
||||||
this.level = 0;
|
this.level = 0;
|
||||||
if (bindings) this.define(bindings);
|
if (bindings) this.define(bindings);
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.SymbolTable.prototype.lookupWithScope = function(name) {
|
SymbolTable.prototype.lookupWithScope = function(name) {
|
||||||
var i = this.level,
|
var i = this.level,
|
||||||
symbol;
|
symbol;
|
||||||
while (i >= 0) {
|
while (i >= 0) {
|
||||||
|
|
@ -29,14 +28,18 @@ elisp.SymbolTable.prototype.lookupWithScope = function(name) {
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.SymbolTable.prototype.lookup = function(name) {
|
SymbolTable.prototype.lookup = function(name) {
|
||||||
|
// print('name: ' + name);
|
||||||
var pair = this.lookupWithScope(name);
|
var pair = this.lookupWithScope(name);
|
||||||
|
// print('pair: ' + pair);
|
||||||
|
// print('------');
|
||||||
return pair && pair[1];
|
return pair && pair[1];
|
||||||
};
|
};
|
||||||
|
|
||||||
// store the given symbol/value pair in the symbol table at the current level.
|
// store the given symbol/value pair in the symbol table at the current level.
|
||||||
elisp.SymbolTable.prototype.define = function(name, value) {
|
SymbolTable.prototype.define = function(name, value) {
|
||||||
if (value === undefined && elisp.typeOf(name) == 'array') {
|
// print('###### REAL DEFINE: ' + name + ' = ' + value);
|
||||||
|
if (value === undefined && utils.typeOf(name) == 'array') {
|
||||||
var bindings = name,
|
var bindings = name,
|
||||||
i = 0,
|
i = 0,
|
||||||
n = bindings.length,
|
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('>>> pushing scope <<<');
|
||||||
// print('>>> level going from ' + this.level + ' to ' + (1+this.level));
|
// print('>>> level going from ' + this.level + ' to ' + (1+this.level));
|
||||||
// print(bindings);
|
// print(bindings);
|
||||||
|
|
@ -63,16 +66,15 @@ elisp.SymbolTable.prototype.pushScope = function(bindings) {
|
||||||
if (bindings) this.define(bindings);
|
if (bindings) this.define(bindings);
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.SymbolTable.prototype.popScope = function() {
|
SymbolTable.prototype.popScope = function() {
|
||||||
--this.level;
|
--this.level;
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.SymbolTable.prototype.set = function(name, value) {
|
SymbolTable.prototype.set = function(name, value) {
|
||||||
var pair = this.lookupWithScope(name),
|
var pair = this.lookupWithScope(name),
|
||||||
level = pair[0];
|
level = pair[0];
|
||||||
this.symbols[level][name] = value;
|
this.symbols[level][name] = value;
|
||||||
};
|
};
|
||||||
|
|
||||||
/****************************************************************
|
exports.SymbolTable = SymbolTable;
|
||||||
****************************************************************/
|
|
||||||
|
|
||||||
|
|
|
||||||
46
elisp/trace.js
Normal file
46
elisp/trace.js
Normal file
|
|
@ -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<len; i++) {
|
||||||
|
if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
|
||||||
|
callstack.push(lines[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Remove call to printStackTrace()
|
||||||
|
callstack.shift();
|
||||||
|
isCallstackPopulated = true;
|
||||||
|
}
|
||||||
|
else if (window.opera && e.message) { //Opera
|
||||||
|
var lines = e.message.split("\n");
|
||||||
|
for (var i=0, len=lines.length; i<len; i++) {
|
||||||
|
if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
|
||||||
|
var entry = lines[i];
|
||||||
|
//Append next line also since it has the file info
|
||||||
|
if (lines[i+1]) {
|
||||||
|
entry += " at " + lines[i+1];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
callstack.push(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Remove call to printStackTrace()
|
||||||
|
callstack.shift();
|
||||||
|
isCallstackPopulated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isCallstackPopulated) { //IE and Safari
|
||||||
|
var currentFunction = arguments.callee.caller;
|
||||||
|
while (currentFunction) {
|
||||||
|
var fn = currentFunction.toString();
|
||||||
|
var fname = fn.substring(fn.indexOf("function") + 8, fn.indexOf("(")) || "anonymous";
|
||||||
|
callstack.push(fname);
|
||||||
|
currentFunction = currentFunction.caller;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print('stack trace', callstack.join("\n\n"));
|
||||||
|
};
|
||||||
270
elisp/types.js
270
elisp/types.js
|
|
@ -6,108 +6,240 @@
|
||||||
// Released under the terms of the MIT license. See the included file
|
// Released under the terms of the MIT license. See the included file
|
||||||
// LICENSE.
|
// LICENSE.
|
||||||
|
|
||||||
// data types are simple tags
|
var tags = ['symbol', 'string', 'number', 'cons', 'lambda'];
|
||||||
elisp._defineTags = function() {
|
exports.tags = tags;
|
||||||
elisp.tags = ['symbol', 'string', 'number', 'cons', 'lambda', 'regex'];
|
|
||||||
elisp.tags.each = Array.prototype.each;
|
|
||||||
|
|
||||||
// define constructors for the primitive types (box values)
|
|
||||||
// e.g. elisp.symbol('foo') => ['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);
|
|
||||||
|
|
||||||
|
|
||||||
// retrieve the tag from a value
|
// All types descend from this object
|
||||||
elisp.tag = function(expr) {
|
var LispObject = function(value) {
|
||||||
elisp.assert(function() { var f='tag'; return elisp.typeOf(expr) == 'array'; }, expr);
|
this._tag = 'object';
|
||||||
return expr[0];
|
this._value = value;
|
||||||
};
|
};
|
||||||
|
|
||||||
// unbox a value
|
LispObject.prototype.tag = function() {
|
||||||
elisp.val = function(expr) {
|
return this._tag;
|
||||||
elisp.assert(function() { var f='val'; return elisp.typeOf(expr) == 'array'; }, expr);
|
|
||||||
return expr[1];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.symbolName = function(symbol) {
|
LispObject.prototype.value = function() {
|
||||||
// elisp.Util.pp(symbol);
|
return this._value;
|
||||||
elisp.assert(function(){ var f='symbolName'; return elisp.isSymbol(symbol); }, symbol);
|
|
||||||
return elisp.val(symbol);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.isNilSymbol = function(expr) {
|
LispObject.prototype.repr = function() {
|
||||||
return (elisp.isSymbol(expr) && elisp.symbolName(expr) == 'nil');
|
return '#<object value:' + this._value + '>';
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.isNil = function(expr) {
|
LispObject.prototype.eq = function(other) {
|
||||||
return elisp.isNilSymbol(expr);
|
return other && this._tag == other._tag && this._value == other._value;
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.list = function(exprs) {
|
LispObject.prototype.isA = function(what) {
|
||||||
var list = elisp.nil,
|
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;
|
i = exprs.length;
|
||||||
while (--i >= 0) {
|
while (--i >= 0) {
|
||||||
list = elisp.cons(exprs[i], list);
|
list = new LispCons(exprs[i], list);
|
||||||
}
|
}
|
||||||
return 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) {
|
var construct = function(typeName, value) {
|
||||||
return !elisp.isCons(expr);
|
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,
|
initial = 0,
|
||||||
i = exprs.length-1;
|
i = exprs.length-1;
|
||||||
while(i >= 0) {
|
while(i >= 0) {
|
||||||
if (elisp.isString(exprs[i--])) {
|
if (!exprs[i--].isNumber()) {
|
||||||
type = 'string';
|
type_name = 'string';
|
||||||
initial = '';
|
initial = '';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return elisp[type](initial);
|
return construct(type_name, initial);
|
||||||
};
|
};
|
||||||
|
exports.inferType = inferType;
|
||||||
|
|
||||||
|
|
||||||
// special forms
|
// special forms
|
||||||
|
|
||||||
elisp.isSpecialForm = function(name, expr) {
|
LispObject.prototype._isSpecialForm = function(name) {
|
||||||
var tag = elisp.tag(expr),
|
return (this.isCons() && this.car().isSymbol() && this.car().symbolName() == name);
|
||||||
car = elisp.typeOf(expr) == 'array' && elisp.val(expr)[0],
|
|
||||||
thisName = car && elisp.symbolName(car);
|
|
||||||
return (tag == 'cons' && thisName == name);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
elisp.isQuote = function(expr){return elisp.isSpecialForm('quote', expr);};
|
LispObject.prototype.isQuote = function(){return this._isSpecialForm('quote');};
|
||||||
elisp.isDefVar = function(expr){return elisp.isSpecialForm('defvar', expr);};
|
LispObject.prototype.isDefvar = function(){return this._isSpecialForm('defvar');};
|
||||||
elisp.isDefFunc = function(expr){return elisp.isSpecialForm('defun', expr);};
|
LispObject.prototype.isDefun = function(){return this._isSpecialForm('defun');};
|
||||||
elisp.isSet = function(expr){return elisp.isSpecialForm('set', expr);};
|
LispObject.prototype.isSet = function(){return this._isSpecialForm('set');};
|
||||||
elisp.isSetq = function(expr){return elisp.isSpecialForm('setq', expr);};
|
LispObject.prototype.isSetq = function(){return this._isSpecialForm('setq');};
|
||||||
elisp.isCond = function(expr){return elisp.isSpecialForm('cond', expr);};
|
LispObject.prototype.isCond = function(){return this._isSpecialForm('cond');};
|
||||||
elisp.isIf = function(expr){return elisp.isSpecialForm('if', expr);};
|
LispObject.prototype.isIf = function(){return this._isSpecialForm('if');};
|
||||||
|
|
|
||||||
143
elisp/util.js
143
elisp/util.js
|
|
@ -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();
|
|
||||||
};
|
|
||||||
101
elisp/utils.js
Normal file
101
elisp/utils.js
Normal file
|
|
@ -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;
|
||||||
3
repl.js
Normal file
3
repl.js
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
require.paths.unshift('.');
|
||||||
|
var elisp = require('elisp');
|
||||||
|
elisp.repl.repl();
|
||||||
Loading…
Reference in a new issue