more refactors to nodes

This commit is contained in:
Jeremy Ashkenas
2010-01-10 22:35:55 -05:00
parent d9d09a9a72
commit d1ddeacbe3
3 changed files with 46 additions and 38 deletions

View File

@@ -1,6 +1,12 @@
module CoffeeScript module CoffeeScript
# The abstract base class for all CoffeeScript nodes. # 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 class Node
# Tabs are two spaces for pretty-printing. # Tabs are two spaces for pretty-printing.
TAB = ' ' TAB = ' '
@@ -115,16 +121,18 @@ module CoffeeScript
o.merge!(:indent => indent, :scope => Scope.new(nil, self)) o.merge!(:indent => indent, :scope => Scope.new(nil, self))
code = o[:globals] ? compile_node(o) : compile_with_declarations(o) code = o[:globals] ? compile_node(o) : compile_with_declarations(o)
code.gsub!(TRAILING_WHITESPACE, '') code.gsub!(TRAILING_WHITESPACE, '')
o[:no_wrap] ? code : "(function(){\n#{code}\n})();" write(o[:no_wrap] ? code : "(function(){\n#{code}\n})();")
end end
# Compile the expressions body, with declarations of all inner variables
# at the top.
def compile_with_declarations(o={}) def compile_with_declarations(o={})
code = compile_node(o) code = compile_node(o)
decls = '' return code unless o[:scope].declarations?(self)
decls = "#{idt}var #{o[:scope].declared_variables.join(', ')};\n" if o[:scope].declarations?(self) write("#{idt}var #{o[:scope].declared_variables.join(', ')};\n#{code}")
decls + code
end end
# Compiles a single expression within the expression list.
def compile_expression(node, o) def compile_expression(node, o)
@indent = o[:indent] @indent = o[:indent]
stmt = node.statement? stmt = node.statement?
@@ -207,8 +215,7 @@ module CoffeeScript
def compile_node(o={}) def compile_node(o={})
delimiter = "\n#{idt}//" delimiter = "\n#{idt}//"
comment = "#{delimiter}#{@lines.join(delimiter)}" write("#{delimiter}#{@lines.join(delimiter)}")
write(comment)
end end
end end
@@ -243,6 +250,7 @@ module CoffeeScript
@arguments << argument @arguments << argument
end end
# Compile a vanilla function call.
def compile_node(o) def compile_node(o)
return write(compile_splat(o)) if splat? return write(compile_splat(o)) if splat?
args = @arguments.map{|a| a.compile(o) }.join(', ') args = @arguments.map{|a| a.compile(o) }.join(', ')
@@ -250,6 +258,7 @@ module CoffeeScript
write("#{prefix}#{@variable.compile(o)}(#{args})") write("#{prefix}#{@variable.compile(o)}(#{args})")
end end
# Compile a call against the superclass's implementation of the current function.
def compile_super(args, o) def compile_super(args, o)
methname = o[:last_assign] methname = o[:last_assign]
arg_part = args.empty? ? '' : ", #{args}" arg_part = args.empty? ? '' : ", #{args}"
@@ -258,6 +267,7 @@ module CoffeeScript
"#{meth}.call(this#{arg_part})" "#{meth}.call(this#{arg_part})"
end end
# Compile a function call being passed variable arguments.
def compile_splat(o) def compile_splat(o)
meth = @variable.compile(o) meth = @variable.compile(o)
obj = @variable.source || 'this' obj = @variable.source || 'this'
@@ -280,6 +290,7 @@ module CoffeeScript
@sub_object, @super_object = sub_object, super_object @sub_object, @super_object = sub_object, super_object
end end
# Hooking one constructor into another's prototype chain.
def compile_node(o={}) def compile_node(o={})
constructor = o[:scope].free_variable constructor = o[:scope].free_variable
sub, sup = @sub_object.compile(o), @super_object.compile(o) sub, sup = @sub_object.compile(o), @super_object.compile(o)
@@ -294,10 +305,10 @@ module CoffeeScript
# A value, indexed or dotted into, or vanilla. # A value, indexed or dotted into, or vanilla.
class ValueNode < Node class ValueNode < Node
attr_reader :literal, :properties, :last, :source attr_reader :base, :properties, :last, :source
def initialize(literal, properties=[]) def initialize(base, properties=[])
@literal, @properties = literal, properties @base, @properties = base, properties
end end
def <<(other) def <<(other)
@@ -309,23 +320,23 @@ module CoffeeScript
return !@properties.empty? return !@properties.empty?
end end
# Values are statements if their base is a statement.
def statement? def statement?
@literal.is_a?(Node) && @literal.statement? && !properties? @base.is_a?(Node) && @base.statement? && !properties?
end end
def compile_node(o) def compile_node(o)
only = o.delete(:only_first) only = o.delete(:only_first)
props = only ? @properties[0...-1] : @properties props = only ? @properties[0...-1] : @properties
parts = [@literal, props].flatten.map do |val| parts = [@base, props].flatten.map {|val| val.compile(o) }
val.respond_to?(:compile) ? val.compile(o) : val.to_s
end
@last = parts.last @last = parts.last
@source = parts.length > 1 ? parts[0...-1].join('') : nil @source = parts.length > 1 ? parts[0...-1].join('') : nil
write(parts.join('')) write(parts.join(''))
end end
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 class AccessorNode < Node
attr_reader :name attr_reader :name
@@ -365,14 +376,6 @@ module CoffeeScript
@exclusive @exclusive
end end
def less_operator
@exclusive ? '<' : '<='
end
def greater_operator
@exclusive ? '>' : '>='
end
def compile_variables(o) def compile_variables(o)
@indent = o[:indent] @indent = o[:indent]
@from_var, @to_var = o[:scope].free_variable, o[:scope].free_variable @from_var, @to_var = o[:scope].free_variable, o[:scope].free_variable
@@ -381,12 +384,13 @@ module CoffeeScript
end end
def compile_node(o) def compile_node(o)
return compile_array(o) unless o[:index]
idx, step = o.delete(:index), o.delete(:step) idx, step = o.delete(:index), o.delete(:step)
return compile_array(o) unless idx vars = "#{idx}=#{@from_var}"
vars = "#{idx}=#{@from_var}" step = step ? step.compile(o) : '1'
step = step ? step.compile(o) : '1' equals = @exclusive ? '' : '='
compare = "(#{@from_var} <= #{@to_var} ? #{idx} #{less_operator} #{@to_var} : #{idx} #{greater_operator} #{@to_var})" compare = "(#{@from_var} <= #{@to_var} ? #{idx} <#{equals} #{@to_var} : #{idx} >#{equals} #{@to_var})"
incr = "(#{@from_var} <= #{@to_var} ? #{idx} += #{step} : #{idx} -= #{step})" incr = "(#{@from_var} <= #{@to_var} ? #{idx} += #{step} : #{idx} -= #{step})"
write("#{vars}; #{compare}; #{incr}") write("#{vars}; #{compare}; #{incr}")
end end
@@ -645,8 +649,8 @@ module CoffeeScript
def compile_node(o) def compile_node(o)
top_level = o.delete(:top) && !o[:return] top_level = o.delete(:top) && !o[:return]
range = @source.is_a?(ValueNode) && @source.literal.is_a?(RangeNode) && @source.properties.empty? range = @source.is_a?(ValueNode) && @source.base.is_a?(RangeNode) && @source.properties.empty?
source = range ? @source.literal : @source source = range ? @source.base : @source
scope = o[:scope] scope = o[:scope]
name_found = @name && scope.find(@name) name_found = @name && scope.find(@name)
index_found = @index && scope.find(@index) index_found = @index && scope.find(@index)

View File

@@ -18,6 +18,10 @@ module CoffeeScript
to_str.to_sym to_str.to_sym
end end
def compile(o={})
to_s
end
def inspect def inspect
@value.inspect @value.inspect
end end

View File

@@ -17,15 +17,15 @@ class ParserTest < Test::Unit::TestCase
assert nodes.length == 1 assert nodes.length == 1
assign = nodes.first assign = nodes.first
assert assign.is_a?(AssignNode) assert assign.is_a?(AssignNode)
assert assign.variable.literal == 'a' assert assign.variable.base == 'a'
end end
def test_parsing_an_object_literal def test_parsing_an_object_literal
nodes = @par.parse("{one : 1\ntwo : 2}").expressions nodes = @par.parse("{one : 1\ntwo : 2}").expressions
obj = nodes.first.literal obj = nodes.first.base
assert obj.is_a?(ObjectNode) assert obj.is_a?(ObjectNode)
assert obj.properties.first.variable.literal.value == "one" assert obj.properties.first.variable.base.value == "one"
assert obj.properties.last.variable.literal.value == "two" assert obj.properties.last.variable.base.value == "two"
end end
def test_parsing_an_function_definition def test_parsing_an_function_definition
@@ -39,17 +39,17 @@ class ParserTest < Test::Unit::TestCase
def test_parsing_if_statement def test_parsing_if_statement
the_if = @par.parse("clap_your_hands() if happy").expressions.first the_if = @par.parse("clap_your_hands() if happy").expressions.first
assert the_if.is_a?(IfNode) 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.is_a?(CallNode)
assert the_if.body.variable.literal == 'clap_your_hands' assert the_if.body.variable.base == 'clap_your_hands'
end end
def test_parsing_array_comprehension 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 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.is_a?(ForNode)
assert nodes.first.body.literal == 'i' assert nodes.first.body.base == 'i'
assert nodes.first.filter.operator == '===' 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 end
def test_parsing_comment def test_parsing_comment
@@ -66,7 +66,7 @@ class ParserTest < Test::Unit::TestCase
nodes = @par.parse(File.read('test/fixtures/generation/each.coffee')) nodes = @par.parse(File.read('test/fixtures/generation/each.coffee'))
assign = nodes.expressions[1] assign = nodes.expressions[1]
assert assign.is_a?(AssignNode) assert assign.is_a?(AssignNode)
assert assign.variable.literal == '_' assert assign.variable.base == '_'
assert assign.value.is_a?(CodeNode) assert assign.value.is_a?(CodeNode)
assert assign.value.params == ['obj', 'iterator', 'context'] assert assign.value.params == ['obj', 'iterator', 'context']
end end