adding splats as arguments to function calls

This commit is contained in:
Jeremy Ashkenas 2009-12-31 19:52:13 -05:00
parent abfc9f5a2d
commit 409283a30f
5 changed files with 90 additions and 27 deletions

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -3,4 +3,33 @@ func: first, second, *rest =>
result: func(1, 2, 3, 4, 5)
print(result is "3 4 5")
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)

View File

@ -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