diff --git a/lib/coffee_script/nodes.js b/lib/coffee_script/nodes.js index 0ef4d16a..5ceaab62 100644 --- a/lib/coffee_script/nodes.js +++ b/lib/coffee_script/nodes.js @@ -1,5 +1,5 @@ (function(){ - var AccessorNode, ArrayNode, AssignNode, CallNode, CommentNode, Expressions, ExtendsNode, IndexNode, LiteralNode, Node, ObjectNode, RangeNode, ReturnNode, SliceNode, TAB, TRAILING_WHITESPACE, ThisNode, ValueNode, any, compact, del, dup, flatten, inherit, merge, statement; + var AccessorNode, ArrayNode, AssignNode, CallNode, ClosureNode, CodeNode, CommentNode, Expressions, ExtendsNode, IndexNode, LiteralNode, Node, ObjectNode, PushNode, RangeNode, ReturnNode, SliceNode, TAB, TRAILING_WHITESPACE, ThisNode, 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. @@ -366,7 +366,7 @@ is_last: function is_last(node) { var l; l = this.expressions.length; - this.last_index = this.last_index || this.expressions[l - 1] instanceof CommentNode ? -2 : -1; + this.last_index = this.last_index || this.expressions[l - 1] instanceof CommentNode ? 2 : 1; return node === this.expressions[l - this.last_index]; }, compile: function compile(o) { @@ -435,7 +435,7 @@ })); } // Otherwise, we can just return the value of the expression. - return this.idt() + 'return ' + node.compile(o); + return this.idt() + 'return ' + node.compile(o) + ';'; } })); // Wrap up a node as an Expressions, unless it already is one. @@ -817,6 +817,29 @@ return '[' + objects + ending; } })); + // A faux-node that is never created by the grammar, but is used during + // code generation to generate a quick "array.push(value)" tree of nodes. + PushNode = (exports.PushNode = { + wrap: function wrap(array, expressions) { + var expr; + expr = expressions.unwrap(); + if (expr.is_statement_only() || expr.contains(function(n) { + return n.is_statement_only(); + })) { + return expressions; + } + return Expressions.wrap(new CallNode(new ValueNode(new LiteralNode(array), [new AccessorNode(new LiteralNode('push'))]), [expr])); + } + }); + // A faux-node used to wrap an expressions body in a closure. + ClosureNode = (exports.ClosureNode = { + wrap: function wrap(expressions, statement) { + var call, func; + func = new ParentheticalNode(new CodeNode([], Expressions.wrap(expressions))); + call = new CallNode(new ValueNode(func, new AccessorNode(new LiteralNode('call'))), [new LiteralNode('this')]); + return statement ? Expressions.wrap(call) : call; + } + }); // Setting the value of a local variable, or the value of an object property. AssignNode = (exports.AssignNode = inherit(Node, { // Keep the identifier regex in sync with the Lexer. @@ -912,4 +935,49 @@ return name + '.splice.apply(' + name + ', [' + from + ', ' + to + '].concat(' + this.value.compile(o) + '))'; } })); + // A function definition. The only node that creates a new Scope. + // A CodeNode does not have any children -- they're within the new scope. + CodeNode = (exports.CodeNode = inherit(Node, { + constructor: function constructor(params, body, tag) { + this.params = params; + this.body = body; + this.bound = tag === 'boundfunc'; + return this; + }, + compile_node: function compile_node(o) { + var __a, __b, code, func, inner, name_part, param, shared_scope, splat, top; + shared_scope = del(o, 'shared_scope'); + top = del(o, 'top'); + o.scope = shared_scope || new Scope(o.scope, this.body, this); + o.returns = true; + o.top = true; + o.indent = this.idt(this.bound ? 2 : 1); + del(o, 'no_wrap'); + del(o, 'globals'); + if (this.params[this.params.length - 1] instanceof SplatNode) { + splat = this.params.pop(); + splat.index = this.params.length; + this.body.unshift(splat); + } + __a = this.params; + for (__b = 0; __b < __a.length; __b++) { + param = __a[__b]; + (o.scope.parameter(param)); + } + code = this.body.expressions.length ? '\n' + this.body.compile_with_declarations(o) + '\n' : ''; + name_part = this.name ? ' ' + this.name : ''; + func = 'function' + (this.bound ? '' : name_part) + '(' + this.params.join(', ') + ') {' + code + this.idt(this.bound ? 1 : 0) + '}'; + if (top && !this.bound) { + func = '(' + func + ')'; + } + if (!(this.bound)) { + return func; + } + inner = '(function' + name_part + '() {\n' + this.idt(2) + 'return __func.apply(__this, arguments);\n' + this.idt(1) + '});'; + return '(function(__this) {\n' + this.idt(1) + 'var __func = ' + func + ';\n' + this.idt(1) + 'return ' + inner + '\n' + this.idt() + '})(this)'; + }, + top_sensitive: function top_sensitive() { + return true; + } + })); })(); \ No newline at end of file diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 3658e219..1bf47960 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -639,76 +639,6 @@ module CoffeeScript end end - # Simple Arithmetic and logical operations. Performs some conversion from - # CoffeeScript operations into their JavaScript equivalents. - class OpNode < Node - children :first, :second - attr_reader :operator - attr_accessor :second - - CONVERSIONS = { - :== => "===", - :'!=' => "!==", - :and => '&&', - :or => '||', - :is => '===', - :isnt => "!==", - :not => '!' - } - CHAINABLE = [:<, :>, :>=, :<=, :===, :'!==='] - ASSIGNMENT = [:'||=', :'&&=', :'?='] - PREFIX_OPERATORS = [:typeof, :delete] - - def initialize(operator, first, second=nil, flip=false) - @first, @second, @flip = first, second, flip - @operator = CONVERSIONS[operator.to_sym] || operator - end - - def unary? - @second.nil? - end - - def chainable? - CHAINABLE.include?(operator.to_sym) - end - - def compile_node(o) - return write(compile_chain(o)) if chainable? && @first.unwrap.is_a?(OpNode) && @first.unwrap.chainable? - return write(compile_assignment(o)) if ASSIGNMENT.include?(@operator.to_sym) - return write(compile_unary(o)) if unary? - return write(compile_existence(o)) if @operator == '?' - write("#{@first.compile(o)} #{@operator} #{@second.compile(o)}") - end - - # Mimic Python's chained comparisons. See: - # http://docs.python.org/reference/expressions.html#notin - def compile_chain(o) - shared = @first.unwrap.second - @first.second, shared = *shared.compile_reference(o) if shared.is_a?(CallNode) - "(#{@first.compile(o)}) && (#{shared.compile(o)} #{@operator} #{@second.compile(o)})" - end - - def compile_assignment(o) - first, second = @first.compile(o), @second.compile(o) - o[:scope].find(first) if @first.unwrap.is_a?(Value) - sym = @operator[0..1] - return "#{first} = #{ExistenceNode.compile_test(o, @first)} ? #{first} : #{second}" if @operator == '?=' - "#{first} = #{first} #{sym} #{second}" - end - - def compile_existence(o) - first, second = @first.compile(o), @second.compile(o) - "#{ExistenceNode.compile_test(o, @first)} ? #{first} : #{second}" - end - - def compile_unary(o) - space = PREFIX_OPERATORS.include?(@operator.to_sym) ? ' ' : '' - parts = [@operator.to_s, space, @first.compile(o)] - parts.reverse! if @flip - parts.join('') - end - end - # A function definition. The only node that creates a new Scope. # A CodeNode does not have any children -- they're within the new scope. class CodeNode < Node @@ -716,19 +646,12 @@ module CoffeeScript attr_reader :params, :body, :bound attr_accessor :name, :proto - # Constructor functions start with an uppercase letter, by convention. - UPPERCASE = /[A-Z]/ - def initialize(params, body, tag=nil) @params = params @body = body @bound = tag == :boundfunc end - def constructor? - @name && @name[0..0][UPPERCASE] - end - def compile_node(o) shared_scope = o.delete(:shared_scope) top = o.delete(:top) @@ -808,6 +731,76 @@ module CoffeeScript end end + # Simple Arithmetic and logical operations. Performs some conversion from + # CoffeeScript operations into their JavaScript equivalents. + class OpNode < Node + children :first, :second + attr_reader :operator + attr_accessor :second + + CONVERSIONS = { + :== => "===", + :'!=' => "!==", + :and => '&&', + :or => '||', + :is => '===', + :isnt => "!==", + :not => '!' + } + CHAINABLE = [:<, :>, :>=, :<=, :===, :'!==='] + ASSIGNMENT = [:'||=', :'&&=', :'?='] + PREFIX_OPERATORS = [:typeof, :delete] + + def initialize(operator, first, second=nil, flip=false) + @first, @second, @flip = first, second, flip + @operator = CONVERSIONS[operator.to_sym] || operator + end + + def unary? + @second.nil? + end + + def chainable? + CHAINABLE.include?(operator.to_sym) + end + + def compile_node(o) + return write(compile_chain(o)) if chainable? && @first.unwrap.is_a?(OpNode) && @first.unwrap.chainable? + return write(compile_assignment(o)) if ASSIGNMENT.include?(@operator.to_sym) + return write(compile_unary(o)) if unary? + return write(compile_existence(o)) if @operator == '?' + write("#{@first.compile(o)} #{@operator} #{@second.compile(o)}") + end + + # Mimic Python's chained comparisons. See: + # http://docs.python.org/reference/expressions.html#notin + def compile_chain(o) + shared = @first.unwrap.second + @first.second, shared = *shared.compile_reference(o) if shared.is_a?(CallNode) + "(#{@first.compile(o)}) && (#{shared.compile(o)} #{@operator} #{@second.compile(o)})" + end + + def compile_assignment(o) + first, second = @first.compile(o), @second.compile(o) + o[:scope].find(first) if @first.unwrap.is_a?(Value) + sym = @operator[0..1] + return "#{first} = #{ExistenceNode.compile_test(o, @first)} ? #{first} : #{second}" if @operator == '?=' + "#{first} = #{first} #{sym} #{second}" + end + + def compile_existence(o) + first, second = @first.compile(o), @second.compile(o) + "#{ExistenceNode.compile_test(o, @first)} ? #{first} : #{second}" + end + + def compile_unary(o) + space = PREFIX_OPERATORS.include?(@operator.to_sym) ? ' ' : '' + parts = [@operator.to_s, space, @first.compile(o)] + parts.reverse! if @flip + parts.join('') + end + end + # The replacement for the for loop is an array comprehension (that compiles) # into a for loop. Also acts as an expression, able to return the result # of the comprehenion. Unlike Python array comprehensions, it's able to pass diff --git a/lib/coffee_script/parser.js b/lib/coffee_script/parser.js index d4cf4277..203ae22d 100644 --- a/lib/coffee_script/parser.js +++ b/lib/coffee_script/parser.js @@ -190,12 +190,12 @@ ParamList: [o("Param", function() { return [$1]; }), o("ParamList , Param", function() { - return $1.push($3); + return $1.concat([$3]); }) ], // A Parameter (or ParamSplat) in a function definition. Param: [o("PARAM", function() { - return new LiteralNode(yytext); + return yytext; }), o("PARAM . . .", function() { return new SplatNode(yytext); }) diff --git a/src/nodes.coffee b/src/nodes.coffee index f3f12634..cf500eaa 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -177,7 +177,7 @@ Expressions: exports.Expressions: inherit Node, { # Is the node last in this block of expressions? is_last: (node) -> l: @expressions.length - @last_index ||= if @expressions[l - 1] instanceof CommentNode then -2 else -1 + @last_index ||= if @expressions[l - 1] instanceof CommentNode then 2 else 1 node is @expressions[l - @last_index] compile: (o) -> @@ -218,7 +218,7 @@ Expressions: exports.Expressions: inherit Node, { # If it's a statement, the node knows how to return itself. return node.compile(merge(o, {returns: true})) if node.is_statement() # Otherwise, we can just return the value of the expression. - return @idt() + 'return ' + node.compile(o) + return @idt() + 'return ' + node.compile(o) + ';' } @@ -561,9 +561,28 @@ ArrayNode: exports.ArrayNode: inherit Node, { } +# A faux-node that is never created by the grammar, but is used during +# code generation to generate a quick "array.push(value)" tree of nodes. +PushNode: exports.PushNode: { + wrap: (array, expressions) -> + expr: expressions.unwrap() + return expressions if expr.is_statement_only() or expr.contains (n) -> n.is_statement_only() + Expressions.wrap(new CallNode( + new ValueNode(new LiteralNode(array), [new AccessorNode(new LiteralNode('push'))]), [expr] + )) +} +# A faux-node used to wrap an expressions body in a closure. +ClosureNode: exports.ClosureNode: { + + wrap: (expressions, statement) -> + func: new ParentheticalNode(new CodeNode([], Expressions.wrap(expressions))) + call: new CallNode(new ValueNode(func, new AccessorNode(new LiteralNode('call'))), [new LiteralNode('this')]) + if statement then Expressions.wrap(call) else call + +} # Setting the value of a local variable, or the value of an object property. AssignNode: exports.AssignNode: inherit Node, { @@ -635,8 +654,42 @@ AssignNode: exports.AssignNode: inherit Node, { } - - +# A function definition. The only node that creates a new Scope. +# A CodeNode does not have any children -- they're within the new scope. +CodeNode: exports.CodeNode: inherit Node, { + + constructor: (params, body, tag) -> + @params: params + @body: body + @bound: tag is 'boundfunc' + this + + compile_node: (o) -> + shared_scope: del o, 'shared_scope' + top: del o, 'top' + o.scope: shared_scope or new Scope(o.scope, @body, this) + o.returns: true + o.top: true + o.indent: @idt(if @bound then 2 else 1) + del o, 'no_wrap' + del o, 'globals' + if @params[@params.length - 1] instanceof SplatNode + splat: @params.pop() + splat.index: @params.length + @body.unshift(splat) + (o.scope.parameter(param)) for param in @params + code: if @body.expressions.length then '\n' + @body.compile_with_declarations(o) + '\n' else '' + name_part: if @name then ' ' + @name else '' + func: 'function' + (if @bound then '' else name_part) + '(' + @params.join(', ') + ') {' + code + @idt(if @bound then 1 else 0) + '}' + func: '(' + func + ')' if top and not @bound + return func unless @bound + inner: '(function' + name_part + '() {\n' + @idt(2) + 'return __func.apply(__this, arguments);\n' + @idt(1) + '});' + '(function(__this) {\n' + @idt(1) + 'var __func = ' + func + ';\n' + @idt(1) + 'return ' + inner + '\n' + @idt() + '})(this)' + + top_sensitive: -> + true + +} diff --git a/src/parser.coffee b/src/parser.coffee index 90a2bb12..42d04940 100644 --- a/src/parser.coffee +++ b/src/parser.coffee @@ -215,12 +215,12 @@ grammar: { # The parameters to a function definition. ParamList: [ o "Param", -> [$1] - o "ParamList , Param", -> $1.push($3) + o "ParamList , Param", -> $1.concat [$3] ] # A Parameter (or ParamSplat) in a function definition. Param: [ - o "PARAM", -> new LiteralNode(yytext) + o "PARAM", -> yytext o "PARAM . . .", -> new SplatNode(yytext) ]