view interp.js @ 251:2557ce4e671f

Fix a couple of compiler bugs. topenv was getting initialized in multiple places. This resulted in multiple copies of modules getting created which caused problems for macro expansion. Additionally, arguments were not being marked as declared during code generation so assigning to an argument that was not closed over generated invalid C code.
author Michael Pavone <pavone@retrodev.com>
date Fri, 11 Apr 2014 22:29:32 -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;
};