diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index cfcb4be7..9fae471f 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -1,6 +1,12 @@ module CoffeeScript # The abstract base class for all CoffeeScript nodes. + # All nodes are implement a "compile_node" method, which performs the + # code generation for that node. To compile a node, call the "compile" + # method, which wraps "compile_node" in some extra smarts, to know when the + # generated code should be wrapped up in a closure. An options hash is passed + # and cloned throughout, containing messages from higher in the AST, + # information about the current scope, and indentation level. class Node # Tabs are two spaces for pretty-printing. TAB = ' ' @@ -115,16 +121,18 @@ module CoffeeScript o.merge!(:indent => indent, :scope => Scope.new(nil, self)) code = o[:globals] ? compile_node(o) : compile_with_declarations(o) code.gsub!(TRAILING_WHITESPACE, '') - o[:no_wrap] ? code : "(function(){\n#{code}\n})();" + write(o[:no_wrap] ? code : "(function(){\n#{code}\n})();") end + # Compile the expressions body, with declarations of all inner variables + # at the top. def compile_with_declarations(o={}) code = compile_node(o) - decls = '' - decls = "#{idt}var #{o[:scope].declared_variables.join(', ')};\n" if o[:scope].declarations?(self) - decls + code + return code unless o[:scope].declarations?(self) + write("#{idt}var #{o[:scope].declared_variables.join(', ')};\n#{code}") end + # Compiles a single expression within the expression list. def compile_expression(node, o) @indent = o[:indent] stmt = node.statement? @@ -207,8 +215,7 @@ module CoffeeScript def compile_node(o={}) delimiter = "\n#{idt}//" - comment = "#{delimiter}#{@lines.join(delimiter)}" - write(comment) + write("#{delimiter}#{@lines.join(delimiter)}") end end @@ -243,6 +250,7 @@ module CoffeeScript @arguments << argument end + # Compile a vanilla function call. def compile_node(o) return write(compile_splat(o)) if splat? args = @arguments.map{|a| a.compile(o) }.join(', ') @@ -250,6 +258,7 @@ module CoffeeScript write("#{prefix}#{@variable.compile(o)}(#{args})") end + # Compile a call against the superclass's implementation of the current function. def compile_super(args, o) methname = o[:last_assign] arg_part = args.empty? ? '' : ", #{args}" @@ -258,6 +267,7 @@ module CoffeeScript "#{meth}.call(this#{arg_part})" end + # Compile a function call being passed variable arguments. def compile_splat(o) meth = @variable.compile(o) obj = @variable.source || 'this' @@ -280,6 +290,7 @@ module CoffeeScript @sub_object, @super_object = sub_object, super_object end + # Hooking one constructor into another's prototype chain. def compile_node(o={}) constructor = o[:scope].free_variable sub, sup = @sub_object.compile(o), @super_object.compile(o) @@ -294,10 +305,10 @@ module CoffeeScript # A value, indexed or dotted into, or vanilla. class ValueNode < Node - attr_reader :literal, :properties, :last, :source + attr_reader :base, :properties, :last, :source - def initialize(literal, properties=[]) - @literal, @properties = literal, properties + def initialize(base, properties=[]) + @base, @properties = base, properties end def <<(other) @@ -309,23 +320,23 @@ module CoffeeScript return !@properties.empty? end + # Values are statements if their base is a statement. def statement? - @literal.is_a?(Node) && @literal.statement? && !properties? + @base.is_a?(Node) && @base.statement? && !properties? end def compile_node(o) only = o.delete(:only_first) props = only ? @properties[0...-1] : @properties - parts = [@literal, props].flatten.map do |val| - val.respond_to?(:compile) ? val.compile(o) : val.to_s - end + parts = [@base, props].flatten.map {|val| val.compile(o) } @last = parts.last @source = parts.length > 1 ? parts[0...-1].join('') : nil write(parts.join('')) end end - # A dotted accessor into a part of a value. + # A dotted accessor into a part of a value, or the :: shorthand for + # an accessor into the object's prototype. class AccessorNode < Node attr_reader :name @@ -365,14 +376,6 @@ module CoffeeScript @exclusive end - def less_operator - @exclusive ? '<' : '<=' - end - - def greater_operator - @exclusive ? '>' : '>=' - end - def compile_variables(o) @indent = o[:indent] @from_var, @to_var = o[:scope].free_variable, o[:scope].free_variable @@ -381,12 +384,13 @@ module CoffeeScript end def compile_node(o) + return compile_array(o) unless o[:index] idx, step = o.delete(:index), o.delete(:step) - return compile_array(o) unless idx - vars = "#{idx}=#{@from_var}" - step = step ? step.compile(o) : '1' - compare = "(#{@from_var} <= #{@to_var} ? #{idx} #{less_operator} #{@to_var} : #{idx} #{greater_operator} #{@to_var})" - incr = "(#{@from_var} <= #{@to_var} ? #{idx} += #{step} : #{idx} -= #{step})" + vars = "#{idx}=#{@from_var}" + step = step ? step.compile(o) : '1' + equals = @exclusive ? '' : '=' + compare = "(#{@from_var} <= #{@to_var} ? #{idx} <#{equals} #{@to_var} : #{idx} >#{equals} #{@to_var})" + incr = "(#{@from_var} <= #{@to_var} ? #{idx} += #{step} : #{idx} -= #{step})" write("#{vars}; #{compare}; #{incr}") end @@ -645,8 +649,8 @@ module CoffeeScript def compile_node(o) top_level = o.delete(:top) && !o[:return] - range = @source.is_a?(ValueNode) && @source.literal.is_a?(RangeNode) && @source.properties.empty? - source = range ? @source.literal : @source + range = @source.is_a?(ValueNode) && @source.base.is_a?(RangeNode) && @source.properties.empty? + source = range ? @source.base : @source scope = o[:scope] name_found = @name && scope.find(@name) index_found = @index && scope.find(@index) diff --git a/lib/coffee_script/value.rb b/lib/coffee_script/value.rb index 49d65979..bfd448ab 100644 --- a/lib/coffee_script/value.rb +++ b/lib/coffee_script/value.rb @@ -18,6 +18,10 @@ module CoffeeScript to_str.to_sym end + def compile(o={}) + to_s + end + def inspect @value.inspect end diff --git a/test/unit/test_parser.rb b/test/unit/test_parser.rb index ef3feab5..fc0ae6aa 100644 --- a/test/unit/test_parser.rb +++ b/test/unit/test_parser.rb @@ -17,15 +17,15 @@ class ParserTest < Test::Unit::TestCase assert nodes.length == 1 assign = nodes.first assert assign.is_a?(AssignNode) - assert assign.variable.literal == 'a' + assert assign.variable.base == 'a' end def test_parsing_an_object_literal nodes = @par.parse("{one : 1\ntwo : 2}").expressions - obj = nodes.first.literal + obj = nodes.first.base assert obj.is_a?(ObjectNode) - assert obj.properties.first.variable.literal.value == "one" - assert obj.properties.last.variable.literal.value == "two" + assert obj.properties.first.variable.base.value == "one" + assert obj.properties.last.variable.base.value == "two" end def test_parsing_an_function_definition @@ -39,17 +39,17 @@ class ParserTest < Test::Unit::TestCase def test_parsing_if_statement the_if = @par.parse("clap_your_hands() if happy").expressions.first assert the_if.is_a?(IfNode) - assert the_if.condition.literal == 'happy' + assert the_if.condition.base == 'happy' assert the_if.body.is_a?(CallNode) - assert the_if.body.variable.literal == 'clap_your_hands' + assert the_if.body.variable.base == 'clap_your_hands' end def test_parsing_array_comprehension nodes = @par.parse("i for x, i in [10, 9, 8, 7, 6, 5] when i % 2 is 0").expressions assert nodes.first.is_a?(ForNode) - assert nodes.first.body.literal == 'i' + assert nodes.first.body.base == 'i' assert nodes.first.filter.operator == '===' - assert nodes.first.source.literal.objects.last.literal.value == "5" + assert nodes.first.source.base.objects.last.base.value == "5" end def test_parsing_comment @@ -66,7 +66,7 @@ class ParserTest < Test::Unit::TestCase nodes = @par.parse(File.read('test/fixtures/generation/each.coffee')) assign = nodes.expressions[1] assert assign.is_a?(AssignNode) - assert assign.variable.literal == '_' + assert assign.variable.base == '_' assert assign.value.is_a?(CodeNode) assert assign.value.params == ['obj', 'iterator', 'context'] end