changeset 207:60eff5f81d9a

Basic implementation of macros is now working
author Mike Pavone <pavone@retrodev.com>
date Tue, 19 Nov 2013 22:02:11 -0800
parents b4a9d4e405c5
children a1b4a2bc8d72
files compiler.js interp.js tpc.js tpi.js
diffstat 4 files changed, 188 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/compiler.js	Wed Oct 23 19:10:03 2013 -0700
+++ b/compiler.js	Tue Nov 19 22:02:11 2013 -0800
@@ -13,7 +13,7 @@
 
 modulefile.prototype.populateSymbols = function (toplevel) {
 	if (!this.ast) {
-		this.ast = parseFile(this.path + '/' + this.file);
+		this.ast = parseFile(this.path + '/' + this.file).macroexpand(new topenv(toplevel.moduledirs));
 		this.ast.populateSymbols(toplevel);
 	}
 };
@@ -22,6 +22,7 @@
 	if (!this.ast) {
 		var self = this;
 		get(this.path + '/' + this.file, function(data) {
+      //TODO: macro expand in the async case
 			self.ast = parser.parse(data.responseText);
 			self.ast.populateSymbols(toplevel);
 			whenDone();
@@ -48,6 +49,7 @@
 var toplevel = new topsymbols([]);
 function topsymbols(moduledirs)
 {
+  this.moduledirs = moduledirs;
 	this.names = null;
 	this.used = {};
 	this.nextmodulenum = 0;
--- a/interp.js	Wed Oct 23 19:10:03 2013 -0700
+++ b/interp.js	Tue Nov 19 22:02:11 2013 -0800
@@ -79,7 +79,7 @@
 		}
 		if (name in this.names) {
 			var parsed = parseFile(this.names[name]);
-			this.modules[name] = parsed.eval(this);
+			this.modules[name] = parsed.macroexpand(this).eval(this);
 			return this.modules[name];
 		}
 		return null;
@@ -89,13 +89,17 @@
 	},
 	findSetPresent: function(name, val) {
 		return false;
-	}
+	},
+	findMacro: function(name) {
+		return null;
+	},
 }
 
 function environment(parent)
 {
 	this.parent = parent;
 	this.syms = {};
+	this.macros = {};
 }
 
 environment.prototype = {
@@ -133,9 +137,45 @@
 			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;
+	},
+	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 stringlit(val);
+	}
+	if (val instanceof Array) {
+		return new arraylit(val);
+	}
+	if (val == tptrue) {
+		return new symbol('true');
+	}
+	if (val == tpfalse) {
+		return new symbol('false');
+	}
+	return val;
+}
+
 op.prototype.eval = function(env) {
 	var l = this.left.eval(env);
 	var r = this.right.eval(env);
@@ -150,6 +190,12 @@
 	return ret;
 };
 
+op.prototype.macroexpand = function(env) {
+	this.left = this.left.macroexpand(env);
+	this.right = this.right.macroexpand(env);
+	return this;
+};
+
 symbol.prototype.eval = function(env) {
 	return env.find(this.name);
 };
@@ -161,26 +207,88 @@
 	return this.val;
 };
 
+symbol.prototype.macroexpand =
+	intlit.prototype.macroexpand =
+	floatlit.prototype.macroexpand =
+	strlit.prototype.macroexpand =
+	arraylit.prototype.macroexpand =
+	listlit.prototype.macroexpand = function(env) {
+	return this;
+};
+
 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;
+		}
+		if (this.args.length) {
+			return this.args[0];
+		}
+		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) {
 		return fun.apply(null, args);
 	} else {
-		return args[0]['tpmeth_'+name].apply(args[0], args.slice(1));
+		//if (typeof args[0]'tpmeth_'+name in args[0]) {
+			//try {
+				return args[0]['tpmeth_'+name].apply(args[0], args.slice(1));
+			/*} catch(e) {
+				throw new Error('Error, \n\t' + e.message.split('\n').join('\n\t') + '\ninvoking method ' + name + ' on object ' + args[0] + ' ' + JSON.stringify(Object.keys(args[0])));
+			}*/
+		/*} 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);
+	}
+	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]);
+	}
+	return makeASTNode(macro.apply(null, args));
+};
+
 object.prototype.eval = function(parentenv) {
 	var env = new environment(parentenv);
 	var obj = {env: env};
@@ -216,6 +324,51 @@
 	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);
+					};
+				})(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;
+};
+
 lambda.prototype.eval = function(parentenv) {
 	var args = this.args;
 	var exprs = this.expressions;
@@ -238,8 +391,34 @@
 	};
 };
 
+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);
+			}
+		} else {
+			this.expressions[i] = expr.macroexpand(env);
+		}
+	}
+	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;
+};
--- a/tpc.js	Wed Oct 23 19:10:03 2013 -0700
+++ b/tpc.js	Tue Nov 19 22:02:11 2013 -0800
@@ -92,6 +92,7 @@
 	PEG = module.exports;
 	load(basedir + 'parser.js');
 	load(basedir + 'compiler.js');
+  load(basedir + 'interp.js');
 	if (backend == 'C') {
 		load(basedir + 'cbackend.js');
 	} else if (backend == 'IL') {
@@ -104,6 +105,7 @@
 	if (debugmode) {
 		debugprint = print;
 	}
+  parsed = parsed.macroexpand(new topenv(includes));
 	toplevel = new topsymbols(includes);
 	switch(backend)
 	{
--- a/tpi.js	Wed Oct 23 19:10:03 2013 -0700
+++ b/tpi.js	Tue Nov 19 22:02:11 2013 -0800
@@ -98,6 +98,7 @@
 
 	toplevel = new topenv(includes);
 
+	parsed = parsed.macroexpand(toplevel);
 	var mainModule = parsed.eval(toplevel);
 	return mainModule.tpmeth_main();
 }