diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index eecd8530..d3c7b862 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -119,8 +119,8 @@ rule # Assignment within an object literal. AssignObj: - IDENTIFIER ASSIGN Expression { result = AssignNode.new(val[0], val[2], :object) } - | STRING ASSIGN Expression { result = AssignNode.new(val[0], val[2], :object) } + IDENTIFIER ASSIGN Expression { result = AssignNode.new(ValueNode.new(val[0]), val[2], :object) } + | STRING ASSIGN Expression { result = AssignNode.new(ValueNode.new(LiteralNode.new(val[0])), val[2], :object) } | Comment { result = val[0] } ; @@ -143,10 +143,10 @@ rule | '-' Expression = UMINUS { result = OpNode.new(val[0], val[1]) } | NOT Expression { result = OpNode.new(val[0], val[1]) } | '~' Expression { result = OpNode.new(val[0], val[1]) } - | '--' Expression { result = OpNode.new(val[0], val[1]) } - | '++' Expression { result = OpNode.new(val[0], val[1]) } - | Expression '--' { result = OpNode.new(val[1], val[0], nil, true) } - | Expression '++' { result = OpNode.new(val[1], val[0], nil, true) } + | '--' Expression { result = OpNode.new(val[0], val[1]) } + | '++' Expression { result = OpNode.new(val[0], val[1]) } + | Expression '--' { result = OpNode.new(val[1], val[0], nil, true) } + | Expression '++' { result = OpNode.new(val[1], val[0], nil, true) } | Expression '*' Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression '/' Expression { result = OpNode.new(val[1], val[0], val[2]) } diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index ff361d19..69b9f6e4 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -77,16 +77,18 @@ module CoffeeScript # If this is the top-level Expressions, wrap everything in a safety closure. def root_compile(o={}) indent = o[:no_wrap] ? '' : TAB - code = compile(o.merge(:indent => indent, :scope => Scope.new)) + code = compile(o.merge(:indent => indent, :scope => Scope.new), o[:no_wrap] ? nil : :code) code.gsub!(STRIP_TRAILING_WHITESPACE, '') o[:no_wrap] ? code : "(function(){\n#{code}\n})();" end # The extra fancy is to handle pushing down returns and assignments # recursively to the final lines of inner statements. - def compile(options={}) + # Variables first defined within the Expressions body have their + # declarations pushed up to the top scope. + def compile(options={}, parent=nil) return root_compile(options) unless options[:scope] - code = @expressions.map { |node| + compiled = @expressions.map do |node| o = super(options) if last?(node) && (o[:return] || o[:assign]) if o[:return] @@ -99,14 +101,17 @@ module CoffeeScript if node.statement? || node.custom_assign? "#{o[:indent]}#{node.compile(o)}#{node.line_ending}" else - "#{o[:indent]}#{AssignNode.new(ValueNode.new(LiteralNode.new(o[:assign])), node).compile(o)};" + "#{o[:indent]}#{AssignNode.new(o[:assign], node).compile(o)};" end end else o.delete(:return) and o.delete(:assign) "#{o[:indent]}#{node.compile(o)}#{node.line_ending}" end - }.join("\n") + end + scope = options[:scope] + declarations = scope.any_declared? && parent == :code ? "#{options[:indent]}var #{scope.declared_variables.join(', ')};\n" : '' + code = declarations + compiled.join("\n") write(code) end end @@ -338,10 +343,8 @@ module CoffeeScript # Setting the value of a local variable, or the value of an object property. class AssignNode < Node - LEADING_VAR = /\Avar\s+/ PROTO_ASSIGN = /\A(\S+)\.prototype/ - statement custom_return attr_reader :variable, :value, :context @@ -356,19 +359,16 @@ module CoffeeScript def compile(o={}) o = super(o) - name = @variable.respond_to?(:compile) ? @variable.compile(o) : @variable.to_s - last = @variable.respond_to?(:last) ? @variable.last.to_s : name.to_s + name = @variable.compile(o) + last = @variable.last.to_s proto = name[PROTO_ASSIGN, 1] - o = o.merge(:assign => name, :last_assign => last, :proto_assign => proto) + o = o.merge(:assign => @variable, :last_assign => last, :proto_assign => proto) postfix = o[:return] ? ";\n#{o[:indent]}return #{name}" : '' - return write("#{@variable}: #{@value.compile(o)}") if @context == :object + return write("#{name}: #{@value.compile(o)}") if @context == :object return write("#{name} = #{@value.compile(o)}#{postfix}") if @variable.properties? && !@value.custom_assign? - defined = o[:scope].find(name) - def_part = defined || @variable.properties? || o[:no_wrap] ? "" : "var #{name};\n#{o[:indent]}" - return write(def_part + @value.compile(o)) if @value.custom_assign? - def_part = defined || o[:no_wrap] ? name : "var #{name}" - val_part = @value.compile(o).sub(LEADING_VAR, '') - write("#{def_part} = #{val_part}#{postfix}") + o[:scope].find(name) unless @variable.properties? + return write(@value.compile(o)) if @value.custom_assign? + write("#{name} = #{@value.compile(o)}#{postfix}") end end @@ -436,8 +436,8 @@ module CoffeeScript o[:indent] += TAB o.delete(:assign) o.delete(:no_wrap) - @params.each {|id| o[:scope].find(id.to_s) } - code = @body.compile(o) + @params.each {|id| o[:scope].parameter(id.to_s) } + code = @body.compile(o, :code) write("function(#{@params.join(', ')}) {\n#{code}\n#{indent}}") end end @@ -533,20 +533,19 @@ module CoffeeScript ivar = scope.free_variable lvar = scope.free_variable rvar = scope.free_variable - name_part = name_found ? @name : "var #{@name}" - index_name = @index ? (index_found ? @index : "var #{@index}") : nil - source_part = "var #{svar} = #{@source.compile(o)};" - for_part = "var #{ivar}=0, #{lvar}=#{svar}.length; #{ivar}<#{lvar}; #{ivar}++" - var_part = "\n#{o[:indent] + TAB}#{name_part} = #{svar}[#{ivar}];\n" + index_name = @index ? @index : nil + source_part = "#{svar} = #{@source.compile(o)};" + for_part = "#{ivar}=0, #{lvar}=#{svar}.length; #{ivar}<#{lvar}; #{ivar}++" + var_part = "\n#{o[:indent] + TAB}#{@name} = #{svar}[#{ivar}];\n" index_part = @index ? "#{o[:indent] + TAB}#{index_name} = #{ivar};\n" : '' body = @body suffix = ';' - set_result = "var #{rvar} = [];\n#{o[:indent]}" + set_result = "#{rvar} = [];\n#{o[:indent]}" save_result = "#{rvar}[#{ivar}] = " return_result = rvar if o[:return] || o[:assign] - return_result = "#{o[:assign]} = #{return_result}" if o[:assign] + return_result = "#{o[:assign].compile(o)} = #{return_result}" if o[:assign] return_result = "return #{return_result}" if o[:return] if @filter body = CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [@body]) diff --git a/lib/coffee_script/scope.rb b/lib/coffee_script/scope.rb index 8c07785b..5e510b12 100644 --- a/lib/coffee_script/scope.rb +++ b/lib/coffee_script/scope.rb @@ -5,7 +5,7 @@ module CoffeeScript # whether a variable has been seen before or if it needs to be declared. class Scope - attr_reader :parent, :temp_variable + attr_reader :parent, :variables, :temp_variable # Initialize a scope with its parent, for lookups up the chain. def initialize(parent=nil) @@ -18,10 +18,16 @@ module CoffeeScript def find(name, remote=false) found = check(name, remote) return found if found || remote - @variables[name.to_sym] = true + @variables[name.to_sym] = :var found end + # Define a local variable as originating from a parameter in current scope + # -- no var required. + def parameter(name) + @variables[name.to_sym] = :param + end + # Just check to see if a variable has already been declared. def check(name, remote=false) return true if @variables[name.to_sym] @@ -36,10 +42,19 @@ module CoffeeScript # Find an available, short, name for a compiler-generated variable. def free_variable @temp_variable.succ! while check(@temp_variable) - @variables[@temp_variable.to_sym] = true + @variables[@temp_variable.to_sym] = :var @temp_variable.dup end + def any_declared? + !declared_variables.empty? + end + + # Return the list of variables first declared in current scope. + def declared_variables + @variables.select {|k, v| v == :var }.map {|pair| pair[0].to_s }.sort + end + end end \ No newline at end of file diff --git a/test/fixtures/generation/each.js b/test/fixtures/generation/each.js index 8536f46a..8787ea06 100644 --- a/test/fixtures/generation/each.js +++ b/test/fixtures/generation/each.js @@ -3,24 +3,25 @@ // The cornerstone, an each implementation. // Handles objects implementing forEach, arrays, and raw objects. _.each = function(obj, iterator, context) { - var index = 0; + var __a, __b, __c, __d, __e, __f, __g, __h, i, index, item, key; + index = 0; try { if (obj.forEach) { obj.forEach(iterator, context); } else if (_.isArray(obj) || _.isArguments(obj)) { - var __a = obj; - var __d = []; - for (var __b=0, __c=__a.length; __b<__c; __b++) { - var item = __a[__b]; - var i = __b; + __a = obj; + __d = []; + for (__b=0, __c=__a.length; __b<__c; __b++) { + item = __a[__b]; + i = __b; __d[__b] = iterator.call(context, item, i, obj); } __d; } else { - var __e = _.keys(obj); - var __h = []; - for (var __f=0, __g=__e.length; __f<__g; __f++) { - var key = __e[__f]; + __e = _.keys(obj); + __h = []; + for (__f=0, __g=__e.length; __f<__g; __f++) { + key = __e[__f]; __h[__f] = iterator.call(context, obj[key], key, obj); } __h; diff --git a/test/fixtures/generation/each_no_wrap.js b/test/fixtures/generation/each_no_wrap.js index 5cc80f37..aa88e142 100644 --- a/test/fixtures/generation/each_no_wrap.js +++ b/test/fixtures/generation/each_no_wrap.js @@ -2,24 +2,25 @@ // The cornerstone, an each implementation. // Handles objects implementing forEach, arrays, and raw objects. _.each = function(obj, iterator, context) { - var index = 0; + var __a, __b, __c, __d, __e, __f, __g, __h, i, index, item, key; + index = 0; try { if (obj.forEach) { obj.forEach(iterator, context); } else if (_.isArray(obj) || _.isArguments(obj)) { - var __a = obj; - var __d = []; - for (var __b=0, __c=__a.length; __b<__c; __b++) { - var item = __a[__b]; - var i = __b; + __a = obj; + __d = []; + for (__b=0, __c=__a.length; __b<__c; __b++) { + item = __a[__b]; + i = __b; __d[__b] = iterator.call(context, item, i, obj); } __d; } else { - var __e = _.keys(obj); - var __h = []; - for (var __f=0, __g=__e.length; __f<__g; __f++) { - var key = __e[__f]; + __e = _.keys(obj); + __h = []; + for (__f=0, __g=__e.length; __f<__g; __f++) { + key = __e[__f]; __h[__f] = iterator.call(context, obj[key], key, obj); } __h; diff --git a/test/fixtures/generation/inner_comments.js b/test/fixtures/generation/inner_comments.js index 1b4386d2..123b8cea 100644 --- a/test/fixtures/generation/inner_comments.js +++ b/test/fixtures/generation/inner_comments.js @@ -1,12 +1,13 @@ (function(){ - var object = { + var array, object; + object = { a: 1, // Comments between the elements. b: 2, // Like this. c: 3 }; - var array = [1, + array = [1, // Comments between the elements. 2, // Like this. diff --git a/test/unit/test_parser.rb b/test/unit/test_parser.rb index 67bd93f3..fecb1823 100644 --- a/test/unit/test_parser.rb +++ b/test/unit/test_parser.rb @@ -24,8 +24,8 @@ class ParserTest < Test::Unit::TestCase nodes = @par.parse("{one : 1 \n two : 2}").expressions obj = nodes.first.literal assert obj.is_a? ObjectNode - assert obj.properties.first.variable == "one" - assert obj.properties.last.variable == "two" + assert obj.properties.first.variable.literal.value == "one" + assert obj.properties.last.variable.literal.value == "two" end def test_parsing_an_function_definition @@ -79,7 +79,7 @@ class ParserTest < Test::Unit::TestCase def test_no_wrapping_parens_around_statements assert_raises(SyntaxError) do - @par.parse("(a: 1)").compile + @par.parse("(try thing() catch error fail().)").compile end end