CoffeeScript-in-CoffeeScript is compiling function calls

This commit is contained in:
Jeremy Ashkenas
2010-02-08 22:55:56 -05:00
parent 0b5b6113ee
commit 210d673ef0
4 changed files with 164 additions and 16 deletions

View File

@@ -1,5 +1,5 @@
(function(){ (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; var __hasProp = Object.prototype.hasOwnProperty;
process.mixin(require('./scope')); process.mixin(require('./scope'));
// The abstract base class for all CoffeeScript nodes. // The abstract base class for all CoffeeScript nodes.
@@ -270,6 +270,21 @@
}).call(this); }).call(this);
return dest; 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. // Delete a key from an object, returning the value.
del = function del(obj, key) { del = function del(obj, key) {
var val; var val;
@@ -612,4 +627,79 @@
} }
})); }));
statement(CommentNode); 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];
}
}));
})(); })();

View File

@@ -81,7 +81,7 @@
], ],
// Assignment within an object literal (can be quoted). // Assignment within an object literal (can be quoted).
AssignObj: [o("IDENTIFIER ASSIGN Expression", function() { 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() { }), o("STRING ASSIGN Expression", function() {
return new AssignNode(new ValueNode(new LiteralNode(yytext)), $3, 'object'); return new AssignNode(new ValueNode(new LiteralNode(yytext)), $3, 'object');
}), o("NUMBER ASSIGN Expression", function() { }), o("NUMBER ASSIGN Expression", function() {
@@ -189,7 +189,7 @@
], ],
// A Parameter (or ParamSplat) in a function definition. // A Parameter (or ParamSplat) in a function definition.
Param: [o("PARAM", function() { Param: [o("PARAM", function() {
return yytext; return new LiteralNode(yytext);
}), o("PARAM . . .", function() { }), o("PARAM . . .", function() {
return new SplatNode(yytext); return new SplatNode(yytext);
}) })
@@ -201,7 +201,7 @@
], ],
// Expressions that can be treated as values. // Expressions that can be treated as values.
Value: [o("IDENTIFIER", function() { Value: [o("IDENTIFIER", function() {
return new ValueNode(yytext); return new ValueNode(new LiteralNode(yytext));
}), o("Literal", function() { }), o("Literal", function() {
return new ValueNode($1); return new ValueNode($1);
}), o("Array", function() { }), o("Array", function() {
@@ -220,11 +220,11 @@
], ],
// Accessing into an object or array, through dot or index notation. // Accessing into an object or array, through dot or index notation.
Accessor: [o("PROPERTY_ACCESS IDENTIFIER", function() { Accessor: [o("PROPERTY_ACCESS IDENTIFIER", function() {
return new AccessorNode(yytext); return new AccessorNode(new LiteralNode(yytext));
}), o("PROTOTYPE_ACCESS IDENTIFIER", function() { }), o("PROTOTYPE_ACCESS IDENTIFIER", function() {
return new AccessorNode(yytext, 'prototype'); return new AccessorNode(new LiteralNode(yytext), 'prototype');
}), o("SOAK_ACCESS IDENTIFIER", function() { }), o("SOAK_ACCESS IDENTIFIER", function() {
return new AccessorNode(yytext, 'soak'); return new AccessorNode(new LiteralNode(yytext), 'soak');
}), o("Index"), o("Slice", function() { }), o("Index"), o("Slice", function() {
return new SliceNode($1); return new SliceNode($1);
}) })
@@ -308,7 +308,7 @@
ArgList: [o("", function() { ArgList: [o("", function() {
return []; return [];
}), o("Expression", function() { }), o("Expression", function() {
return val; return [$1];
}), o("INDENT Expression", function() { }), o("INDENT Expression", function() {
return [$2]; return [$2];
}), o("ArgList , Expression", function() { }), o("ArgList , Expression", function() {

View File

@@ -72,6 +72,11 @@ merge: (src, dest) ->
dest[key]: val for key, val of src dest[key]: val for key, val of src
dest 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. # Delete a key from an object, returning the value.
del: (obj, key) -> del: (obj, key) ->
val: obj[key] val: obj[key]
@@ -354,7 +359,60 @@ CommentNode: exports.CommentNode: inherit Node, {
statement CommentNode 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]
}

View File

@@ -110,7 +110,7 @@ grammar: {
# Assignment within an object literal (can be quoted). # Assignment within an object literal (can be quoted).
AssignObj: [ 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 "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 "NUMBER ASSIGN Expression", -> new AssignNode(new ValueNode(new LiteralNode(yytext)), $3, 'object')
o "Comment" o "Comment"
@@ -213,7 +213,7 @@ grammar: {
# A Parameter (or ParamSplat) in a function definition. # A Parameter (or ParamSplat) in a function definition.
Param: [ Param: [
o "PARAM", -> yytext o "PARAM", -> new LiteralNode(yytext)
o "PARAM . . .", -> new SplatNode(yytext) o "PARAM . . .", -> new SplatNode(yytext)
] ]
@@ -224,7 +224,7 @@ grammar: {
# Expressions that can be treated as values. # Expressions that can be treated as values.
Value: [ Value: [
o "IDENTIFIER", -> new ValueNode(yytext) o "IDENTIFIER", -> new ValueNode(new LiteralNode(yytext))
o "Literal", -> new ValueNode($1) o "Literal", -> new ValueNode($1)
o "Array", -> new ValueNode($1) o "Array", -> new ValueNode($1)
o "Object", -> new ValueNode($1) o "Object", -> new ValueNode($1)
@@ -236,9 +236,9 @@ grammar: {
# Accessing into an object or array, through dot or index notation. # Accessing into an object or array, through dot or index notation.
Accessor: [ Accessor: [
o "PROPERTY_ACCESS IDENTIFIER", -> new AccessorNode(yytext) o "PROPERTY_ACCESS IDENTIFIER", -> new AccessorNode(new LiteralNode(yytext))
o "PROTOTYPE_ACCESS IDENTIFIER", -> new AccessorNode(yytext, 'prototype') o "PROTOTYPE_ACCESS IDENTIFIER", -> new AccessorNode(new LiteralNode(yytext), 'prototype')
o "SOAK_ACCESS IDENTIFIER", -> new AccessorNode(yytext, 'soak') o "SOAK_ACCESS IDENTIFIER", -> new AccessorNode(new LiteralNode(yytext), 'soak')
o "Index" o "Index"
o "Slice", -> new SliceNode($1) 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. # A list of arguments to a method call, or as the contents of an array.
ArgList: [ ArgList: [
o "", -> [] o "", -> []
o "Expression", -> val o "Expression", -> [$1]
o "INDENT Expression", -> [$2] o "INDENT Expression", -> [$2]
o "ArgList , Expression", -> $1.push $3 o "ArgList , Expression", -> $1.push $3
o "ArgList TERMINATOR Expression", -> $1.push $3 o "ArgList TERMINATOR Expression", -> $1.push $3