diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage
index 88bb3bff..e2922226 100644
--- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage
+++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage
@@ -39,7 +39,7 @@
comment
match stuff like: funcName: => …
match
- ([a-zA-Z0-9_?.$]*)\s*(=|:)\s*([\w,\s]*?)\s*(=>)
+ ([a-zA-Z0-9_?.$*]*)\s*(=|:)\s*([\w,\s]*?)\s*(=>)
name
meta.function.coffee
@@ -60,7 +60,7 @@
comment
match stuff like: a => …
match
- ([a-zA-Z0-9_?., $]*)\s*(=>)
+ ([a-zA-Z0-9_?., $*]*)\s*(=>)
name
meta.inline.function.coffee
diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y
index 2b747738..049482f3 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 NEW RETURN
+token CODE PARAM SPLAT NEW RETURN
token TRY CATCH FINALLY THROW
token BREAK CONTINUE
token FOR IN WHILE
@@ -187,8 +187,13 @@ rule
# The parameters to a function definition.
ParamList:
- PARAM { result = val }
- | ParamList "," PARAM { result = val[0] << val[2] }
+ Param { result = val }
+ | ParamList "," Param { result = val[0] << val[2] }
+ ;
+
+ Param:
+ PARAM
+ | SPLAT { result = SplatNode.new(val[0]) }
;
# Expressions that can be treated as values.
diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb
index 48a0a32f..02341497 100644
--- a/lib/coffee_script/lexer.rb
+++ b/lib/coffee_script/lexer.rb
@@ -190,15 +190,23 @@ module CoffeeScript
# A source of ambiguity in our grammar was parameter lists in function
# definitions (as opposed to argument lists in function calls). Tag
- # parameter identifiers in order to avoid this.
+ # parameter identifiers in order to avoid this. Also, parameter lists can
+ # make use of splats.
def tag_parameters
- index = 0
+ i = 0
loop do
- tok = @tokens[index -= 1]
+ i -= 1
+ tok, prev = @tokens[i], @tokens[i - 1]
return if !tok
next if tok[0] == ','
return if tok[0] != :IDENTIFIER
- tok[0] = :PARAM
+ if prev && prev[0] == '*'
+ tok[0] = :SPLAT
+ @tokens.delete_at(i - 1)
+ i -= 1
+ else
+ tok[0] = :PARAM
+ end
end
end
diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb
index 060ae24d..d2683292 100644
--- a/lib/coffee_script/nodes.rb
+++ b/lib/coffee_script/nodes.rb
@@ -64,6 +64,11 @@ module CoffeeScript
self
end
+ def unshift(node)
+ @expressions.unshift(node)
+ self
+ end
+
# If this Expressions consists of a single node, pull it back out.
def unwrap
@expressions.length == 1 ? @expressions.first : self
@@ -389,7 +394,7 @@ module CoffeeScript
o[:scope].find(name) unless @variable.properties?
return write(@value.compile(o)) if @value.custom_assign?
val = "#{name} = #{@value.compile(o)}"
- write(o[:return] && !@value.custom_return? ? "return (#{val})" : val)
+ write(o[:return] && !@value.custom_return? ? "#{o[:indent]}return (#{val})" : val)
end
end
@@ -459,12 +464,35 @@ module CoffeeScript
o.delete(:no_wrap)
name = o.delete(:immediate_assign)
@params.each {|id| o[:scope].parameter(id.to_s) }
+ if @params.last.is_a?(SplatNode)
+ splat = @params.pop
+ splat.index = @params.length
+ @body.unshift(splat)
+ end
code = @body.compile(o, :code)
name_part = name ? " #{name}" : ''
write("function#{name_part}(#{@params.join(', ')}) {\n#{code}\n#{indent}}")
end
end
+ # A parameter splat in a function definition.
+ class SplatNode < Node
+ attr_accessor :index
+ attr_reader :name
+
+ def initialize(name)
+ @name = name
+ end
+
+ def to_s
+ @name
+ end
+
+ def compile(o={})
+ "var #{@name} = Array.prototype.slice.call(arguments, #{@index})"
+ 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
new file mode 100644
index 00000000..6b273137
--- /dev/null
+++ b/test/fixtures/execution/test_splats.coffee
@@ -0,0 +1,6 @@
+func: first, second, *rest =>
+ rest.join(' ')
+
+result: func(1, 2, 3, 4, 5)
+
+print(result is "3 4 5")
\ No newline at end of file