# HG changeset patch # User Michael Pavone # Date 1387656471 28800 # Node ID adad61ea2f3a1b19b12d640c244092649dcb18f8 # Parent 2dac67e9d18bd65509a9f49541c4456635643b00 Switched to a less hacky implementation of hygiene and exposed more AST properties to macros diff -r 2dac67e9d18b -r adad61ea2f3a interp.js --- 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; +}; diff -r 2dac67e9d18b -r adad61ea2f3a parser.js --- 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("")); };' +