From 341d61867c79e915e973c382858adb840ec6dd93 Mon Sep 17 00:00:00 2001 From: "wingo@igalia.com" Date: Wed, 2 Jul 2014 13:48:28 +0000 Subject: [PATCH] Allow yield expressions without a RHS. R=marja@chromium.org BUG= Review URL: https://codereview.chromium.org/348893007 git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@22163 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/ast.h | 1 + src/preparser.h | 31 +++++++++++--- test/cctest/test-parsing.cc | 43 ++++++++++++------- test/mjsunit/harmony/generators-iteration.js | 18 ++++++++ test/mjsunit/harmony/generators-parsing.js | 45 ++++++++++++++++---- 5 files changed, 109 insertions(+), 29 deletions(-) diff --git a/src/ast.h b/src/ast.h index aecee37b60..15be52fb69 100644 --- a/src/ast.h +++ b/src/ast.h @@ -3368,6 +3368,7 @@ class AstNodeFactory V8_FINAL BASE_EMBEDDED { Expression* expression, Yield::Kind yield_kind, int pos) { + if (!expression) expression = NewUndefinedLiteral(pos); Yield* yield = new(zone_) Yield( zone_, generator_object, expression, yield_kind, pos); VISIT_AND_RETURN(Yield, yield) diff --git a/src/preparser.h b/src/preparser.h index 67990872c9..d6be902713 100644 --- a/src/preparser.h +++ b/src/preparser.h @@ -1792,15 +1792,36 @@ template typename ParserBase::ExpressionT ParserBase::ParseYieldExpression(bool* ok) { // YieldExpression :: - // 'yield' '*'? AssignmentExpression + // 'yield' ([no line terminator] '*'? AssignmentExpression)? int pos = peek_position(); Expect(Token::YIELD, CHECK_OK); - Yield::Kind kind = - Check(Token::MUL) ? Yield::DELEGATING : Yield::SUSPEND; ExpressionT generator_object = factory()->NewVariableProxy(function_state_->generator_object_variable()); - ExpressionT expression = - ParseAssignmentExpression(false, CHECK_OK); + ExpressionT expression = Traits::EmptyExpression(); + Yield::Kind kind = Yield::SUSPEND; + if (!scanner()->HasAnyLineTerminatorBeforeNext()) { + if (Check(Token::MUL)) kind = Yield::DELEGATING; + switch (peek()) { + case Token::EOS: + case Token::SEMICOLON: + case Token::RBRACE: + case Token::RBRACK: + case Token::RPAREN: + case Token::COLON: + case Token::COMMA: + // The above set of tokens is the complete set of tokens that can appear + // after an AssignmentExpression, and none of them can start an + // AssignmentExpression. This allows us to avoid looking for an RHS for + // a Yield::SUSPEND operation, given only one look-ahead token. + if (kind == Yield::SUSPEND) + break; + ASSERT(kind == Yield::DELEGATING); + // Delegating yields require an RHS; fall through. + default: + expression = ParseAssignmentExpression(false, CHECK_OK); + break; + } + } typename Traits::Type::YieldExpression yield = factory()->NewYield(generator_object, expression, kind, pos); if (kind == Yield::DELEGATING) { diff --git a/test/cctest/test-parsing.cc b/test/cctest/test-parsing.cc index af553ed6bb..47f5e6abec 100644 --- a/test/cctest/test-parsing.cc +++ b/test/cctest/test-parsing.cc @@ -1856,6 +1856,10 @@ TEST(NoErrorsGenerator) { "yield 3; yield 4;", "yield * 3; yield * 4;", "(function (yield) { })", + "yield { yield: 12 }", + "yield /* comment */ { yield: 12 }", + "yield * \n { yield: 12 }", + "yield /* comment */ * \n { yield: 12 }", // You can return in a generator. "yield 1; return", "yield * 1; return", @@ -1866,8 +1870,21 @@ TEST(NoErrorsGenerator) { // Yield is still a valid key in object literals. "({ yield: 1 })", "({ get yield() { } })", - // TODO(348893007): Invalid (no newline allowed between yield and *). - "yield\n*3", + // Yield without RHS. + "yield;", + "yield", + "yield\n", + "yield /* comment */" + "yield // comment\n" + "(yield)", + "[yield]", + "{yield}", + "yield, yield", + "yield; yield", + "(yield) ? yield : yield", + "(yield) \n ? yield : yield", + // If there is a newline before the next token, we don't look for RHS. + "yield\nfor (;;) {}", NULL }; @@ -1913,19 +1930,15 @@ TEST(ErrorsYieldGenerator) { "yield ? 1 : 2", // Parses as yield (/ yield): invalid. "yield / yield", - // TODO(348893007): The rest of these should be valid. - "yield;", - "yield", - "yield\n", - "(yield)", - "[yield]", - "{yield}", - "yield, yield", - "yield; yield", - "(yield) ? yield : yield", - "(yield) \n ? yield : yield", - // Parses as yield (+ yield). - "yield + yield", + "+ yield", + "+ yield 3", + // Invalid (no newline allowed between yield and *). + "yield\n*3", + // Invalid (we see a newline, so we parse {yield:42} as a statement, not an + // object literal, and yield is not a valid label). + "yield\n{yield: 42}", + "yield /* comment */\n {yield: 42}", + "yield //comment\n {yield: 42}", NULL }; diff --git a/test/mjsunit/harmony/generators-iteration.js b/test/mjsunit/harmony/generators-iteration.js index d86a20f9e7..e54aeabd75 100644 --- a/test/mjsunit/harmony/generators-iteration.js +++ b/test/mjsunit/harmony/generators-iteration.js @@ -337,6 +337,24 @@ TestGenerator( "foo", [2, "1foo3", 5, "4foo6", "foofoo"]); +// Yield with no arguments yields undefined. +TestGenerator( + function* g26() { return yield yield }, + [undefined, undefined, undefined], + "foo", + [undefined, "foo", "foo"]); + +// A newline causes the parser to stop looking for an argument to yield. +TestGenerator( + function* g27() { + yield + 3 + return + }, + [undefined, undefined], + "foo", + [undefined, undefined]); + // Generator function instances. TestGenerator(GeneratorFunction(), [undefined], diff --git a/test/mjsunit/harmony/generators-parsing.js b/test/mjsunit/harmony/generators-parsing.js index 2a4a68c37c..21790b0e13 100644 --- a/test/mjsunit/harmony/generators-parsing.js +++ b/test/mjsunit/harmony/generators-parsing.js @@ -35,6 +35,40 @@ function* g() { yield 3; yield 4; } // Yield expressions. function* g() { (yield 3) + (yield 4); } +// Yield without a RHS. +function* g() { yield; } +function* g() { yield } +function* g() { + yield +} +function* g() { (yield) } +function* g() { [yield] } +function* g() { {yield} } +function* g() { yield, yield } +function* g() { yield; yield } +function* g() { (yield) ? yield : yield } +function* g() { + (yield) + ? yield + : yield +} + +// If yield has a RHS, it needs to start on the same line. The * in a +// yield* counts as starting the RHS. +function* g() { + yield * + foo +} +assertThrows("function* g() { yield\n* foo }", SyntaxError); +assertEquals(undefined, + (function*(){ + yield + 3 + })().next().value); + +// A YieldExpression is not a LogicalORExpression. +assertThrows("function* g() { yield ? yield : yield }", SyntaxError); + // You can have a generator in strict mode. function* g() { "use strict"; yield 3; yield 4; } @@ -50,14 +84,10 @@ function* g() { yield 1; return 2; yield "dead"; } // Named generator expression. (function* g() { yield 3; }); -// A generator without a yield is specified as causing an early error. This -// behavior is currently unimplemented. See -// https://bugs.ecmascript.org/show_bug.cgi?id=1283. +// You can have a generator without a yield. function* g() { } -// A YieldExpression in the RHS of a YieldExpression is currently specified as -// causing an early error. This behavior is currently unimplemented. See -// https://bugs.ecmascript.org/show_bug.cgi?id=1283. +// A YieldExpression is valid as the RHS of a YieldExpression. function* g() { yield yield 1; } function* g() { yield 3 + (yield 4); } @@ -86,9 +116,6 @@ assertThrows("function* g() { yield: 1 }", SyntaxError) // functions. function* g() { function f() { yield (yield + yield (0)); } } -// Yield needs a RHS. -assertThrows("function* g() { yield; }", SyntaxError); - // Yield in a generator is not an identifier. assertThrows("function* g() { yield = 10; }", SyntaxError);