changeset 206:b4a9d4e405c5

Implemented a simple interpreter to be used for macro expansion and a driver for testing it
author Mike Pavone <pavone@retrodev.com>
date Wed, 23 Oct 2013 19:10:03 -0700
parents 6fe9343b1400
children 60eff5f81d9a
files interp.js tpi.js
diffstat 2 files changed, 348 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interp.js	Wed Oct 23 19:10:03 2013 -0700
@@ -0,0 +1,245 @@
+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_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_print = function() {
+	print(this);
+}
+
+var tptrue = null;
+var tpfalse = 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;
+		};
+	}
+}
+
+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]);
+			this.modules[name] = parsed.eval(this);
+			return this.modules[name];
+		}
+		return null;
+	},
+	findNoTop: function(name) {
+		return null;
+	},
+	findSetPresent: function(name, val) {
+		return false;
+	}
+}
+
+function environment(parent)
+{
+	this.parent = parent;
+	this.syms = {};
+}
+
+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;
+	}
+};
+
+op.prototype.eval = function(env) {
+	var l = this.left.eval(env);
+	var r = this.right.eval(env);
+	var name = this.op;
+	var fun = env.findNoTop(name);
+	var ret;
+	if (fun) {
+		ret = fun(l,r)
+	} else {
+		ret = l['tpmeth_'+name](r);
+	}
+	return ret;
+};
+
+symbol.prototype.eval = function(env) {
+	return env.find(this.name);
+};
+
+intlit.prototype.eval =
+	floatlit.prototype.eval =
+	strlit.prototype.eval =
+	arraylit.prototype.eval = function(env) {
+	return this.val;
+};
+
+funcall.prototype.eval = function(env) {
+	var args = [];
+	var name = this.name;
+	if (name[name.length-1] == ":") {
+		name = name.substr(0, name.length-1);
+	}
+	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));
+	}
+	var fun = env.findNoTop(name);
+	if (fun) {
+		return fun.apply(null, args);
+	} else {
+		return args[0]['tpmeth_'+name].apply(args[0], args.slice(1));
+	}
+};
+
+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() {
+						return obj['tpmeth_' + name].apply(obj, arguments);
+					};
+				})(msg.symbol.name);
+			} else {
+				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 {
+			throw new Error('pseudo function calls in object definitions not implemented yet');
+		}
+	}
+	return obj;
+};
+
+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 && (args.length == 0 || args[0].cleanName() != 'self')) {
+			env.syms['self'] = this;
+		}
+		var res = null;
+		for (var i = 0; i < exprs.length; i++) {
+			res = exprs[i].eval(env);
+		}
+		return res;
+	};
+};
+
+assignment.prototype.eval = function(env) {
+	var val = this.expression.eval(env);
+	env.findSet(this.symbol.name, val);
+	return val;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tpi.js	Wed Oct 23 19:10:03 2013 -0700
@@ -0,0 +1,103 @@
+var module = {exports: {}};
+var console = {log: function(text){}};
+var PEG;
+
+var file = null;
+var argtype = 'normal';
+var includes = ['.'];
+var basedir = '';
+var debugmode = false;
+for (var i = 0; i < arguments.length; i++) {
+	switch (argtype) {
+	case 'normal':
+		switch (arguments[i]) {
+		case '-basedir':
+		case '-i':
+		case '-interpdebug':
+			debugmode = true;
+			break;
+		default:
+			if (arguments[i].charAt(0) == '-') {
+				print("unrecognized switch", arguments[i]);
+				quit(1);
+			}
+			file = arguments[i];
+		}
+		break;
+	case '-basedir':
+		if (basedir == '') {
+			basedir = arguments[i];
+			argtype = 'normal';
+		} else {
+			print("only one -basedir option allowed");
+			quit(1);
+		}
+		break;
+	case '-i':
+		includes.push(arguments[i]);
+		argtype = 'normal';
+		break;
+	case '-backend':
+		backend = arguments[i];
+		argtype = 'normal';
+		break;
+	}
+}
+if (argtype != 'normal') {
+	print("switch", argtype, "expects a parameter");
+	quit(1);
+}
+
+if (!file) {
+	print('usage: d8 tpi.js -- filename');
+	quit(1);
+}
+includes.push(basedir + 'modules');
+interpFile(file, basedir, includes, debugmode);
+
+
+function parseFile(filename)
+{
+	debugprint('//parsing', filename);
+	var text = read(filename);
+	try {
+		var parsed = parser.parse(text);
+	} catch (error) {
+		print('SyntaxError on at', error.line, ',', error.column, ':', error.message);
+		var lines = text.split('\n');
+		print(lines[error.line-1]);
+		var spacer = '';
+		for (var i = 1; i < error.column; i++) {
+			if (lines[error.line-1].charAt(i-1) == '\t') {
+				spacer += '    ';
+			} else {
+				spacer += ' ';
+			}
+		}
+		print(spacer + '^');
+		quit(1);
+	}
+	return parsed;
+}
+
+
+function interpFile(filename, basedir, includes, debugmode)
+{
+	load(basedir + 'mquery.js');
+	load(basedir + 'peg.js');
+	PEG = module.exports;
+	load(basedir + 'parser.js');
+	load(basedir + 'interp.js');
+
+	if (debugmode) {
+		debugprint = print;
+	} else {
+		debugprint = function() {};
+	}
+	var parsed = parseFile(filename);
+
+	toplevel = new topenv(includes);
+
+	var mainModule = parsed.eval(toplevel);
+	return mainModule.tpmeth_main();
+}