1
0
Fork 0
mirror of https://github.com/jashkenas/coffeescript.git synced 2022-11-09 12:23:24 -05:00

patched up array comprehensions somewhat. Parens are still a necessary evil, and there's still probably plenty of edge cases

This commit is contained in:
Jeremy Ashkenas 2009-12-18 22:30:09 -05:00
parent 2f75854a61
commit 6f81ac3684
5 changed files with 85 additions and 25 deletions

7
TODO
View file

@ -2,6 +2,13 @@ TODO:
* Finish the examples.
* Write a test suite that checks the JS evaluation.
* Figure out a generic way to transform statements into expressions, and
use it recursively for returns and assigns on whiles, fors, ifs, etc.
* If we manage to get array comprehensions working ... object comprehensions?
* Create the documentation page. (amy, idle)
uv -c . -s coffeescript -t amy --no-lines examples/code.cs > code.html

View file

@ -109,9 +109,9 @@ change_a_and_set_b: =>
b: 20
# Array comprehensions.
supper: food.capitalize() for food in ['toast', 'cheese', 'wine'].
supper: [food.capitalize() for food in ['toast', 'cheese', 'wine']]
drink(bottle) for bottle, i in ['soda', 'wine', 'lemonade'] if even(i).
[drink(bottle) for bottle, i in ['soda', 'wine', 'lemonade'] if even(i)]
# Switch statements ("else" serves as a default).
switch day

View file

@ -26,14 +26,14 @@ prechigh
left ':'
right '-=' '+=' '/=' '*=' '||=' '&&='
right DELETE
right RETURN THROW FOR WHILE
right RETURN THROW FOR IN WHILE
left UNLESS IF ELSE
nonassoc "."
preclow
# We expect 2 shift/reduce errors for optional syntax.
# We expect 4 shift/reduce errors for optional syntax.
# There used to be 252 -- greatly improved.
expect 2
expect 4
rule
@ -54,12 +54,22 @@ rule
# All types of expressions in our language.
Expression:
PureExpression
| Statement
;
# The parts that are natural JavaScript expressions.
PureExpression:
Literal
| Value
| Call
| Assign
| Code
| Operation
;
# We have to take extra care to convert these statements into expressions.
Statement:
Assign
| If
| Try
| Throw
@ -267,17 +277,17 @@ rule
# Array comprehensions, including guard and current index.
For:
Expression FOR IDENTIFIER
IN Expression "." { result = ForNode.new(val[0], val[4], val[2]) }
IN PureExpression "." { result = ForNode.new(val[0], val[4], val[2], nil) }
| Expression FOR
IDENTIFIER "," IDENTIFIER
IN Expression "." { result = ForNode.new(val[0], val[6], val[2], val[4]) }
IN PureExpression "." { result = ForNode.new(val[0], val[6], val[2], nil, val[4]) }
| Expression FOR IDENTIFIER
IN Expression
IF Expression "." { result = ForNode.new(IfNode.new(val[6], Expressions.new([val[0]])), val[4], val[2]) }
IN PureExpression
IF Expression "." { result = ForNode.new(val[0], val[4], val[2], val[6]) }
| Expression FOR
IDENTIFIER "," IDENTIFIER
IN Expression
IF Expression "." { result = ForNode.new(IfNode.new(val[8], Expressions.new([val[0]])), val[6], val[2], val[4]) }
IN PureExpression
IF Expression "." { result = ForNode.new(val[0], val[6], val[2], val[8], val[4]) }
;
# Switch/Case blocks.

View file

@ -169,9 +169,21 @@ module CoffeeScript
return !@properties.empty?
end
def statement?
@literal.is_a?(Node) && @literal.statement? && !properties?
end
def custom_assign?
@literal.is_a?(Node) && @literal.custom_assign? && !properties?
end
def custom_return?
@literal.is_a?(Node) && @literal.custom_return? && !properties?
end
def compile(indent, scope, opts={})
parts = [@literal, @properties].flatten.map do |v|
v.respond_to?(:compile) ? v.compile(indent, scope) : v.to_s
v.respond_to?(:compile) ? v.compile(indent, scope, opts) : v.to_s
end
@last = parts.last
parts.join('')
@ -364,10 +376,10 @@ module CoffeeScript
custom_return
custom_assign
attr_reader :body, :source, :name, :index
attr_reader :body, :source, :name, :filter, :index
def initialize(body, source, name, index=nil)
@body, @source, @name, @index = body, source, name, index
def initialize(body, source, name, filter, index=nil)
@body, @source, @name, @filter, @index = body, source, name, filter, index
end
def line_ending
@ -388,18 +400,28 @@ module CoffeeScript
set_result = ''
save_result = ''
return_result = ''
body = @body
suffix = ';'
if opts[:return] || opts[:assign]
rvar = scope.free_variable
set_result = "var #{rvar} = [];\n#{indent}"
save_result = "#{rvar}[#{ivar}] = "
save_result += "#{rvar}[#{ivar}] = "
return_result = rvar
return_result = "#{opts[:assign]} = #{return_result}" if opts[:assign]
return_result = "return #{return_result}" if opts[:return]
return_result = "\n#{indent}#{return_result}"
if @filter
body = CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [@body])
body = @filter ? IfNode.new(@filter, body, nil, :statement) : body
save_result = ''
suffix = ''
end
elsif @filter
body = IfNode.new(@filter, @body)
end
body = @body.compile(indent + TAB, scope)
"#{source_part}\n#{indent}#{set_result}for (#{for_part}) {#{var_part}#{index_part}#{indent + TAB}#{save_result}#{body};\n#{indent}}#{return_result}"
body = body.compile(indent + TAB, scope)
"#{source_part}\n#{indent}#{set_result}for (#{for_part}) {#{var_part}#{index_part}#{indent + TAB}#{save_result}#{body}#{suffix}\n#{indent}}#{return_result}"
end
end
@ -444,13 +466,25 @@ module CoffeeScript
attr_reader :expressions
def initialize(expressions)
@expressions = expressions
@expressions = expressions.unwrap
end
def statement?
@expressions.statement?
end
def custom_assign?
@expressions.custom_assign?
end
def custom_return?
@expressions.custom_return?
end
def compile(indent, scope, opts={})
compiled = @expressions.unwrap.compile(indent, scope)
compiled = @expressions.compile(indent, scope, opts)
compiled = compiled[0...-1] if compiled[-1..-1] == ';'
opts[:no_paren] ? compiled : "(#{compiled})"
opts[:no_paren] || statement? ? compiled : "(#{compiled})"
end
end
@ -465,7 +499,8 @@ module CoffeeScript
@condition = condition
@body = body && body.unwrap
@else_body = else_body && else_body.unwrap
@condition = OpNode.new("!", @condition) if tag == :invert
@tag = tag
@condition = OpNode.new("!", @condition) if @tag == :invert
end
def <<(else_body)
@ -495,7 +530,7 @@ module CoffeeScript
# The IfNode only compiles into a statement if either of the bodies needs
# to be a statement.
def statement?
@is_statement ||= (@body.statement? || (@else_body && @else_body.statement?))
@is_statement ||= ((@tag == :statement) || @body.statement? || (@else_body && @else_body.statement?))
end
def custom_return?

View file

@ -36,7 +36,7 @@ class ParserTest < Test::Unit::TestCase
assert body.operator == '*'
end
def test_lexing_if_statement
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'
@ -44,6 +44,14 @@ class ParserTest < Test::Unit::TestCase
assert the_if.body.variable.literal == 'clap_your_hands'
end
def test_parsing_array_comprehension
nodes = @par.parse("i for x, i in [10, 9, 8, 7, 6, 5] if i % 2 is 0.").expressions
assert nodes.first.is_a? ForNode
assert nodes.first.body.literal == 'i'
assert nodes.first.filter.operator == '==='
assert nodes.first.source.literal.objects.last.value == "5"
end
def test_parsing
nodes = @par.parse(File.read('test/fixtures/each.cs'))
assign = nodes.expressions.first