Merge branch 'master' into 2

# Conflicts:
#	lib/coffee-script/lexer.js
#	lib/coffee-script/nodes.js
#	lib/coffee-script/optparse.js
#	lib/coffee-script/rewriter.js
#	lib/coffee-script/scope.js
#	lib/coffee-script/sourcemap.js
#	src/nodes.coffee
#	test/classes.coffee
#	test/comments.coffee
#	test/error_messages.coffee
This commit is contained in:
Geoffrey Booth 2016-11-10 22:51:39 -08:00
commit 9524159e68
16 changed files with 254 additions and 66 deletions

View File

@ -581,6 +581,7 @@
};
}), o('ForStart ForSource', function() {
$2.own = $1.own;
$2.ownTag = $1.ownTag;
$2.name = $1[0];
$2.index = $1[1];
return $2;
@ -591,6 +592,7 @@
return $2;
}), o('FOR OWN ForVariables', function() {
$3.own = true;
$3.ownTag = LOC(2)(new Literal($2));
return $3;
})
],
@ -646,6 +648,17 @@
step: $4,
guard: $6
};
}), o('FORFROM Expression', function() {
return {
source: $2,
from: true
};
}), o('FORFROM Expression WHEN Expression', function() {
return {
source: $2,
guard: $4,
from: true
};
})
],
Switch: [
@ -764,7 +777,7 @@
]
};
operators = [['left', '.', '?.', '::', '?::'], ['left', 'CALL_START', 'CALL_END'], ['nonassoc', '++', '--'], ['left', '?'], ['right', 'UNARY'], ['right', 'AWAIT'], ['right', '**'], ['right', 'UNARY_MATH'], ['left', 'MATH'], ['left', '+', '-'], ['left', 'SHIFT'], ['left', 'RELATION'], ['left', 'COMPARE'], ['left', '&'], ['left', '^'], ['left', '|'], ['left', '&&'], ['left', '||'], ['left', 'BIN?'], ['nonassoc', 'INDENT', 'OUTDENT'], ['right', 'YIELD'], ['right', '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS'], ['right', 'FORIN', 'FOROF', 'BY', 'WHEN'], ['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS', 'IMPORT', 'EXPORT'], ['left', 'POST_IF']];
operators = [['left', '.', '?.', '::', '?::'], ['left', 'CALL_START', 'CALL_END'], ['nonassoc', '++', '--'], ['left', '?'], ['right', 'UNARY'], ['right', 'AWAIT'], ['right', '**'], ['right', 'UNARY_MATH'], ['left', 'MATH'], ['left', '+', '-'], ['left', 'SHIFT'], ['left', 'RELATION'], ['left', 'COMPARE'], ['left', '&'], ['left', '^'], ['left', '|'], ['left', '&&'], ['left', '||'], ['left', 'BIN?'], ['nonassoc', 'INDENT', 'OUTDENT'], ['right', 'YIELD'], ['right', '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS'], ['right', 'FORIN', 'FOROF', 'FORFROM', 'BY', 'WHEN'], ['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS', 'IMPORT', 'EXPORT'], ['left', 'POST_IF']];
tokens = [];

View File

@ -128,6 +128,9 @@
}
}
}
} else if (tag === 'IDENTIFIER' && this.seenFor && id === 'from') {
tag = 'FORFROM';
this.seenFor = false;
}
if (tag === 'IDENTIFIER' && indexOf.call(RESERVED, id) >= 0) {
this.error("reserved word '" + id + "'", {

View File

@ -3263,13 +3263,20 @@
this.body = Block.wrap([body]);
this.own = !!source.own;
this.object = !!source.object;
this.from = !!source.from;
if (this.from && this.index) {
this.index.error('cannot use index with for-from');
}
if (this.own && !this.object) {
source.ownTag.error("cannot use own with for-" + (this.from ? 'from' : 'in'));
}
if (this.object) {
ref3 = [this.index, this.name], this.name = ref3[0], this.index = ref3[1];
}
if (this.index instanceof Value) {
this.index.error('index cannot be a pattern matching expression');
}
this.range = this.source instanceof Value && this.source.base instanceof Range && !this.source.properties.length;
this.range = this.source instanceof Value && this.source.base instanceof Range && !this.source.properties.length && !this.from;
this.pattern = this.name instanceof Value;
if (this.range && this.index) {
this.index.error('indexes do not apply to range loops');
@ -3277,9 +3284,6 @@
if (this.range && this.pattern) {
this.name.error('cannot pattern match over range loops');
}
if (this.own && !this.object) {
this.name.error('cannot use own with for-in');
}
this.returns = false;
}
@ -3307,10 +3311,18 @@
if (this.returns) {
rvar = scope.freeVariable('results');
}
ivar = (this.object && index) || scope.freeVariable('i', {
single: true
});
kvar = (this.range && name) || index || ivar;
if (this.from) {
if (this.pattern) {
ivar = scope.freeVariable('x', {
single: true
});
}
} else {
ivar = (this.object && index) || scope.freeVariable('i', {
single: true
});
}
kvar = ((this.range || this.from) && name) || index || ivar;
kvarAssign = kvar !== ivar ? kvar + " = " : "";
if (this.step && !this.range) {
ref4 = this.cacheToCodeFragments(this.step.cache(o, LEVEL_LIST, isComplexOrAssignable)), step = ref4[0], stepVar = ref4[1];
@ -3338,10 +3350,10 @@
defPart += "" + this.tab + (ref = scope.freeVariable('ref')) + " = " + svar + ";\n";
svar = ref;
}
if (name && !this.pattern) {
if (name && !this.pattern && !this.from) {
namePart = name + " = " + svar + "[" + kvar + "]";
}
if (!this.object) {
if (!this.object && !this.from) {
if (step !== stepVar) {
defPart += "" + this.tab + step + ";\n";
}
@ -3385,7 +3397,7 @@
}
}
if (this.pattern) {
body.expressions.unshift(new Assign(this.name, new Literal(svar + "[" + kvar + "]")));
body.expressions.unshift(new Assign(this.name, this.from ? new IdentifierLiteral(kvar) : new Literal(svar + "[" + kvar + "]")));
}
defPartFragments = [].concat(this.makeCode(defPart), this.pluckDirectCall(o, body));
if (namePart) {
@ -3396,11 +3408,13 @@
if (this.own) {
guardPart = "\n" + idt1 + "if (!" + (utility('hasProp', o)) + ".call(" + svar + ", " + kvar + ")) continue;";
}
} else if (this.from) {
forPartFragments = [this.makeCode(kvar + " of " + svar)];
}
bodyFragments = body.compileToFragments(merge(o, {
indent: idt1
}), LEVEL_TOP);
if (bodyFragments && (bodyFragments.length > 0)) {
if (bodyFragments && bodyFragments.length > 0) {
bodyFragments = [].concat(this.makeCode("\n"), bodyFragments, this.makeCode("\n"));
}
return [].concat(defPartFragments, this.makeCode("" + (resultPart || '') + this.tab + "for ("), forPartFragments, this.makeCode(") {" + guardPart + varPart), bodyFragments, this.makeCode(this.tab + "}" + (returnResult || '')));

File diff suppressed because one or more lines are too long

View File

@ -371,7 +371,7 @@
Rewriter.prototype.fixOutdentLocationData = function() {
return this.scanTokens(function(token, i, tokens) {
var prevLocationData;
if (!(token[0] === 'OUTDENT' || (token.generated && token[0] === 'CALL_END'))) {
if (!(token[0] === 'OUTDENT' || (token.generated && token[0] === 'CALL_END') || (token.generated && token[0] === '}'))) {
return 1;
}
prevLocationData = tokens[i - 1][2];

View File

@ -570,12 +570,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.
@ -606,6 +606,8 @@ grammar =
o 'FORIN Expression BY Expression', -> source: $2, step: $4
o 'FORIN Expression WHEN Expression BY Expression', -> source: $2, guard: $4, step: $6
o 'FORIN Expression BY Expression WHEN Expression', -> source: $2, step: $4, guard: $6
o 'FORFROM Expression', -> source: $2, from: yes
o 'FORFROM Expression WHEN Expression', -> source: $2, guard: $4, from: yes
]
Switch: [
@ -728,7 +730,7 @@ operators = [
['nonassoc', 'INDENT', 'OUTDENT']
['right', 'YIELD']
['right', '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS']
['right', 'FORIN', 'FOROF', 'BY', 'WHEN']
['right', 'FORIN', 'FOROF', 'FORFROM', 'BY', 'WHEN']
['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS', 'IMPORT', 'EXPORT']
['left', 'POST_IF']
]

View File

@ -43,7 +43,7 @@ exports.Lexer = class Lexer
@indentLiteral = '' # The indentation
@ends = [] # The stack for pairing up tokens.
@tokens = [] # Stream of parsed tokens in the form `['TYPE', value, location data]`.
@seenFor = no # Used to recognize FORIN and FOROF tokens.
@seenFor = no # Used to recognize FORIN, FOROF and FORFROM tokens.
@seenImport = no # Used to recognize IMPORT FROM? AS? tokens.
@seenExport = no # Used to recognize EXPORT FROM? AS? tokens.
@exportSpecifierList = no # Used to identify when in an EXPORT {...} FROM? ...
@ -166,6 +166,9 @@ exports.Lexer = class Lexer
if @value() is '!'
poppedToken = @tokens.pop()
id = '!' + id
else if tag is 'IDENTIFIER' and @seenFor and id is 'from'
tag = 'FORFROM'
@seenFor = no
if tag is 'IDENTIFIER' and id in RESERVED
@error "reserved word '#{id}'", length: id.length

View File

@ -2245,13 +2245,15 @@ exports.For = class For extends While
@body = Block.wrap [body]
@own = !!source.own
@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
@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
@name.error 'cannot use own with for-in' if @own and not @object
@returns = false
children: ['body', 'source', 'guard', 'step']
@ -2271,8 +2273,11 @@ exports.For = class For extends While
scope.find(name) if name and not @pattern
scope.find(index) if index
rvar = scope.freeVariable 'results' if @returns
ivar = (@object and index) or scope.freeVariable 'i', single: true
kvar = (@range and name) or index or ivar
if @from
ivar = scope.freeVariable 'x', single: true if @pattern
else
ivar = (@object and index) or scope.freeVariable 'i', single: true
kvar = ((@range or @from) and name) or index or ivar
kvarAssign = if kvar isnt ivar then "#{kvar} = " else ""
if @step and not @range
[step, stepVar] = @cacheToCodeFragments @step.cache o, LEVEL_LIST, isComplexOrAssignable
@ -2290,9 +2295,9 @@ exports.For = class For extends While
if (name or @own) and @source.unwrap() not instanceof IdentifierLiteral
defPart += "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n"
svar = ref
if name and not @pattern
if name and not @pattern and not @from
namePart = "#{name} = #{svar}[#{kvar}]"
if not @object
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
@ -2311,7 +2316,7 @@ exports.For = class For extends While
increment = "#{ivar} += #{stepVar}"
else
increment = "#{if kvar isnt ivar then "++#{ivar}" else "#{ivar}++"}"
forPartFragments = [@makeCode("#{declare}; #{compare}; #{kvarAssign}#{increment}")]
forPartFragments = [@makeCode("#{declare}; #{compare}; #{kvarAssign}#{increment}")]
if @returns
resultPart = "#{@tab}#{rvar} = [];\n"
returnResult = "\n#{@tab}return #{rvar};"
@ -2322,14 +2327,16 @@ exports.For = class For extends While
else
body = Block.wrap [new If @guard, body] if @guard
if @pattern
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
forPartFragments = [@makeCode("#{kvar} in #{svar}")]
forPartFragments = [@makeCode("#{kvar} in #{svar}")]
guardPart = "\n#{idt1}if (!#{utility 'hasProp', o}.call(#{svar}, #{kvar})) continue;" if @own
else if @from
forPartFragments = [@makeCode("#{kvar} of #{svar}")]
bodyFragments = body.compileToFragments merge(o, indent: idt1), LEVEL_TOP
if bodyFragments and (bodyFragments.length > 0)
if bodyFragments and bodyFragments.length > 0
bodyFragments = [].concat @makeCode("\n"), bodyFragments, @makeCode("\n")
[].concat defPartFragments, @makeCode("#{resultPart or ''}#{@tab}for ("),
forPartFragments, @makeCode(") {#{guardPart}#{varPart}"), bodyFragments,

View File

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

View File

@ -114,3 +114,42 @@ test "splat extraction from generators", ->
yield 2
yield 3
arrayEq [ gen()... ], [ 1, 2, 3 ]
test "for-from loops over Array", ->
array1 = [50, 30, 70, 20]
array2 = []
for x from array1
array2.push(x)
arrayEq array1, array2
array1 = [[20, 30], [40, 50]]
array2 = []
for [a, b] from array1
array2.push(b)
array2.push(a)
arrayEq array2, [30, 20, 50, 40]
array1 = [{a: 10, b: 20, c: 30}, {a: 40, b: 50, c: 60}]
array2 = []
for {a: a, b, c: d} from array1
array2.push([a, b, d])
arrayEq array2, [[10, 20, 30], [40, 50, 60]]
array1 = [[10, 20, 30, 40, 50]]
for [a, b..., c] from array1
eq 10, a
arrayEq [20, 30, 40], b
eq 50, c
test "for-from comprehensions over Array", ->
array1 = (x + 10 for x from [10, 20, 30])
ok array1.join(' ') is '20 30 40'
array2 = (x for x from [30, 41, 57] when x %% 3 is 0)
ok array2.join(' ') is '30 57'
array1 = (b + 5 for [a, b] from [[20, 30], [40, 50]])
ok array1.join(' ') is '35 55'
array2 = (a + b for [a, b] from [[10, 20], [30, 40], [50, 60]] when a + b >= 70)
ok array2.join(' ') is '70 110'

View File

@ -12,7 +12,7 @@ test "ensure that carriage returns don't break compilation on Windows", ->
test "#3089 - don't mutate passed in options to compile", ->
opts = {}
CoffeeScript.compile '1 + 1', opts
ok !opts.scope
ok !opts.scope
test "--bare", ->
eq -1, CoffeeScript.compile('x = y', bare: on).indexOf 'function'

View File

@ -1192,7 +1192,7 @@ test "function cannot contain both `await` and `yield from`", ->
^^^^^^^
'''
test "Cannnot have `await` outside a function", ->
test "cannot have `await` outside a function", ->
assertErrorFormat '''
await 1
''', '''
@ -1200,3 +1200,17 @@ test "Cannnot have `await` outside a function", ->
await 1
^^^^^^^
'''
test "indexes are not supported in for-from loops", ->
assertErrorFormat "x for x, i from [1, 2, 3]", '''
[stdin]:1:10: error: cannot use index with for-from
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]
^^^
'''

View File

@ -232,3 +232,41 @@ test "yield handles 'this' correctly", ->
ok z.done is false
throws -> y.next new Error "boom"
test "for-from loops over generators", ->
array1 = [50, 30, 70, 20]
gen = -> yield from array1
array2 = []
array3 = []
array4 = []
iterator = gen()
for x from iterator
array2.push(x)
break if x is 30
for x from iterator
array3.push(x)
for x from iterator
array4.push(x)
arrayEq array2, [50, 30]
# Different JS engines have different opinions on the value of array3:
# https://github.com/jashkenas/coffeescript/pull/4306#issuecomment-257066877
# As a temporary measure, either result is accepted.
ok array3.length is 0 or array3.join(',') is '70,20'
arrayEq array4, []
test "for-from comprehensions over generators", ->
gen = ->
yield from [30, 41, 51, 60]
iterator = gen()
array1 = (x for x from iterator when x %% 2 is 1)
array2 = (x for x from iterator)
ok array1.join(' ') is '41 51'
ok array2.length is 0

View File

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

View File

@ -75,6 +75,20 @@ test "large ranges are generated with looping constructs", ->
eq 100, (len = up.length)
eq 99, up[len - 1]
test "for-from loops over ranges", ->
array1 = []
for x from [20..30]
array1.push(x)
break if x is 25
arrayEq array1, [20, 21, 22, 23, 24, 25]
test "for-from comprehensions over ranges", ->
array1 = (x + 10 for x from [20..25])
ok array1.join(' ') is '30 31 32 33 34 35'
array2 = (x for x from [20..30] when x %% 2 == 0)
ok array2.join(' ') is '20 22 24 26 28 30'
test "#1012 slices with arguments object", ->
expected = [0..9]
argsAtStart = (-> [arguments[0]..9]) 0

View File

@ -41,6 +41,10 @@ test "loop variable should be accessible after for-in loop", ->
d = (x for x in [1,2])
eq x, 2
test "loop variable should be accessible after for-from loop", ->
d = (x for x from [1,2])
eq x, 2
class Array then slice: fail # needs to be global
class Object then hasOwnProperty: fail
test "#1973: redefining Array/Object constructors shouldn't confuse __X helpers", ->