changeset 217:adad61ea2f3a

Switched to a less hacky implementation of hygiene and exposed more AST properties to macros
author Michael Pavone <pavone@retrodev.com>
date Sat, 21 Dec 2013 12:07:51 -0800
parents 2dac67e9d18b
children b799192e404b
files interp.js parser.js
diffstat 2 files changed, 321 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- a/interp.js	Sat Dec 21 12:07:00 2013 -0800
+++ b/interp.js	Sat Dec 21 12:07:51 2013 -0800
@@ -119,8 +119,12 @@
 		}
 		if (name in this.names) {
 			var parsed = parseFile(this.names[name]);
-			this.modules[name] = parsed.macroexpand(this).eval(this);
-			return this.modules[name];
+			var ret = parsed.macroexpand(this).eval(this);
+			if (typeof ret == 'function') {
+				ret = ret();
+			}
+			this.modules[name] = ret;
+			return ret;
 		}
 		return null;
 	},
@@ -246,6 +250,8 @@
 			name = 'if'
 		} else if (name == '||') {
 			name = 'ifnot'
+		} else if (name == '|') {
+			return r['tpmeth_|'](l);
 		}
 		ret = l['tpmeth_'+name](r);
 	}
@@ -264,6 +270,12 @@
 	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";
 };
@@ -294,17 +306,24 @@
 	var val = env.find(this.name);
 	if (val) {
 		var newnode = makeASTNode(val);
-		if (!(newnode instanceof symbol)) {
-			if ('quote' in newnode) {
-				newnode = newnode.quote(env);
-			} else {
-				throw new Error('Symbol ' + this.name + ' is not bound to a valid AST value, instead it is bound to an object with keys ' + JSON.stringify(Object.keys(newnode)));
-			}
-		}
 		return newnode;
 	} else {
-		var hygenic = env.findQuoteTrans(this.name);
-		return hygenic ? new symbol(hygenic, this.symbols) : this;
+		this.dirty = true;
+		return this;
+	}
+};
+
+symbol.prototype.makeHygienic = function(env) {
+	if (this.dirty) {
+		var hygenic = env.findQuoteTrans(this.cleanName());
+		if (hygenic)
+		{
+			return new symbol(hygenic, this.symbols);
+		} else {
+			return this;
+		}
+	} else {
+		return this;
 	}
 };
 
@@ -315,6 +334,10 @@
 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 =
@@ -323,6 +346,14 @@
 	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 =
@@ -347,6 +378,14 @@
 	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";
 };
@@ -457,7 +496,10 @@
 	for (var i = 0; i < this.args.length; i++) {
 		args.push(this.args[i]);
 	}
-	return makeASTNode(macro.apply(null, args));
+	var ret = makeASTNode(macro.apply(null, args));
+	ret = ret.makeHygienic(env);
+	quote_prefix++;
+	return ret;
 };
 
 funcall.prototype.quote = function(env) {
@@ -479,10 +521,26 @@
 			throw new Error('FIXME');
 		}
 	} else {
-		var hygenic = env.findQuoteTrans(this.name);
-		if (hygenic) {
-			name = hygenic;
-		}
+		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;
@@ -493,6 +551,11 @@
 	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};
@@ -581,12 +644,9 @@
 	for (var i = 0; i < this.messages.length; i++) {
 		var msg = this.messages[i];
 		if (msg instanceof assignment) {
-			//Make sure method names don't get renamed for hygene
 			env.syms[msg.symbol.name] = null;
-			env.quotetrans[msg.symbol.name] = msg.symbol.name;
-			if (msg.expression instanceof lambda) {
+			if (!(msg.expression instanceof lambda)) {
 				env.syms[msg.symbol.name + '!'] = null;
-				env.quotetrans[msg.symbol.name + '!'] = msg.symbol.name + '!';
 			}
 		}
 	}
@@ -601,10 +661,41 @@
 	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;
@@ -654,9 +745,9 @@
 	var env = new environment(parentenv);
 	for (var i = 0; i < this.args.length; i++) {
 		env.syms[this.args[i].cleanName()] = null;
-		var hygenic = '' + quote_prefix + this.args[i].cleanName();
-		env.quotetrans[this.args[i].cleanName()] = hygenic;
-		args.push(new symbol(hygenic, this.args[i].symbols));
+		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));
@@ -664,6 +755,26 @@
 	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) {
+			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')) {
@@ -676,6 +787,37 @@
 	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);
@@ -689,12 +831,49 @@
 
 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!');
+		}
+		name = node.cleanName();
+		dirty = node.dirty;
+	}
 	env.syms[name] = null;
-	var hygenic = '' + quote_prefix + name;
-	env.quotetrans[name] = hygenic;
-	return new assignment(new symbol(hygenic, this.symbol.symbols), this.expression.quote(env));
+	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;
+};
--- a/parser.js	Sat Dec 21 12:07:00 2013 -0800
+++ b/parser.js	Sat Dec 21 12:07:51 2013 -0800
@@ -2,18 +2,28 @@
 function op(left, op, right)
 {
 	this.left = left;
+	if (op instanceof Array) {
+		op = op[0];
+	}
 	this.op = op;
 	this.right = right;
 }
+op.prototype.valueOf = function(indent) {
+	return this.left.valueOf(indent) + ' ' + this.op.valueOf(indent) + ' ' + this.right.valueOf(indent);
+};
 
 function symbol(name, symbols)
 {
 	this.name = name;
 	this.symbols = symbols;
+	this.dirty = false;
 }
 symbol.prototype.cleanName = function() {
 	return this.name[0] == ':' ? this.name.substr(1) : this.name;
 }
+symbol.prototype.valueOf = function() {
+	return this.name;
+};
 
 function intlit(val, bits, unsigned)
 {
@@ -27,51 +37,156 @@
 	this.bits = bits;
 	this.val = val;
 }
+intlit.prototype.valueOf = function() {
+	var val = '' + this.val;
+	if (this.bits != 32 || this.unsigned) {
+		val += this.unsigned ? 'u' : 'i'
+		val += this.bits;
+	}
+	return val;
+};
 
 function floatlit(val)
 {
 	this.val = val;
 }
+floatlit.prototype.valueOf = function() {
+	return '' + val;
+};
 
 function strlit(val)
 {
 	this.val = val;
 }
+strlit.prototype.valueOf = function() {
+	return '"'+this.val+'"';
+};
 
 function listlit(val)
 {
 	this.val = val;
 }
+listlit.prototype.valueOf = function(indent) {
+	if (indent === undefined) {
+		indent = '';
+	}
+	var nextindent = indent + '\t';
+	var val = '['
+	for (var i = 0; i < this.val.length; i++) {
+		val += '\n' + nextindent + this.val[i].valueOf(nextindent);
+	}
+	if (this.val.length) {
+		val += '\n' + indent;
+	}
+	val += ']';
+	return val;
+};
 
 function arraylit(val)
 {
 	this.val = val;
 }
+arraylit.prototype.valueOf = function(indent) {
+	if (indent === undefined) {
+		indent = '';
+	}
+	var nextindent = indent + '\t';
+	var val = '#['
+	for (var i = 0; i < this.val.length; i++) {
+		val += '\n' + nextindent + this.val[i].valueOf(nextindent);
+	}
+	if (this.val.length) {
+		val += '\n' + indent;
+	}
+	val += ']';
+	return val;
+};
 
 function funcall(name, args)
 {
 	this.name = name;
 	this.args = args;
 	this.receiver = null;
+	this.dirty = false;
 }
+funcall.prototype.valueOf = function(indent) {
+	var parts = this.name.split(':');
+	var val = '';
+	if (this.receiver) {
+		val += this.receiver.valueOf(indent);
+	}
+	var curarg = 0;
+	for (var i = 0; i < parts.length; i++) {
+		if (val) {
+			val += ' ';
+		}
+		if (parts[i]) {
+			val += parts[i] + ': ';
+		}
+		if (curarg < this.args.length) {
+			val += this.args[curarg++].valueOf(indent);
+		}
+	}
+	while (curarg < this.args.length) {
+		val += ' ' + this.args[curarg++].valueOf(indent);
+	}
+	return val;
+};
 
 function object(messages)
 {
 	this.messages = messages;
 	this.name = null;
 }
+object.prototype.valueOf = function(indent) {
+	if (indent === undefined) {
+		indent = '';
+	}
+	var nextindent = indent + '\t';
+	var val = '#{';
+	for (var i = 0; i < this.messages.length; i++) {
+		val += '\n' + nextindent +  this.messages[i].valueOf(nextindent);
+	}
+	if (val.length > 2) {
+		val += '\n' + indent;
+	}
+	val += '}';
+	return val;
+};
 
 function lambda(args, expressions)
 {
 	this.args = args ? args : [];
 	this.expressions = expressions;
 }
+lambda.prototype.valueOf = function(indent) {
+	if (indent === undefined) {
+		indent = '';
+	}
+	var nextindent = indent + '\t';
+	var val = '';
+	for (var i = 0; i < this.args.length; i++) {
+		val += this.args[i].valueOf(indent) + ' ';
+	}
+	val += '{';
+	for (var i = 0; i < this.expressions.length; i++) {
+		val += '\n' + nextindent + this.expressions[i].valueOf(nextindent);
+	}
+	if (this.expressions.length) {
+		val += '\n' + indent;
+	}
+	val += '}';
+	return val;
+};
 
 function assignment(sym, expr)
 {
 	this.symbol = sym;
 	this.expression = expr;
 }
+assignment.prototype.valueOf = function(indent) {
+	return this.symbol.valueOf(indent) + ' <- ' + this.expression.valueOf(indent);
+};
 
 function isLambda(node)
 {
@@ -87,7 +202,7 @@
 'compareop = left:maybecons pieces:(hws ("<=" / ">=" / "<" / ">" / "=" / "!=") hws maybecons)* { if (pieces.length) { var cur = new op(left, pieces[0][1], pieces[0][3]); for (var i = 1; i < pieces.length; i++) { cur = new op(cur, pieces[i][1], pieces[i][3]); } return cur; } else { return left; } };'+
 'maybecons = consop / addsub;' +
 'consop = left:addsub hws "|" hws right:maybecons { return new op(left, "|", right); };'+
-'addsub = left:muldiv pieces:(hws ("+"/"-"/"xor"/"and"/"or"/".") hws muldiv)* { if (pieces.length) { var cur = new op(left, pieces[0][1], pieces[0][3]); for (var i = 1; i < pieces.length; i++) { cur = new op(cur, pieces[i][1], pieces[i][3]); } return cur; } else { return left; } };'+
+'addsub = left:muldiv pieces:(hws ((("xor"/"and"/"or") ! [a-zA-Z_!?@0-9])/("+"/"-"/".")) hws muldiv)* { if (pieces.length) { var cur = new op(left, pieces[0][1], pieces[0][3]); for (var i = 1; i < pieces.length; i++) { cur = new op(cur, pieces[i][1], pieces[i][3]); } return cur; } else { return left; } };'+
 'muldiv = left:primlitsym pieces:(hws ("*"/"/"/"%") hws primlitsym)* { if (pieces.length) { var cur = new op(left, pieces[0][1], pieces[0][3]); for (var i = 1; i < pieces.length; i++) { cur = new op(cur, pieces[i][1], pieces[i][3]); } return cur; } else { return left; } };'+
 'primlitsym = hws val:(float / hex / binary / int / string / symbol / object / array / list / lambda / "(" ws expr:expr hws ")" { return expr; }) { return val; };' +
 'symbol = chars:[a-zA-Z_!?@]+ trailing:(":"? [a-zA-Z_!?@0-9])* ! ":" { for (var i = 0; i < trailing.length; i++) { trailing[i] = trailing[i].join(""); } return new symbol(chars.join("") + trailing.join("")); };' +