diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 049482f3..e83ad1d1 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -5,7 +5,7 @@ token IF ELSE UNLESS token NUMBER STRING REGEX token TRUE FALSE YES NO ON OFF token IDENTIFIER PROPERTY_ACCESS -token CODE PARAM SPLAT NEW RETURN +token CODE PARAM NEW RETURN token TRY CATCH FINALLY THROW token BREAK CONTINUE token FOR IN WHILE @@ -19,7 +19,7 @@ token INDENT OUTDENT # Declare order of operations. prechigh - nonassoc UMINUS NOT '!' '!!' '~' '++' '--' + nonassoc UMINUS SPLAT NOT '!' '!!' '~' '++' '--' left '*' '/' '%' left '+' '-' left '<<' '>>' '>>>' @@ -70,6 +70,7 @@ rule | For | Switch | Extends + | Splat | Comment ; @@ -193,7 +194,11 @@ rule Param: PARAM - | SPLAT { result = SplatNode.new(val[0]) } + | '*' PARAM = SPLAT { result = ParamSplatNode.new(val[1]) } + ; + + Splat: + '*' Value = SPLAT { result = ArgSplatNode.new(val[1]) } ; # Expressions that can be treated as values. diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 02341497..58789815 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -196,17 +196,11 @@ module CoffeeScript i = 0 loop do i -= 1 - tok, prev = @tokens[i], @tokens[i - 1] + tok = @tokens[i] return if !tok - next if tok[0] == ',' + next if ['*', ','].include?(tok[0]) return if tok[0] != :IDENTIFIER - if prev && prev[0] == '*' - tok[0] = :SPLAT - @tokens.delete_at(i - 1) - i -= 1 - else - tok[0] = :PARAM - end + tok[0] = :PARAM end end diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 737709d1..682b5112 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -212,11 +212,16 @@ module CoffeeScript @variable == :super end + def prefix + @new ? "new " : '' + end + def compile(o={}) o = super(o) + @splat = @arguments.detect {|a| a.is_a?(ArgSplatNode) } + return write(compile_splat(o)) if @splat args = @arguments.map{|a| a.compile(o) }.join(', ') return write(compile_super(args, o)) if super? - prefix = @new ? "new " : '' write("#{prefix}#{@variable.compile(o)}(#{args})") end @@ -225,6 +230,17 @@ module CoffeeScript arg_part = args.empty? ? '' : ", #{args}" "#{o[:proto_assign]}.__superClass__.#{methname}.call(this#{arg_part})" end + + def compile_splat(o) + meth = @variable.compile(o) + obj = @variable.source || 'this' + args = @arguments.map do |arg| + code = arg.compile(o) + code = arg == @splat ? code : "[#{code}]" + arg.equal?(@arguments.first) ? code : ".concat(#{code})" + end + "#{prefix}#{meth}.apply(#{obj}, #{args.join('')})" + end end # Node to extend an object's prototype with an ancestor object. @@ -247,7 +263,7 @@ module CoffeeScript # A value, indexed or dotted into, or vanilla. class ValueNode < Node - attr_reader :literal, :properties, :last + attr_reader :literal, :properties, :last, :source def initialize(literal, properties=[]) @literal, @properties = literal, properties @@ -280,6 +296,7 @@ module CoffeeScript val.respond_to?(:compile) ? val.compile(o) : val.to_s end @last = parts.last + @source = parts.length > 1 ? parts[0...-1].join('') : nil write(parts.join('')) end end @@ -463,7 +480,7 @@ module CoffeeScript o.delete(:assign) o.delete(:no_wrap) name = o.delete(:immediate_assign) - if @params.last.is_a?(SplatNode) + if @params.last.is_a?(ParamSplatNode) splat = @params.pop splat.index = @params.length @body.unshift(splat) @@ -476,7 +493,7 @@ module CoffeeScript end # A parameter splat in a function definition. - class SplatNode < Node + class ParamSplatNode < Node attr_accessor :index attr_reader :name @@ -486,10 +503,23 @@ module CoffeeScript def compile(o={}) o[:scope].find(@name) - "#{@name} = Array.prototype.slice.call(arguments, #{@index})" + write("#{@name} = Array.prototype.slice.call(arguments, #{@index})") end end + class ArgSplatNode < Node + attr_reader :value + + def initialize(value) + @value = value + end + + def compile(o={}) + write(@value.compile(o)) + end + + end + # An object literal. class ObjectNode < Node attr_reader :properties diff --git a/test/fixtures/execution/test_splats.coffee b/test/fixtures/execution/test_splats.coffee index 6b273137..c5881bbe 100644 --- a/test/fixtures/execution/test_splats.coffee +++ b/test/fixtures/execution/test_splats.coffee @@ -3,4 +3,33 @@ func: first, second, *rest => result: func(1, 2, 3, 4, 5) -print(result is "3 4 5") \ No newline at end of file +print(result is "3 4 5") + + +gold: silver: bronze: the_field: null + +medalists: first, second, third, *rest => + gold: first + silver: second + bronze: third + the_field: rest + +contenders: [ + "Michael Phelps" + "Liu Xiang" + "Yao Ming" + "Allyson Felix" + "Shawn Johnson" + "Roman Sebrle" + "Guo Jingjing" + "Tyson Gay" + "Asafa Powell" + "Usain Bolt" +] + +medalists("Mighty Mouse", *contenders) + +print(gold is "Mighty Mouse") +print(silver is "Michael Phelps") +print(bronze is "Liu Xiang") +print(the_field.length is 8) \ No newline at end of file diff --git a/test/unit/test_execution.rb b/test/unit/test_execution.rb index 64efc8f8..04186305 100644 --- a/test/unit/test_execution.rb +++ b/test/unit/test_execution.rb @@ -2,30 +2,35 @@ require 'test_helper' class ExecutionTest < Test::Unit::TestCase - NO_WARNINGS = /\A(0 error\(s\), 0 warning\(s\)\n)+\Z/ - ALLS_WELL = /\A\n?(true\n)+\Z/m + NO_WARNINGS = "0 error(s), 0 warning(s)" # This is by far the most important test. It evaluates all of the # CoffeeScript in test/fixtures/execution, ensuring that all our # syntax actually works. def test_execution_of_coffeescript sources = ['test/fixtures/execution/*.coffee'].join(' ') - assert `bin/coffee -r #{sources}`.match(ALLS_WELL) + (`bin/coffee -r #{sources}`).split("\n").each do |line| + assert line == "true" + end end def test_lintless_coffeescript - lint_results = `bin/coffee -l test/fixtures/execution/*.coffee` - assert lint_results.match(NO_WARNINGS) + no_warnings `bin/coffee -l test/fixtures/execution/*.coffee` end def test_lintless_examples - lint_results = `bin/coffee -l examples/*.coffee` - assert lint_results.match(NO_WARNINGS) + no_warnings `bin/coffee -l examples/*.coffee` end def test_lintless_documentation - lint_results = `bin/coffee -l documentation/coffee/*.coffee` - assert lint_results.match(NO_WARNINGS) + no_warnings `bin/coffee -l documentation/coffee/*.coffee` + end + + + private + + def no_warnings(output) + output.split("\n").each {|line| assert line == NO_WARNINGS } end end