Skip to content
2 changes: 2 additions & 0 deletions lib/coffee-script/grammar.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions lib/coffee-script/lexer.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

74 changes: 31 additions & 43 deletions lib/coffee-script/nodes.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion lib/coffee-script/parser.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/coffee-script/rewriter.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/grammar.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -560,12 +560,12 @@ grammar =
ForBody: [
o 'FOR Range', -> source: (LOC(2) new Value($2))
o 'FOR Range BY Expression', -> source: (LOC(2) new Value($2)), step: $4
o 'ForStart ForSource', -> $2.own = $1.own; $2.name = $1[0]; $2.index = $1[1]; $2
o 'ForStart ForSource', -> $2.own = $1.own; $2.ownTag = $1.ownTag; $2.name = $1[0]; $2.index = $1[1]; $2
]

ForStart: [
o 'FOR ForVariables', -> $2
o 'FOR OWN ForVariables', -> $3.own = yes; $3
o 'FOR OWN ForVariables', -> $3.own = yes; $3.ownTag = (LOC(2) new Literal($2)); $3
]

# An array of all accepted values for a variable inside the loop.
Expand Down
60 changes: 26 additions & 34 deletions src/nodes.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -2251,17 +2251,13 @@ exports.For = class For extends While
@object = !!source.object
@from = !!source.from
@index.error 'cannot use index with for-from' if @from and @index

source.ownTag.error "cannot use own with for-#{if @from then 'from' else 'in'}" if @own and not @object
[@name, @index] = [@index, @name] if @object

@index.error 'index cannot be a pattern matching expression' if @index instanceof Value
@range = @source instanceof Value and @source.base instanceof Range and not @source.properties.length and not @from
@pattern = @name instanceof Value
@index.error 'indexes do not apply to range loops' if @range and @index
@name.error 'cannot pattern match over range loops' if @range and @pattern
if @own and not @object
@name.error 'cannot use own with for-from' if @from
@name.error 'cannot use own with for-in' if not @from
@returns = false

children: ['body', 'source', 'guard', 'step']
Expand Down Expand Up @@ -2299,33 +2295,32 @@ exports.For = class For extends While
forPartFragments = source.compileToFragments merge o,
{index: ivar, name, @step, isComplex: isComplexOrAssignable}
else
svar = @source.compile o, LEVEL_LIST
svar = @source.compile o, LEVEL_LIST
if (name or @own) and @source.unwrap() not instanceof IdentifierLiteral
defPart += "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n"
svar = ref
unless @from
if name and not @pattern
namePart = "#{name} = #{svar}[#{kvar}]"
unless @object
defPart += "#{@tab}#{step};\n" if step isnt stepVar
down = stepNum < 0
lvar = scope.freeVariable 'len' unless @step and stepNum? and down
declare = "#{kvarAssign}#{ivar} = 0, #{lvar} = #{svar}.length"
declareDown = "#{kvarAssign}#{ivar} = #{svar}.length - 1"
compare = "#{ivar} < #{lvar}"
compareDown = "#{ivar} >= 0"
if @step
if stepNum?
if down
compare = compareDown
declare = declareDown
else
compare = "#{stepVar} > 0 ? #{compare} : #{compareDown}"
declare = "(#{stepVar} > 0 ? (#{declare}) : #{declareDown})"
increment = "#{ivar} += #{stepVar}"
defPart += "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n"
svar = ref
if name and not @pattern and not @from
namePart = "#{name} = #{svar}[#{kvar}]"
if not @object and not @from
defPart += "#{@tab}#{step};\n" if step isnt stepVar
down = stepNum < 0
lvar = scope.freeVariable 'len' unless @step and stepNum? and down
declare = "#{kvarAssign}#{ivar} = 0, #{lvar} = #{svar}.length"
declareDown = "#{kvarAssign}#{ivar} = #{svar}.length - 1"
compare = "#{ivar} < #{lvar}"
compareDown = "#{ivar} >= 0"
if @step
if stepNum?
if down
compare = compareDown
declare = declareDown
else
increment = "#{if kvar isnt ivar then "++#{ivar}" else "#{ivar}++"}"
forPartFragments = [@makeCode("#{declare}; #{compare}; #{kvarAssign}#{increment}")]
compare = "#{stepVar} > 0 ? #{compare} : #{compareDown}"
declare = "(#{stepVar} > 0 ? (#{declare}) : #{declareDown})"
increment = "#{ivar} += #{stepVar}"
else
increment = "#{if kvar isnt ivar then "++#{ivar}" else "#{ivar}++"}"
forPartFragments = [@makeCode("#{declare}; #{compare}; #{kvarAssign}#{increment}")]
if @returns
resultPart = "#{@tab}#{rvar} = [];\n"
returnResult = "\n#{@tab}return #{rvar};"
Expand All @@ -2336,10 +2331,7 @@ exports.For = class For extends While
else
body = Block.wrap [new If @guard, body] if @guard
if @pattern
if @from
body.expressions.unshift new Assign @name, new IdentifierLiteral kvar
else
body.expressions.unshift new Assign @name, new Literal "#{svar}[#{kvar}]"
body.expressions.unshift new Assign @name, if @from then new IdentifierLiteral kvar else new Literal "#{svar}[#{kvar}]"
defPartFragments = [].concat @makeCode(defPart), @pluckDirectCall(o, body)
varPart = "\n#{idt1}#{namePart};" if namePart
if @object
Expand Down
3 changes: 2 additions & 1 deletion src/rewriter.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,8 @@ class exports.Rewriter
fixOutdentLocationData: ->
@scanTokens (token, i, tokens) ->
return 1 unless token[0] is 'OUTDENT' or
(token.generated and token[0] is 'CALL_END')
(token.generated and token[0] is 'CALL_END') or
(token.generated and token[0] is '}')
prevLocationData = tokens[i - 1][2]
token[2] =
first_line: prevLocationData.last_line
Expand Down
3 changes: 0 additions & 3 deletions test/compilation.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,3 @@ test "#3001: `own` shouldn't be allowed in a `for`-`in` loop", ->

test "#2994: single-line `if` requires `then`", ->
cantCompile "if b else x"

test "own is not supported in for-from loops", ->
cantCompile "x for own x from [1, 2, 3]"
7 changes: 7 additions & 0 deletions test/error_messages.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -1181,3 +1181,10 @@ test "indexes are not supported in for-from loops", ->
x for x, i from [1, 2, 3]
^
'''

test "own is not supported in for-from loops", ->
assertErrorFormat "x for own x from [1, 2, 3]", '''
[stdin]:1:7: error: cannot use own with for-from
x for own x from [1, 2, 3]
^^^
'''
2 changes: 0 additions & 2 deletions test/generators.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,6 @@ test "yield handles 'this' correctly", ->

throws -> y.next new Error "boom"


test "for-from loops over generators", ->
array1 = [50, 30, 70, 20]
gen = -> yield from array1
Expand Down Expand Up @@ -289,4 +288,3 @@ test "for-from comprehensions over generators", ->

ok array1.join(' ') is '41 51'
ok array2.length is 0

21 changes: 21 additions & 0 deletions test/location.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,27 @@ test "Verify OUTDENT and CALL_END tokens are located at the end of the previous
eq token[0], 'CALL_END'
assertAtCloseCurly(token)

test "Verify generated } tokens are located at the end of the previous token", ->
source = '''
a(b, ->
c: () ->
if d
e
)
'''
tokens = CoffeeScript.tokens source
[..., identifier, outdent1, outdent2, closeCurly, outdent3, callEnd,
terminator] = tokens
eq identifier[0], 'IDENTIFIER'
assertAtIdentifier = (token) ->
eq token[2].first_line, identifier[2].last_line
eq token[2].first_column, identifier[2].last_column
eq token[2].last_line, identifier[2].last_line
eq token[2].last_column, identifier[2].last_column

for token in [outdent1, outdent2, closeCurly, outdent3]
assertAtIdentifier(token)

test "Verify real CALL_END tokens have the right position", ->
source = '''
a()
Expand Down
2 changes: 1 addition & 1 deletion test/ranges.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ test "for-from loops over ranges", ->
array1 = []
for x from [20..30]
array1.push(x)
break if x == 25
break if x is 25
arrayEq array1, [20, 21, 22, 23, 24, 25]

test "for-from comprehensions over ranges", ->
Expand Down