mirror of
https://github.com/samsonjs/elisp.js.git
synced 2026-04-27 15:07:47 +00:00
hooked up defvar, defun, set, and setq.
This commit is contained in:
parent
6fe9048c6e
commit
df628aa959
1 changed files with 116 additions and 68 deletions
184
el.js
184
el.js
|
|
@ -1,4 +1,4 @@
|
||||||
var EL = function(){};
|
var spidermonkeykludge = 0;
|
||||||
// spidermonkey doesn't like a C-style comment at the beginning
|
// spidermonkey doesn't like a C-style comment at the beginning
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -39,6 +39,10 @@ Array.prototype.each = function(fn) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// our namespace
|
||||||
|
var EL = function(){};
|
||||||
|
|
||||||
|
|
||||||
/****************************************************************
|
/****************************************************************
|
||||||
**** Parser ****************************************************
|
**** Parser ****************************************************
|
||||||
****************************************************************/
|
****************************************************************/
|
||||||
|
|
@ -188,48 +192,39 @@ EL.Parser.prototype.lookingAtNumber = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
EL.Parser.prototype.lookingAtCons = function() {
|
EL.Parser.prototype.lookingAtCons = function() {
|
||||||
this.fake = true;
|
|
||||||
var orig_pos = this.pos,
|
var orig_pos = this.pos,
|
||||||
_ = this.consumeChar(),
|
_ = this.consumeChar(),
|
||||||
_ = this.parseExpression(),
|
_ = this.parseExpression(),
|
||||||
cdr = this.parseExpression();
|
cdr = this.parseExpression();
|
||||||
this.pos = orig_pos; // rewind, like it never happened.
|
this.pos = orig_pos; // rewind, like it never happened.
|
||||||
this.fake = false;
|
return EL.typeOf(cdr) == 'array' && EL.tag(cdr) == 'symbol' && EL.val(cdr) == '.';
|
||||||
return cdr != null && EL.tag(cdr) == 'symbol' && EL.val(cdr) == '.';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
EL.Parser.prototype.parseExpression = function() {
|
EL.Parser.prototype.parseExpression = function() {
|
||||||
var value,
|
var value,
|
||||||
c = this.peek();
|
c = this.peek();
|
||||||
if (c == '(' && this.lookingAtCons()) {
|
if (c == '(' && this.lookingAtCons()) {
|
||||||
// print("CONS(");
|
|
||||||
value = ['cons', this.parseCons()];
|
value = ['cons', this.parseCons()];
|
||||||
}
|
}
|
||||||
else if (c == '(') {
|
else if (c == '(') {
|
||||||
// print("LIST(");
|
|
||||||
var list = this.parseList();
|
var list = this.parseList();
|
||||||
value = (list.length > 0) ? ['list', list] : EL.nil;
|
value = (list.length > 0) ? ['list', list] : EL.nil;
|
||||||
}
|
}
|
||||||
else if (c == ')') {
|
else if (c == ')') {
|
||||||
// print(">>> ) <<<");
|
|
||||||
return this.consumeChar();
|
return this.consumeChar();
|
||||||
}
|
}
|
||||||
else if (c == "'") {
|
else if (c == "'") {
|
||||||
// print("QUOTE");
|
|
||||||
this.consumeChar();
|
this.consumeChar();
|
||||||
value = ['list', [['symbol', 'quote']]];
|
value = ['list', [['symbol', 'quote']]];
|
||||||
value[1].push(this.parseExpression());
|
value[1].push(this.parseExpression());
|
||||||
}
|
}
|
||||||
else if (c == '"') {
|
else if (c == '"') {
|
||||||
// print("STRING");
|
|
||||||
value = ['string', this.parseString()];
|
value = ['string', this.parseString()];
|
||||||
}
|
}
|
||||||
else if (this.lookingAtNumber()) {
|
else if (this.lookingAtNumber()) {
|
||||||
// print("NUMBER");
|
|
||||||
value = ['number', this.parseNumber()];
|
value = ['number', this.parseNumber()];
|
||||||
}
|
}
|
||||||
else if (c) {
|
else if (c) {
|
||||||
// print("SYMBOL");
|
|
||||||
value = ['symbol', this.parseSymbol()];
|
value = ['symbol', this.parseSymbol()];
|
||||||
}
|
}
|
||||||
this.consumeWhitespace();
|
this.consumeWhitespace();
|
||||||
|
|
@ -250,20 +245,24 @@ EL.SymbolTable = function(bindings) {
|
||||||
if (bindings) this.define(bindings);
|
if (bindings) this.define(bindings);
|
||||||
};
|
};
|
||||||
|
|
||||||
EL.SymbolTable.prototype.lookup = function(name) {
|
EL.SymbolTable.prototype.lookupWithScope = function(name) {
|
||||||
var i = this.level,
|
var i = this.level,
|
||||||
symbol;
|
symbol;
|
||||||
while (i >= 0) {
|
while (i >= 0) {
|
||||||
// print('*** looking for ' + name + ' at level ' + i);
|
value = this.symbols[i][name];
|
||||||
symbol = this.symbols[i][name];
|
if (value) {
|
||||||
if (symbol) {
|
return [i, value];
|
||||||
return symbol;
|
|
||||||
}
|
}
|
||||||
--i;
|
--i;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
EL.SymbolTable.prototype.lookup = function(name) {
|
||||||
|
var pair = this.lookupWithScope(name);
|
||||||
|
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.
|
||||||
EL.SymbolTable.prototype.define = function(name, value) {
|
EL.SymbolTable.prototype.define = function(name, value) {
|
||||||
if (value === undefined && EL.typeOf(name) == 'array') {
|
if (value === undefined && EL.typeOf(name) == 'array') {
|
||||||
|
|
@ -274,17 +273,21 @@ EL.SymbolTable.prototype.define = function(name, value) {
|
||||||
while (i < n) {
|
while (i < n) {
|
||||||
var name = bindings[i][0],
|
var name = bindings[i][0],
|
||||||
value = bindings[i][1];
|
value = bindings[i][1];
|
||||||
|
// print('name: ' + name);
|
||||||
|
// print('value: ' + value);
|
||||||
scope[name] = value;
|
scope[name] = value;
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// print('defining ' + name + ' = ' + value + ' at level ' + this.level);
|
|
||||||
this.symbols[this.level][name] = value;
|
this.symbols[this.level][name] = value;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
EL.SymbolTable.prototype.pushScope = function(bindings) {
|
EL.SymbolTable.prototype.pushScope = function(bindings) {
|
||||||
|
// print('>>> pushing scope <<<');
|
||||||
|
// print('>>> level going from ' + this.level + ' to ' + (1+this.level));
|
||||||
|
// print(bindings);
|
||||||
this.symbols[++this.level] = [];
|
this.symbols[++this.level] = [];
|
||||||
if (bindings) this.define(bindings);
|
if (bindings) this.define(bindings);
|
||||||
};
|
};
|
||||||
|
|
@ -293,6 +296,12 @@ EL.SymbolTable.prototype.popScope = function() {
|
||||||
--this.level;
|
--this.level;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
EL.SymbolTable.prototype.set = function(name, value) {
|
||||||
|
var pair = this.lookupWithScope(name),
|
||||||
|
level = pair[0];
|
||||||
|
this.symbols[level][name] = value;
|
||||||
|
};
|
||||||
|
|
||||||
/****************************************************************
|
/****************************************************************
|
||||||
****************************************************************/
|
****************************************************************/
|
||||||
|
|
||||||
|
|
@ -317,16 +326,6 @@ EL.PrimitiveVariables = [
|
||||||
|
|
||||||
// this is bound to the EL.Evaluator object
|
// this is bound to the EL.Evaluator object
|
||||||
EL.PrimitiveFunctions = [
|
EL.PrimitiveFunctions = [
|
||||||
['defvar', {
|
|
||||||
type: 'primitive',
|
|
||||||
name: 'defvar',
|
|
||||||
params: ['symbol', 'value', 'docstring'],
|
|
||||||
body: function(symbol, value, docstring) {
|
|
||||||
this.defineVar(EL.symbolName(symbol), value, docstring);
|
|
||||||
return EL.nil;
|
|
||||||
},
|
|
||||||
docstring: "define a variable in the local scope"
|
|
||||||
}],
|
|
||||||
['+', {
|
['+', {
|
||||||
type: 'primitive',
|
type: 'primitive',
|
||||||
name: '+',
|
name: '+',
|
||||||
|
|
@ -386,7 +385,7 @@ EL.PrimitiveFunctions = [
|
||||||
else print(s);
|
else print(s);
|
||||||
}
|
}
|
||||||
if (tag == 'number' || tag == 'symbol' || tag == 'string') {
|
if (tag == 'number' || tag == 'symbol' || tag == 'string') {
|
||||||
p(El.val(x));
|
p(EL.val(x));
|
||||||
}
|
}
|
||||||
else if (tag == 'function') {
|
else if (tag == 'function') {
|
||||||
var fn = EL.val(x);
|
var fn = EL.val(x);
|
||||||
|
|
@ -481,33 +480,39 @@ EL.Evaluator.prototype.defineVar = function(symbol, value, docstring) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
EL.Evaluator.prototype.setVar = function(symbol, value) {
|
||||||
|
var valueObject = this.lookupVar(symbol);
|
||||||
|
valueObject.value = value;
|
||||||
|
this.variables.set(symbol, valueObject);
|
||||||
|
};
|
||||||
|
|
||||||
EL.Evaluator.prototype.defineFunc = function(symbol, params, body, docstring) {
|
EL.Evaluator.prototype.defineFunc = function(symbol, params, body, docstring) {
|
||||||
this.functions.define(symbol, {
|
this.functions.define(symbol, {
|
||||||
type: 'function',
|
type: 'function',
|
||||||
name: symbol,
|
name: symbol,
|
||||||
params: params,
|
params: EL.val(params),
|
||||||
body: body,
|
body: body,
|
||||||
docstring: docstring || "(undocumented)"
|
docstring: docstring || "(undocumented)"
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
EL.Evaluator.prototype.bind = function(params, args) {
|
|
||||||
var self = this;
|
|
||||||
return ;
|
|
||||||
};
|
|
||||||
|
|
||||||
EL.Evaluator.prototype.call = function(func, args) {
|
EL.Evaluator.prototype.call = function(func, args) {
|
||||||
//var bindings = this.bind(func.params, args);
|
|
||||||
print('calling ' + func.name + ' with args: ' + args);
|
|
||||||
var result;
|
var result;
|
||||||
if (func.type === 'primitive') {
|
if (func.type === 'primitive') {
|
||||||
result = func.body.apply(this, args);
|
result = func.body.apply(this, args);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
this.functions.pushScope();
|
||||||
this.variables.pushScope(func.params.map(function(e, i){
|
this.variables.pushScope(func.params.map(function(e, i){
|
||||||
return [e, args[i]];
|
var name = EL.symbolName(e),
|
||||||
|
value = {
|
||||||
|
type: 'variable',
|
||||||
|
value: this.eval(args[i])
|
||||||
|
};
|
||||||
|
return [name, value];
|
||||||
}));
|
}));
|
||||||
result = this.evalExpressions(func.body);
|
result = this.evalExpressions(func.body);
|
||||||
|
this.functions.popScope();
|
||||||
this.variables.popScope();
|
this.variables.popScope();
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -529,9 +534,59 @@ EL.Evaluator.prototype.eval = function(expr) {
|
||||||
// var cons = expr[1];
|
// var cons = expr[1];
|
||||||
// result = [this.eval(cons[0]), this.eval(cons[1])];
|
// result = [this.eval(cons[0]), this.eval(cons[1])];
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
///////////////////
|
||||||
|
// special forms //
|
||||||
|
///////////////////
|
||||||
|
// (many could be in lisp when there are macros) //
|
||||||
|
|
||||||
else if (EL.isQuote(expr)) {
|
else if (EL.isQuote(expr)) {
|
||||||
result = EL.cdr(EL.val(expr));
|
result = EL.val(EL.val(expr));
|
||||||
}
|
}
|
||||||
|
else if (EL.isDefVar(expr)) {
|
||||||
|
var val = EL.val(expr),
|
||||||
|
name = EL.symbolName(val[1]),
|
||||||
|
value = this.eval(val[2]),
|
||||||
|
docstring = val[3];
|
||||||
|
// TODO check for re-definitions
|
||||||
|
this.defineVar(name, value, docstring);
|
||||||
|
result = EL.nil;
|
||||||
|
}
|
||||||
|
else if (EL.isDefFunc(expr)) {
|
||||||
|
var val = EL.val(expr),
|
||||||
|
name = EL.symbolName(val[1]),
|
||||||
|
params = val[2],
|
||||||
|
docstring = EL.isString(val[3]) && val[3],
|
||||||
|
body = val.slice(docstring ? 4 : 3);
|
||||||
|
// TODO check for re-definitions
|
||||||
|
this.defineFunc(name, params, body, docstring);
|
||||||
|
result = EL.nil;
|
||||||
|
}
|
||||||
|
else if (EL.isSet(expr)) {
|
||||||
|
var val = EL.val(expr),
|
||||||
|
name = EL.symbolName(val[1]),
|
||||||
|
value = this.eval(val[2]);
|
||||||
|
result = this.setVar(name, value);
|
||||||
|
}
|
||||||
|
else if (EL.isSetq(expr)) {
|
||||||
|
var val = EL.val(expr),
|
||||||
|
i = 0;
|
||||||
|
while (EL.isSymbol(val[i+1])) {
|
||||||
|
var name = EL.symbolName(val[i+1]),
|
||||||
|
value = this.eval(val[i+2]);
|
||||||
|
result = this.setVar(name, value);
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (EL.isIf(expr)) {
|
||||||
|
var val = EL.val(expr),
|
||||||
|
condition = this.eval(val[1]),
|
||||||
|
trueBlock = val[2],
|
||||||
|
nilBlock = val[3];
|
||||||
|
result = this.primitiveIf(condition, trueBlock, nilBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
// function application
|
||||||
else if (EL.tag(expr) == 'list') {
|
else if (EL.tag(expr) == 'list') {
|
||||||
var list = expr[1],
|
var list = expr[1],
|
||||||
car = list[0],
|
car = list[0],
|
||||||
|
|
@ -590,28 +645,18 @@ EL.assert = function(condition, message) {
|
||||||
};
|
};
|
||||||
|
|
||||||
EL.tag = function(expr) {
|
EL.tag = function(expr) {
|
||||||
EL.assert(function() { 'tag'; return EL.typeOf(expr) == 'array'; }, expr);
|
EL.assert(function() { var f='tag'; return EL.typeOf(expr) == 'array'; }, expr);
|
||||||
return expr[0];
|
return expr[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
EL.val = function(expr) {
|
EL.val = function(expr) {
|
||||||
EL.assert(function() { 'val'; return EL.typeOf(expr) == 'array'; }, expr);
|
EL.assert(function() { var f='val'; return EL.typeOf(expr) == 'array'; }, expr);
|
||||||
return expr[1];
|
return expr[1];
|
||||||
};
|
};
|
||||||
|
|
||||||
EL.car = function(cons) {
|
|
||||||
EL.assert(function(){ 'car'; return EL.typeOf(cons) == 'array'; }, cons);
|
|
||||||
return cons[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
EL.cdr = function(cons) {
|
|
||||||
EL.assert(function(){ 'cdr'; return EL.typeOf(cons) == 'array'; }, cons);
|
|
||||||
return cons.slice(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
EL.symbolName = function(symbol) {
|
EL.symbolName = function(symbol) {
|
||||||
// EL.Util.pp(symbol);
|
// EL.Util.pp(symbol);
|
||||||
EL.assert(function(){ 'symbolName'; return EL.isSymbol(symbol); }, symbol);
|
EL.assert(function(){ var f='symbolName'; return EL.isSymbol(symbol); }, symbol);
|
||||||
return symbol[1];
|
return symbol[1];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -626,7 +671,6 @@ EL.isString = function(expr) {
|
||||||
EL.isList = function(expr) {
|
EL.isList = function(expr) {
|
||||||
return (EL.tag(expr) == 'list');
|
return (EL.tag(expr) == 'list');
|
||||||
};
|
};
|
||||||
EL.isCons = EL.isList;
|
|
||||||
|
|
||||||
EL.isFunction = function(expr) {
|
EL.isFunction = function(expr) {
|
||||||
return (EL.tag(expr) == 'function');
|
return (EL.tag(expr) == 'function');
|
||||||
|
|
@ -639,7 +683,7 @@ EL.isAtom = function(expr) {
|
||||||
EL.inferType = function(exprs) {
|
EL.inferType = function(exprs) {
|
||||||
var type = 'number',
|
var type = 'number',
|
||||||
initial = 0,
|
initial = 0,
|
||||||
i = exprs.length;
|
i = exprs.length-1;
|
||||||
while(i >= 0) {
|
while(i >= 0) {
|
||||||
if (EL.isString(exprs[i--])) {
|
if (EL.isString(exprs[i--])) {
|
||||||
type = 'string';
|
type = 'string';
|
||||||
|
|
@ -650,13 +694,19 @@ EL.inferType = function(exprs) {
|
||||||
return [type, initial];
|
return [type, initial];
|
||||||
};
|
};
|
||||||
|
|
||||||
EL.isQuote = function(expr) {
|
EL.isSpecialForm = function(name, expr) {
|
||||||
var tag = EL.tag(expr),
|
var tag = EL.tag(expr),
|
||||||
val = EL.val(expr),
|
car = EL.typeOf(expr) == 'array' && EL.val(expr)[0],
|
||||||
car = EL.isCons(val) && EL.car(val),
|
thisName = car && EL.symbolName(car);
|
||||||
name = car && EL.symbolName(car);
|
return (tag == 'list' && thisName == name);
|
||||||
return (tag == 'list' && name == 'quote');
|
|
||||||
};
|
};
|
||||||
|
EL.isQuote = function(expr){return EL.isSpecialForm('quote', expr);};
|
||||||
|
EL.isDefVar = function(expr){return EL.isSpecialForm('defvar', expr);};
|
||||||
|
EL.isDefFunc = function(expr){return EL.isSpecialForm('defun', expr);};
|
||||||
|
EL.isSet = function(expr){return EL.isSpecialForm('set', expr);};
|
||||||
|
EL.isSetq = function(expr){return EL.isSpecialForm('setq', expr);};
|
||||||
|
EL.isCond = function(expr){return EL.isSpecialForm('cond', expr);};
|
||||||
|
EL.isIf = function(expr){return EL.isSpecialForm('if', expr);};
|
||||||
|
|
||||||
EL.eval = function(exprs) {
|
EL.eval = function(exprs) {
|
||||||
var e = new EL.Evaluator();
|
var e = new EL.Evaluator();
|
||||||
|
|
@ -673,10 +723,6 @@ EL.parseOne = function(string) {
|
||||||
};
|
};
|
||||||
EL.read = EL.parseOne;
|
EL.read = EL.parseOne;
|
||||||
|
|
||||||
EL.print = function(expr) {
|
|
||||||
EL.Util.pp(expr);
|
|
||||||
};
|
|
||||||
|
|
||||||
EL.rep = function(string) {
|
EL.rep = function(string) {
|
||||||
EL.print(EL.eval(EL.read(string)));
|
EL.print(EL.eval(EL.read(string)));
|
||||||
};
|
};
|
||||||
|
|
@ -686,8 +732,7 @@ EL.repl = function() {
|
||||||
e = new EL.Evaluator();
|
e = new EL.Evaluator();
|
||||||
while (true) {
|
while (true) {
|
||||||
print("elisp> ");
|
print("elisp> ");
|
||||||
var x = readline();
|
EL.print(e.eval(p.parseOne(readline())));
|
||||||
EL.print(e.eval(p.parseOne(x)));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -784,9 +829,13 @@ EL.Util.pp = function(x, indent, key, noprint) {
|
||||||
printB(space + '"' + x + '"');
|
printB(space + '"' + x + '"');
|
||||||
}
|
}
|
||||||
return buffer;
|
return buffer;
|
||||||
break;
|
|
||||||
|
|
||||||
case 'array':
|
case 'array':
|
||||||
|
// nil special case
|
||||||
|
if (x.length == 2 && EL.tag(x) == 'symbol' && EL.val(x) == 'nil') {
|
||||||
|
return EL.Util.pp(null, indent, key);
|
||||||
|
}
|
||||||
|
|
||||||
if (key) {
|
if (key) {
|
||||||
printB(space + key + ': [');
|
printB(space + key + ': [');
|
||||||
}
|
}
|
||||||
|
|
@ -809,7 +858,6 @@ EL.Util.pp = function(x, indent, key, noprint) {
|
||||||
printB(space + '(null)');
|
printB(space + '(null)');
|
||||||
}
|
}
|
||||||
return buffer;
|
return buffer;
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (key) {
|
if (key) {
|
||||||
|
|
@ -819,12 +867,12 @@ EL.Util.pp = function(x, indent, key, noprint) {
|
||||||
printB(space + x);
|
printB(space + x);
|
||||||
}
|
}
|
||||||
return buffer;
|
return buffer;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
var s = buffer;
|
var s = buffer;
|
||||||
if (!noprint) dumpBuffer();
|
if (!noprint) dumpBuffer();
|
||||||
return s;
|
return s;
|
||||||
};
|
};
|
||||||
|
EL.print = EL.Util.pp;
|
||||||
|
|
||||||
// spidermonkey doesn't like a C-style comment at the end either
|
// spidermonkey doesn't like a C-style comment at the end either
|
||||||
function spidermonkeyissilly(){};
|
spidermonkeykludge = 1;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue