Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 78 additions & 5 deletions src/main/java/org/codelibs/sai/internal/parser/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -1521,13 +1521,45 @@ private void forStatement() {
* @return true if a for-of head starts at the current token
*/
private boolean isForOf() {
return lookahead(this::isForOfAhead);
}

private boolean isForOfAhead() {
int i = k;

if (T(i) == TokenType.VAR || T(i) == LET || T(i) == CONST) {
i++;
}

return T(i) == IDENT && T(i + 1) == IDENT && "of".equals(getValue(getToken(i + 1)));
if (T(i) == LBRACKET || T(i) == LBRACE) {
// A destructuring pattern. Find the bracket that closes it; what is in
// between only has to balance here, it is parsed properly once this is
// known to be a for-of.
final TokenType open = T(i);
final TokenType close = open == LBRACKET ? RBRACKET : RBRACE;
int depth = 0;

for (;; i++) {
final TokenType tokenType = T(i);

if (tokenType == open) {
depth++;
} else if (tokenType == close) {
if (--depth == 0) {
i++;
break;
}
} else if (tokenType == EOF) {
return false;
}
}
} else if (T(i) == IDENT) {
i++;
} else {
return false;
}

return T(i) == IDENT && "of".equals(getValue(getToken(i)));
}

/**
Expand Down Expand Up @@ -1565,8 +1597,19 @@ private ForNode forOf(final ForNode forNodeArg, final int forLine) {
next();
}

final IdentNode name = getIdent();
verifyStrictIdent(name, "for-of iterator");
final List<Binding> pattern;
final IdentNode name;

if (type == LBRACKET || type == LBRACE) {
// The leaves of a pattern with no declaration are assignment targets rather
// than names to declare, the same distinction destructuringAssignment makes.
pattern = destructuringPattern(declarationType == null);
name = null;
} else {
pattern = null;
name = getIdent();
verifyStrictIdent(name, "for-of iterator");
}

// "of" is a plain identifier, not a keyword.
final long ofToken = token;
Expand Down Expand Up @@ -1597,11 +1640,41 @@ private ForNode forOf(final ForNode forNodeArg, final int forLine) {

Block body = newBlock();
try {
if (declarationType == null) {
final int varFlags = declarationType == LET ? VarNode.IS_LET
: declarationType == CONST ? VarNode.IS_CONST : 0;

if (pattern != null) {
// Bind the element to a temporary and take the pattern apart from there,
// so the element expression is evaluated once per iteration.
final String elementName = newTemporary();

if (declarationType == null) {
final List<Expression> steps = new ArrayList<>();
steps.add(new BinaryNode(Token.recast(ofToken, TokenType.ASSIGN),
identifierFor(ofToken, elementName), element));
assignBindings(elementName, pattern, steps);

Expression result = steps.get(0);

for (int i = 1; i < steps.size(); i++) {
result = new BinaryNode(Token.recast(ofToken, TokenType.COMMARIGHT), result, steps.get(i));
}

appendStatement(new ExpressionStatement(forLine, ofToken, finish, result));
} else {
appendStatement(assignTemporary(forLine, ofToken, elementName, element));

final List<Statement> statements = new ArrayList<>();
declareBindings(elementName, pattern, forLine, varFlags, new ArrayList<VarNode>(), statements);

for (final Statement statement : statements) {
appendStatement(statement);
}
}
} else if (declarationType == null) {
appendStatement(new ExpressionStatement(forLine, ofToken, finish,
new BinaryNode(Token.recast(ofToken, TokenType.ASSIGN), name, element)));
} else {
final int varFlags = declarationType == LET ? VarNode.IS_LET : declarationType == CONST ? VarNode.IS_CONST : 0;
appendStatement(new VarNode(forLine, declarationToken, finish, name.setIsDeclaredHere(), element, varFlags));
}

Expand Down
71 changes: 71 additions & 0 deletions test/script/basic/es6/for-of.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,74 @@ print(out.join(","));
out = [];
for (var w of [1, 2]) out.push(w * 10);
print(out.join(","));

// The loop variable may be a destructuring pattern. The element is bound to a
// temporary once per iteration and the pattern is taken apart from there.
for (var [a, b] of [[1, 2], [3, 4]]) {
print(a + ":" + b);
}

for (let [a, b] of [[5, 6]]) {
print(a + ":" + b);
}

for (const [a, b] of [[7, 8]]) {
print(a + ":" + b);
}

for (var { x, y } of [{ x: 9, y: 10 }]) {
print(x + ":" + y);
}

for (const { k: renamed } of [{ k: 11 }]) {
print(renamed);
}

// Rest, nesting and defaults inside the pattern.
for (const [head, ...rest] of [[12, 13, 14]]) {
print(head + ":" + rest);
}

for (const [{ inner }] of [[{ inner: 15 }]]) {
print(inner);
}

for (let [withDefault = 16] of [[]]) {
print(withDefault);
}

// The common shape: iterating pairs.
var pairs = [["k", 17], ["j", 18]];
for (const [key, value] of pairs) {
print(key + "=" + value);
}

// With no declaration the leaves are assignment targets, member expressions included.
var lhs1, lhs2;
for ([lhs1, lhs2] of [[19, 20]]) {
print(lhs1 + ":" + lhs2);
}

var target = {};
for ([target.field] of [[21]]) {
print(target.field);
}

// A let binding still gets a fresh copy per iteration, so closures made in the
// body capture that iteration's value.
var captured = [];
for (let [each] of [[22], [23]]) {
captured.push(function () { return each; });
}
print(captured[0]() + "," + captured[1]());

// break and continue are unaffected.
for (const [n] of [[24], [25], [26]]) {
if (n === 25) {
continue;
}
if (n === 26) {
break;
}
print(n);
}
15 changes: 15 additions & 0 deletions test/script/basic/es6/for-of.js.EXPECTED
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,18 @@ x,y
[]
1,2,3
10,20
1:2
3:4
5:6
7:8
9:10
11
12:13,14
15
16
k=17
j=18
19:20
21
22,23
24