view interp.js @ 248:96fdc5b37ceb

Added get:else method to linear dict
author Michael Pavone <pavone@retrodev.com>
date Sun, 30 Mar 2014 18:17:56 -0700
parents dc5f487247ee
children 0ee70ac20a02
line wrap: on
line source

Number.prototype['tpmeth_+'] = function(other) {
	return this + other;
};
Number.prototype['tpmeth_-'] = function(other) {
	return this - other;
};
Number.prototype['tpmeth_*'] = function(other) {
	return this * other;
};
Number.prototype['tpmeth_/'] = function(other) {
	return this / other;
};
Number.prototype['tpmeth_='] = function(other) {
	return this == other ? tptrue : tpfalse;
};
Number.prototype['tpmeth_>'] = function(other) {
	return this > other ? tptrue : tpfalse;
};
Number.prototype['tpmeth_<'] = function(other) {
	return this < other ? tptrue : tpfalse;
};
Number.prototype['tpmeth_>='] = function(other) {
	return this >= other ? tptrue : tpfalse;
};
Number.prototype['tpmeth_<='] = function(other) {
	return this <= other ? tptrue : tpfalse;
};
Number.prototype.tpmeth_asStringChar = function() {
	return String.fromCharCode(this);
};
Number.prototype['tpmeth_isString?'] = function() {
	return false;
};
Number.prototype.tpmeth_string = function() {
	return '' + this;
};
Number.prototype.tpmeth_print = function() {
	print(this);
};

String.prototype['tpmeth_.'] = function(other) {
	return this + other;
};
String.prototype['tpmeth_='] = function(other) {
	return this == other ? tptrue : tpfalse;
};
String.prototype.tpmeth_length = function() {
	return this.length;
};
String.prototype.tpmeth_byte_length = function() {
	return this.length;
};
String.prototype.tpmeth_byte = function(index) {
	return this.charCodeAt(index);
};
String.prototype['tpmeth_from:withLength'] = function(pos, length) {
	return this.substr(pos, length);
};
String.prototype['tpmeth_isString?'] = function() {
	return true;
};
String.prototype.tpmeth_print = function() {
	print(this);
};

Function.prototype['tpmeth_while:do'] = function(body) {
	var ret = null;
	for (;;) {
		var res = this.call(null);
		if (res != tptrue) {
			break;
		}
		ret = body.call(null);
	}
	return ret;
};

var tptrue = null;
var tpfalse = null;
var tplist = null;

function topenv(moduledirs)
{
	this.names = {};
	this.modules = {};
	for (var dirnum in moduledirs) {
		var results = os.system("ls", [moduledirs[dirnum]]).split('\n');
		for (var i in results) {
			var tpidx = results[i].indexOf('.tp')
			if (tpidx > 0 && tpidx == results[i].length - 3) {
				this.names[results[i].substr(0, tpidx)] = moduledirs[dirnum] + "/" + results[i];
			}
		}
	}
	if (!tptrue) {
		tptrue = this.find('true');
		tptrue.valueOf = function() {
			return true;
		};
	}
	if (!tpfalse) {
		tpfalse = this.find('false');
		tpfalse.valueOf = function() {
			return false;
		};
	}
	if (!tplist) {
		tplist = this.find('list');
		if (tplist instanceof Function) {
			tplist = tplist.call(null);
		}
	}
}

topenv.prototype = {
	find: function(name) {
		if (name in this.modules) {
			return this.modules[name];
		}
		if (name in this.names) {
			var parsed = parseFile(this.names[name]);
			var ret = parsed.macroexpand(this).eval(this);
			if (typeof ret == 'function') {
				ret = ret();
			}
			this.modules[name] = ret;
			return ret;
		}
		return null;
	},
	findNoTop: function(name) {
		return null;
	},
	findSetPresent: function(name, val) {
		return false;
	},
	findMacro: function(name) {
		return null;
	},
	findQuoteTrans: function(name) {
		return null;
	}
}

function environment(parent)
{
	this.parent = parent;
	this.syms = {};
	this.macros = {};
	this.quotetrans = {};
}

environment.prototype = {
	find: function(name) {
		if (name in this.syms) {
			return this.syms[name];
		}
		if (this.parent) {
			return this.parent.find(name);
		}
		return null;
	},
	findNoTop: function(name) {
		if (name in this.syms) {
			return this.syms[name];
		}
		if (this.parent) {
			return this.parent.findNoTop(name);
		}
		return null;
	},
	findSet: function(name, val) {
		if (name in this.syms
		    || !this.parent
			|| !this.parent.findSetPresent(name, val)) {
			this.syms[name] = val;
		}
	},
	findSetPresent: function(name, val) {
		if (name in this.syms) {
			this.syms[name] = val;
			return true;
		}
		if (this.parent) {
			return this.parent.findSetPresent(name, val);
		}
		return false;
	},
	findMacro: function(name) {
		if (name in this.syms) {
			if (name in this.macros) {
				return this.syms[name];
			}
			return null;
		}
		if (this.parent) {
			return this.parent.findMacro(name);
		}
		return null;
	},
	findQuoteTrans: function(name) {
		if (name in this.quotetrans) {
			return this.quotetrans[name];
		}
		if (this.parent) {
			return this.parent.findQuoteTrans(name);
		}
		return null;
	},
	defMacro: function(name, def) {
		this.syms[name] = def;
		this.macros[name] = true;
	}
};

function makeASTNode(val)
{
	if (typeof val == 'number') {
		return new intlit(val);
	}
	if (typeof val == 'string') {
		return new strlit(val);
	}
	if (val instanceof Array) {
		return new arraylit(val);
	}
	if (val == tptrue) {
		return new symbol('true');
	}
	if (val == tpfalse) {
		return new symbol('false');
	}
	if ('makeHygienic' in val) {
		return val;
	}
	return null;
}

op.prototype.eval = function(env) {
	var l = this.left.eval(env);
	var name = this.op;
	if (name == '&&' || name == '||') {
		var r = (new lambda([], [this.right])).eval(env);
	} else {
		var r = this.right.eval(env);
	}
	var fun = env.findNoTop(name);
	var ret;
	if (fun) {
		ret = fun(l,r)
	} else {
		if (name == '&&') {
			name = 'if'
		} else if (name == '||') {
			name = 'ifnot'
		} else if (name == '|') {
			return r['tpmeth_|'](l);
		}
		ret = l['tpmeth_'+name](r);
	}
	return ret;
};

op.prototype.macroexpand = function(env) {
	this.left = this.left.macroexpand(env);
	this.right = this.right.macroexpand(env);
	return this;
};

op.prototype.quote = function(env) {
	var left = this.left.quote(env);
	var right = this.right.quote(env);
	return new op(left, this.op, right);
};

op.prototype.makeHygienic = function(env) {
	var left = this.left.makeHygienic(env);
	var right = this.right.makeHygienic(env);
	return new op(left, this.op, right);
};

op.prototype.tpmeth_nodeType = function() {
	return "op";
};

op.prototype.tpmeth_left = function() {
	return this.left;
};

op.prototype.tpmeth_right = function() {
	return this.right;
};

op.prototype.tpmeth_opName = function() {
	return this.op;
};

var quote_prefix = 0;

symbol.prototype.eval = function(env) {
	var res = env.find(this.name);
	if (res === null) {
		throw new Error('Symbol ' + this.name + ' is not bound');
	}
	return res;
};

symbol.prototype.quote = function(env) {
	var val = env.find(this.name);
	if (val !== null) {
		var newnode = makeASTNode(val);
		if (!newnode) {
			throw new Error(this.name + ' contains a value ' + val + ' that could not be converted to an AST node');
		}
		return newnode;
	} else {
		this.dirty = true;
		return this;
	}
};

symbol.prototype.makeHygienic = function(env) {
	if (this.dirty && this.cleanName() != 'self') {
		var hygenic = env.findQuoteTrans(this.cleanName());
		if (hygenic)
		{
			return new symbol(hygenic, this.symbols);
		} else {
			return this;
		}
	} else {
		return this;
	}
};

symbol.prototype.tpmeth_nodeType = function() {
	return "symbol";
};

symbol.prototype.tpmeth_name = function() {
	return this.cleanName();
};
symbol.prototype['tpmeth_name!'] = function(val) {
	this.name = val;
	return this;
};

intlit.prototype.eval =
	floatlit.prototype.eval =
	strlit.prototype.eval =
	arraylit.prototype.eval = function(env) {
	return this.val;
};

listlit.prototype.eval = function(env) {
	var cur = tplist.tpmeth_empty();
	for (var idx = this.val.length - 1; idx >= 0; --idx) {
		cur = tplist['tpmeth_node:withTail'](this.val[idx].eval(env), cur);
	}
	return cur;
};

symbol.prototype.macroexpand =
	intlit.prototype.macroexpand =
	floatlit.prototype.macroexpand =
	strlit.prototype.macroexpand = function(env) {
	return this;
};


arraylit.prototype.macroexpand =
	listlit.prototype.macroexpand = function(env) {
	for (var i = 0; i < this.val.length; i++) {
		this.val[i] = this.val[i].macroexpand(env);
	}
	return this;
};

intlit.prototype.quote =
	floatlit.prototype.quote =
	strlit.prototype.quote =
	arraylit.prototype.quote =
	listlit.prototype.quote = function(env) {
	return this;
};

intlit.prototype.makeHygienic =
	floatlit.prototype.makeHygienic =
	strlit.prototype.makeHygienic =
	arraylit.prototype.makeHygienic =
	listlit.prototype.makeHygienic = function(env) {
	return this;
};

intlit.prototype.tpmeth_nodeType = function() {
	return "intlit";
};

floatlit.prototype.tpmeth_nodeType = function() {
	return "floatlit";
};

strlit.prototype.tpmeth_nodeType = function() {
	return "strlit";
};

arraylit.prototype.tpmeth_nodeType = function() {
	return "arraylit";
};

listlit.prototype.tpmeth_nodeType = function() {
	return "strlit";
};

intlit.prototype.tpmeth_value =
	floatlit.prototype.tpmeth_value =
	strlit.prototype.tpmeth_value =
	arraylit.prototype.tpmeth_value = function() {
	return this.val;
};

listlit.prototype.tpmeth_value = function() {
	var cur = tplist.tpmeth_empty();
	for (var idx = this.val.length - 1; idx >= 0; --idx) {
		cur = tplist['tpmeth_node:withTail'](this.val[idx], cur);
	}
	return cur;
};

funcall.prototype.eval = function(env) {
	var args = [];
	var name = this.name;
	if (name[name.length-1] == ":") {
		name = name.substr(0, name.length-1);
	}
	if (name == 'quote') {
		if (this.receiver) {
			return this.receiver.quote(env).macroexpand(env);
		}
		if (this.args.length) {
			var cur = env;
			return this.args[0].quote(env).macroexpand(env);
		}
		throw new Error('quote takes an argument');
	}
	if (name == 'macro') {
		return null;
	}
	if (this.receiver) {
		args.push(this.receiver.eval(env));
	}
	for (var i = 0; i < this.args.length; i++) {
		args.push(this.args[i].eval(env));
	}
	if (name == 'eval:else') {
		try {
			var res = args[0].eval(env);
		} catch(e) {
			return args[2].call(null);
		}
		return args[1].call(null, res);
	}
	var fun = env.findNoTop(name);
	if (fun && (fun.numargs === undefined || fun.numargs == args.length)) {
		return fun.apply(null, args);
	} else {
		//if (typeof args[0]'tpmeth_'+name in args[0]) {
			try {
				return args[0]['tpmeth_'+name].apply(args[0], args.slice(1));
			} catch(e) {
				var msg = 'Error, \n\t' + e.message.split('\n').join('\n\t') + '\ninvoking method ' + name + ' on object ' + args[0];
				if (typeof args[0] == 'object') {
					msg += ' with keys ' + JSON.stringify(Object.keys(args[0]) + ' and constructor ' + args[0].constructor.name);
				}
				throw new Error(msg);
			}
		/*} else {JSON.stringify
			throw new Error('No method named ' + name + ' on object ' + JSON.stringify(args[0]));
		}*/
	}
};

funcall.prototype.macroexpand = function(env) {
	var name = this.name;
	if (name[name.length-1] == ":") {
		name = name.substr(0, name.length-1);
	}
	if (name == 'quote') {
		return this;
	}
	var macro = env.findMacro(name);
	if (this.receiver) {
		this.receiver = this.receiver.macroexpand(env);
	}
	for (var i = 0; i < this.args.length; i++) {
		this.args[i] = this.args[i].macroexpand(env);
	}
	if (!macro) {
		return this;
	}
	var args = [];
	if (this.receiver) {
		args.push(this.receiver);
	}
	for (var i = 0; i < this.args.length; i++) {
		args.push(this.args[i]);
	}
	var ret = makeASTNode(macro.apply(null, args));
	ret = ret.makeHygienic(env);
	quote_prefix++;
	return ret;
};

funcall.prototype.quote = function(env) {
	var receiver = this.receiver ? this.receiver.quote(env) : null;
	var args = [];
	for (var i = 0; i < this.args.length; i++) {
		args.push(this.args[i].quote(env));
	}
	var name = this.name;
	if (name[name.length-1] == ":") {
		name = name.substr(0, name.length-1);
	}
	var fun = env.find(name);
	if (fun) {
		fun = makeASTNode(fun);
		if (fun instanceof symbol) {
			name = fun.cleanName();
		} else if (fun instanceof lambda) {
			throw new Error('FIXME');
		}
	} else {
		this.dirty = true;
	}
	var ret = new funcall(name, args);
	ret.receiver = receiver;
	return ret;
};

funcall.prototype.makeHygienic = function(env) {
	var receiver = this.receiver ? this.receiver.makeHygienic(env) : null;
	var args = [];
	for (var i = 0; i < this.args.length; i++) {
		args.push(this.args[i].makeHygienic(env));
	}
	var name = this.name;
	if (name[name.length-1] == ":") {
		name = name.substr(0, name.length-1);
	}
	var hygienic = env.findQuoteTrans(name);
	if (hygienic) {
		name = hygienic;
	}
	var ret = new funcall(name, args);
	ret.receiver = receiver;
	return ret;
};

funcall.prototype.tpmeth_nodeType = function() {
	return "funcall";
};

funcall.prototype['tpmeth_args!'] = function(arglist) {
	this.args = [];
	//TODO: Finish this
};

object.prototype.eval = function(parentenv) {
	var env = new environment(parentenv);
	var obj = {env: env};
	for (var i = 0; i < this.messages.length; i++) {
		var msg = this.messages[i];
		if (msg instanceof assignment) {
			if (msg.expression instanceof lambda) {
				obj['tpmeth_' + msg.symbol.name] = msg.expression.eval(env);
				(function(name) {
					env.syms[name] = function() {
						var ret = obj['tpmeth_' + name].apply(obj, arguments);
						return ret;
					};
					env.syms[name].numargs = msg.expression.numArgs();
				})(msg.symbol.name);
			} else {
				var makeProp = function(obj, name) {
					obj['tprop_' + name] = msg.expression.eval(env);
					obj['tpmeth_' + name] = function() {
						return this['tprop_'+name];
					};
					var setname = name+'!';
					obj['tpmeth_' + setname] = function(val) {
						this['tprop_'+name] = val;
						return this;
					};
				};
				makeProp(obj, msg.symbol.name);
			}
		} else {
			throw new Error('pseudo function calls in object definitions not implemented yet');
		}
	}
	return obj;
};

object.prototype.macroexpand = function(parentenv) {
	var env = new environment(parentenv);
	var obj = {env: env};
	var outmessages = [];
	for (var i = 0; i < this.messages.length; i++) {
		var msg = this.messages[i].macroexpand(env);
		if (msg instanceof assignment) {
			if (msg.expression instanceof lambda) {
				(function(name, expr) {
					env.syms[name] = function() {
						if (!obj['tpmeth_' + name]) {
							obj['tpmeth_' + name] = expr.eval(env);
						}
						return obj['tpmeth_' + name].apply(obj, arguments);
					};
					env.syms[name].numargs = expr.numArgs();
				})(msg.symbol.name, msg.expression);
				outmessages.push(msg);
			} else if (msg.expression instanceof funcall && msg.expression.name == 'macro:') {
				env.defMacro(msg.symbol.name, msg.expression.args[0].eval(env));
			} else {
				outmessages.push(msg);
				/*
				var makeProp = function(obj, name) {
					obj['tprop_' + name] = msg.expression.eval(env);
					name = 'tpmeth_' + name;
					obj[name] = function() {
						return this[name];
					};
					var setname = name+'!';
					obj[setname] = function(val) {
						this[setname] = val;
						return this;
					};
				};
				makeProp(obj, msg.symbol.name);*/
			}
		} else {
			outmessages.push(msg);
			//throw new Error('pseudo function calls in object definitions not implemented yet');
		}
	}
	this.messages = outmessages;
	return this;
};

object.prototype.quote = function(parentenv) {
	var env = new environment(parentenv);
	var outmessages = [];
	for (var i = 0; i < this.messages.length; i++) {
		var msg = this.messages[i];
		if (msg instanceof assignment) {
			env.syms[msg.symbol.name] = null;
			if (!(msg.expression instanceof lambda)) {
				env.syms[msg.symbol.name + '!'] = null;
			}
		}
	}
	for (var i = 0; i < this.messages.length; i++) {
		var msg = this.messages[i];
		if (msg instanceof assignment) {
			outmessages.push(new assignment(msg.symbol, msg.expression.quote(env)));
		} else {
			outmessages.push(msg.quote(env));
		}
	}
	return new object(outmessages);
};

object.prototype.makeHygienic = function(parentenv) {
	var env = new environment(parentenv);
	var outmessages = [];
	for (var i = 0; i < this.messages.length; i++) {
		var msg = this.messages[i];
		if (msg instanceof assignment) {
			env.syms[msg.symbol.name] = null;
			//make sure method names don't get translated for hygiene
			env.quotetrans[msg.symbol.name] = null;
			if (!(msg.expression instanceof lambda)) {
				env.syms[msg.symbol.name + '!'] = null;
				env.quotetrans[msg.symbol.name + '!'] = null;
			}
		}
	}
	for (var i = 0; i < this.messages.length; i++) {
		var msg = this.messages[i];
		if (msg instanceof assignment) {
			outmessages.push(new assignment(msg.symbol, msg.expression.makeHygienic(env)));
		} else {
			outmessages.push(msg.makeHygienic(env));
		}
	}
	return new object(outmessages);
};

object.prototype.tpmeth_nodeType = function() {
	return "object";
};

object.prototype.tpmeth_addMessage = function(message) {
	this.messages.push(makeASTNode(message));
	return this;
};

lambda.prototype.eval = function(parentenv) {
	var args = this.args;
	var exprs = this.expressions;
	return function() {
		var env = new environment(parentenv);
		for (var i = 0, j = 0; i < arguments.length && j < args.length; i++, j++) {
			while (j < args.length && args[j].cleanName() == 'self') {
				j++;
			}
			env.syms[args[j].cleanName()] = arguments[i];
		}
		if (this != null) {
			env.syms['self'] = this;
		}
		var res = null;
		for (var i = 0; i < exprs.length; i++) {
			res = exprs[i].eval(env);
		}
		return res;
	};
};

lambda.prototype.macroexpand = function(parentenv) {
	var env = new environment(parentenv);
	for (var i = 0; i < this.args.length; i++) {
		env.syms[this.args[i].cleanName()] = {};
	}
	for (var i = 0; i < this.expressions.length; i++) {
		var expr = this.expressions[i];
		if (expr instanceof assignment) {
			if (expr.expression instanceof funcall && expr.expression.name == 'macro:') {
				env.defMacro(expr.symbol.name, exp.expression.args[0].eval(env));
			} else {
				env.syms[expr.symbol.cleanName()] = {};
				this.expressions[i] = expr.macroexpand(env);
				if (this.expressions[i].expression instanceof lambda) {
					env.syms[expr.symbol.cleanName()] = this.expressions[i].expression.eval(env);
				} else {
					env.syms[expr.symbol.cleanName()] = null;
				}
			}
		} else {
			this.expressions[i] = expr.macroexpand(env);
		}
	}
	return this;
};

lambda.prototype.quote = function(parentenv) {
	var args = [];
	var expressions = [];
	var env = new environment(parentenv);
	for (var i = 0; i < this.args.length; i++) {
		env.syms[this.args[i].cleanName()] = null;
		var sym = new symbol(this.args[i].name, this.args[i].symbols);
		sym.dirty = true;
		args.push(sym);
	}
	for (var i = 0; i < this.expressions.length; i++) {
		expressions.push(this.expressions[i].quote(env));
	}
	return new lambda(args, expressions);
};

lambda.prototype.makeHygienic = function(parentenv) {
	var args = [];
	var expressions = [];
	var env = new environment(parentenv);
	for (var i = 0; i < this.args.length; i++) {
		env.syms[this.args[i].cleanName()] = null;
		if (this.args[i].dirty && this.args[i].cleanName() != 'self') {
			var hygienic = '' + quote_prefix + this.args[i].cleanName();
			env.quotetrans[this.args[i].cleanName()] = hygienic;
			args.push(new symbol(hygienic, this.args[i].symbols));
		} else {
			args.push(this.args[i]);
		}
	}
	for (var i = 0; i < this.expressions.length; i++) {
		expressions.push(this.expressions[i].makeHygienic(env));
	}
	return new lambda(args, expressions);
};

lambda.prototype.numArgs = function() {
	var num = this.args.length;
	if (num && (this.args[0].cleanName() == 'self')) {
		--num;
	}
	return num;
};

lambda.prototype.tpmeth_nodeType = function() {
	return "lambda";
};

lambda.prototype.tpmeth_args = function() {
	var cur = tplist.tpmeth_empty();
	for (var idx = this.args.length - 1; idx >= 0; --idx) {
		cur = tplist['tpmeth_node:withTail'](this.args[idx], cur);
	}
	return cur;
};

lambda.prototype.tpmeth_expressions = function() {
	var cur = tplist.tpmeth_empty();
	for (var idx = this.expressions.length - 1; idx >= 0; --idx) {
		cur = tplist['tpmeth_node:withTail'](this.expressions[idx], cur);
	}
	return cur;
};

lambda.prototype['tpmeth_expressions!'] = function(exprs) {
	var expressions = [];
	while (!exprs['tpmeth_empty?']().valueOf()) {
		expressions.push(makeASTNode(exprs.tpmeth_value()));
		exprs = exprs.tpmeth_tail();
	}
	this.expressions = expressions;
	return this;
};

lambda.prototype.tpmeth_addExpression = function(expr) {
	this.expressions.push(makeASTNode(expr));
	return this;
};

assignment.prototype.eval = function(env) {
	var val = this.expression.eval(env);
	env.findSet(this.symbol.name, val);
	return val;
};

assignment.prototype.macroexpand = function(env) {
	this.expression = this.expression.macroexpand(env);
	return this;
};

assignment.prototype.quote = function(env) {
	var name = this.symbol.cleanName();
	var val = env.find(name);
	var dirty = true;
	if (val) {
		var node = makeASTNode(val);
		if (!(node instanceof symbol)) {
			throw new Error('Left hand side of assignment expression must be a symbol not a '+ node.constructor.name + ' '+(typeof node) + ', old name was ' + name);
		}
		name = node.cleanName();
		dirty = node.dirty;
	}
	env.syms[name] = null;
	var sym = new symbol(name, this.symbol.symbols);
	sym.dirty = dirty;
	return new assignment(sym, this.expression.quote(env));
};

assignment.prototype.makeHygienic = function(env) {
	var name = this.symbol.cleanName();
	env.syms[name] = null;
	if (this.symbol.dirty) {
		name = env.quotetrans[name] = '' + quote_prefix + name;
	}
	return new assignment(new symbol(name, this.symbol.symbols), this.expression.makeHygienic(env));
};

assignment.prototype.tpmeth_nodeType = function() {
	return "assignment";
};

assignment.prototype.tpmeth_symbol = function() {
	return this.symbol;
};

assignment.prototype['tpmeth_symbol!'] = function(val) {
	this.symbol = makeASTNode(val);
	return this;
};

assignment.prototype.tpmeth_expression = function() {
	return this.expression;
};

assignment.prototype['tpmeth_expression!'] = function(val) {
	this.expression = makeASTNode(val);
	return this;
};