From 210d673ef0dea8fe62602428f2a9a255ce0591b2 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 8 Feb 2010 22:55:56 -0500 Subject: [PATCH] CoffeeScript-in-CoffeeScript is compiling function calls --- lib/coffee_script/nodes.js | 92 ++++++++++++++++++++++++++++++++++++- lib/coffee_script/parser.js | 14 +++--- src/nodes.coffee | 60 +++++++++++++++++++++++- src/parser.coffee | 14 +++--- 4 files changed, 164 insertions(+), 16 deletions(-) diff --git a/lib/coffee_script/nodes.js b/lib/coffee_script/nodes.js index e805953a..6ac79ad4 100644 --- a/lib/coffee_script/nodes.js +++ b/lib/coffee_script/nodes.js @@ -1,5 +1,5 @@ (function(){ - var CommentNode, Expressions, LiteralNode, Node, ReturnNode, TAB, TRAILING_WHITESPACE, ValueNode, compact, del, dup, flatten, inherit, merge, statement; + var CallNode, CommentNode, Expressions, LiteralNode, Node, ReturnNode, TAB, TRAILING_WHITESPACE, ValueNode, any, compact, del, dup, flatten, inherit, merge, statement; var __hasProp = Object.prototype.hasOwnProperty; process.mixin(require('./scope')); // The abstract base class for all CoffeeScript nodes. @@ -270,6 +270,21 @@ }).call(this); return dest; }; + // Do any of the elements in the list pass a truth test? + any = function any(list, test) { + var __a, __b, __c, item, result; + result = (function() { + __a = []; __b = list; + for (__c = 0; __c < __b.length; __c++) { + item = __b[__c]; + if (test(item)) { + __a.push(true); + } + } + return __a; + }).call(this); + return !!result.length; + }; // Delete a key from an object, returning the value. del = function del(obj, key) { var val; @@ -612,4 +627,79 @@ } })); statement(CommentNode); + // Node for a function invocation. Takes care of converting super() calls into + // calls against the prototype's function of the same name. + CallNode = (exports.CallNode = inherit(Node, { + constructor: function constructor(variable, args) { + this.variable = variable; + this.args = args || []; + this.children = flatten([this.variable, this.args]); + this.prefix = ''; + return this; + }, + new_instance: function new_instance() { + this.prefix = 'new '; + return this; + }, + push: function push(arg) { + this.args.push(arg); + return this.children.push(arg); + }, + // Compile a vanilla function call. + compile_node: function compile_node(o) { + var __a, __b, __c, arg, args; + if (any(this.args, function(a) { + return a instanceof SplatNode; + })) { + return this.compile_splat(o); + } + args = ((function() { + __a = []; __b = this.args; + for (__c = 0; __c < __b.length; __c++) { + arg = __b[__c]; + __a.push(arg.compile(o)); + } + return __a; + }).call(this)).join(', '); + if (this.variable === 'super') { + return this.compile_super(args, o); + } + return this.prefix + this.variable.compile(o) + '(' + args + ')'; + }, + // Compile a call against the superclass's implementation of the current function. + compile_super: function compile_super(args, o) { + var arg_part, meth, methname; + methname = o.scope.method.name; + arg_part = args.length ? ', ' + args : ''; + meth = o.scope.method.proto ? o.scope.method.proto + '.__superClass__.' + methname : methname + '.__superClass__.constructor'; + return meth + '.call(this' + arg_part + ')'; + }, + // Compile a function call being passed variable arguments. + compile_splat: function compile_splat(o) { + var __a, __b, arg, args, code, i, meth, obj; + meth = this.variable.compile(o); + obj = this.variable.source || 'this'; + args = (function() { + __a = []; __b = this.args; + for (i = 0; i < __b.length; i++) { + arg = __b[i]; + __a.push((function() { + code = arg.compile(o); + code = arg instanceof SplatNode ? code : '[' + code + ']'; + return i === 0 ? code : '.concat(' + code + ')'; + }).call(this)); + } + return __a; + }).call(this); + return this.prefix + meth + '.apply(' + obj + ', ' + args.join('') + ')'; + }, + // If the code generation wished to use the result of a function call + // in multiple places, ensure that the function is only ever called once. + compile_reference: function compile_reference(o) { + var call, reference; + reference = o.scope.free_variable(); + call = new ParentheticalNode(new AssignNode(reference, this)); + return [call, reference]; + } + })); })(); \ No newline at end of file diff --git a/lib/coffee_script/parser.js b/lib/coffee_script/parser.js index 842c0f0d..03848cfd 100644 --- a/lib/coffee_script/parser.js +++ b/lib/coffee_script/parser.js @@ -81,7 +81,7 @@ ], // Assignment within an object literal (can be quoted). AssignObj: [o("IDENTIFIER ASSIGN Expression", function() { - return new AssignNode(new ValueNode(yytext), $3, 'object'); + return new AssignNode(new ValueNode(new LiteralNode(yytext)), $3, 'object'); }), o("STRING ASSIGN Expression", function() { return new AssignNode(new ValueNode(new LiteralNode(yytext)), $3, 'object'); }), o("NUMBER ASSIGN Expression", function() { @@ -189,7 +189,7 @@ ], // A Parameter (or ParamSplat) in a function definition. Param: [o("PARAM", function() { - return yytext; + return new LiteralNode(yytext); }), o("PARAM . . .", function() { return new SplatNode(yytext); }) @@ -201,7 +201,7 @@ ], // Expressions that can be treated as values. Value: [o("IDENTIFIER", function() { - return new ValueNode(yytext); + return new ValueNode(new LiteralNode(yytext)); }), o("Literal", function() { return new ValueNode($1); }), o("Array", function() { @@ -220,11 +220,11 @@ ], // Accessing into an object or array, through dot or index notation. Accessor: [o("PROPERTY_ACCESS IDENTIFIER", function() { - return new AccessorNode(yytext); + return new AccessorNode(new LiteralNode(yytext)); }), o("PROTOTYPE_ACCESS IDENTIFIER", function() { - return new AccessorNode(yytext, 'prototype'); + return new AccessorNode(new LiteralNode(yytext), 'prototype'); }), o("SOAK_ACCESS IDENTIFIER", function() { - return new AccessorNode(yytext, 'soak'); + return new AccessorNode(new LiteralNode(yytext), 'soak'); }), o("Index"), o("Slice", function() { return new SliceNode($1); }) @@ -308,7 +308,7 @@ ArgList: [o("", function() { return []; }), o("Expression", function() { - return val; + return [$1]; }), o("INDENT Expression", function() { return [$2]; }), o("ArgList , Expression", function() { diff --git a/src/nodes.coffee b/src/nodes.coffee index 58e363a3..c27ae1c3 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -72,6 +72,11 @@ merge: (src, dest) -> dest[key]: val for key, val of src dest +# Do any of the elements in the list pass a truth test? +any: (list, test) -> + result: true for item in list when test(item) + !!result.length + # Delete a key from an object, returning the value. del: (obj, key) -> val: obj[key] @@ -354,7 +359,60 @@ CommentNode: exports.CommentNode: inherit Node, { statement CommentNode - +# Node for a function invocation. Takes care of converting super() calls into +# calls against the prototype's function of the same name. +CallNode: exports.CallNode: inherit Node, { + + constructor: (variable, args) -> + @variable: variable + @args: args or [] + @children: flatten([@variable, @args]) + @prefix: '' + this + + new_instance: -> + @prefix: 'new ' + this + + push: (arg) -> + @args.push(arg) + @children.push(arg) + + # Compile a vanilla function call. + compile_node: (o) -> + return @compile_splat(o) if any @args, (a) -> a instanceof SplatNode + args: (arg.compile(o) for arg in @args).join(', ') + return @compile_super(args, o) if @variable is 'super' + @prefix + @variable.compile(o) + '(' + args + ')' + + # Compile a call against the superclass's implementation of the current function. + compile_super: (args, o) -> + methname: o.scope.method.name + arg_part: if args.length then ', ' + args else '' + meth: if o.scope.method.proto + o.scope.method.proto + '.__superClass__.' + methname + else + methname + '.__superClass__.constructor' + meth + '.call(this' + arg_part + ')' + + # Compile a function call being passed variable arguments. + compile_splat: (o) -> + meth: @variable.compile o + obj: @variable.source or 'this' + args: for arg, i in @args + code: arg.compile o + code: if arg instanceof SplatNode then code else '[' + code + ']' + if i is 0 then code else '.concat(' + code + ')' + @prefix + meth + '.apply(' + obj + ', ' + args.join('') + ')' + + # If the code generation wished to use the result of a function call + # in multiple places, ensure that the function is only ever called once. + compile_reference: (o) -> + reference: o.scope.free_variable() + call: new ParentheticalNode(new AssignNode(reference, this)) + [call, reference] + +} diff --git a/src/parser.coffee b/src/parser.coffee index 7a339fd9..2c0e2ec8 100644 --- a/src/parser.coffee +++ b/src/parser.coffee @@ -110,7 +110,7 @@ grammar: { # Assignment within an object literal (can be quoted). AssignObj: [ - o "IDENTIFIER ASSIGN Expression", -> new AssignNode(new ValueNode(yytext), $3, 'object') + o "IDENTIFIER ASSIGN Expression", -> new AssignNode(new ValueNode(new LiteralNode(yytext)), $3, 'object') o "STRING ASSIGN Expression", -> new AssignNode(new ValueNode(new LiteralNode(yytext)), $3, 'object') o "NUMBER ASSIGN Expression", -> new AssignNode(new ValueNode(new LiteralNode(yytext)), $3, 'object') o "Comment" @@ -213,7 +213,7 @@ grammar: { # A Parameter (or ParamSplat) in a function definition. Param: [ - o "PARAM", -> yytext + o "PARAM", -> new LiteralNode(yytext) o "PARAM . . .", -> new SplatNode(yytext) ] @@ -224,7 +224,7 @@ grammar: { # Expressions that can be treated as values. Value: [ - o "IDENTIFIER", -> new ValueNode(yytext) + o "IDENTIFIER", -> new ValueNode(new LiteralNode(yytext)) o "Literal", -> new ValueNode($1) o "Array", -> new ValueNode($1) o "Object", -> new ValueNode($1) @@ -236,9 +236,9 @@ grammar: { # Accessing into an object or array, through dot or index notation. Accessor: [ - o "PROPERTY_ACCESS IDENTIFIER", -> new AccessorNode(yytext) - o "PROTOTYPE_ACCESS IDENTIFIER", -> new AccessorNode(yytext, 'prototype') - o "SOAK_ACCESS IDENTIFIER", -> new AccessorNode(yytext, 'soak') + o "PROPERTY_ACCESS IDENTIFIER", -> new AccessorNode(new LiteralNode(yytext)) + o "PROTOTYPE_ACCESS IDENTIFIER", -> new AccessorNode(new LiteralNode(yytext), 'prototype') + o "SOAK_ACCESS IDENTIFIER", -> new AccessorNode(new LiteralNode(yytext), 'soak') o "Index" o "Slice", -> new SliceNode($1) ] @@ -311,7 +311,7 @@ grammar: { # A list of arguments to a method call, or as the contents of an array. ArgList: [ o "", -> [] - o "Expression", -> val + o "Expression", -> [$1] o "INDENT Expression", -> [$2] o "ArgList , Expression", -> $1.push $3 o "ArgList TERMINATOR Expression", -> $1.push $3