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:
parent
2f75854a61
commit
6f81ac3684
5 changed files with 85 additions and 25 deletions
7
TODO
7
TODO
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue