From 5ba1fe5cdfd6cd5be6c71843ad4df838b95f7249 Mon Sep 17 00:00:00 2001 From: Andy Wingo Date: Fri, 7 Mar 2014 22:01:13 +0100 Subject: [PATCH] Bug 979865 - Part 3: Implement ES6 array comprehensions r=jorendorff --- js/src/frontend/Parser.cpp | 178 ++++++++++++++++++++++++++- js/src/frontend/Parser.h | 6 + js/src/frontend/SyntaxParseHandler.h | 8 ++ js/src/js.msg | 6 +- js/src/jsatom.cpp | 1 - js/src/jsatom.h | 1 - js/src/vm/CommonPropertyNames.h | 1 + 7 files changed, 195 insertions(+), 6 deletions(-) diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index f7615196f2f7..fa6b15e15433 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -6423,6 +6423,178 @@ static const char js_generator_str[] = "generator"; #endif /* JS_HAS_GENERATOR_EXPRS */ +template +typename ParseHandler::Node +Parser::comprehensionFor(GeneratorKind comprehensionKind) +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR)); + + uint32_t begin = pos().begin; + + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); + + // FIXME: Destructuring binding (bug 980828). + + MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_VARIABLE_NAME); + RootedPropertyName name(context, tokenStream.currentName()); + if (name == context->names().let) { + report(ParseError, false, null(), JSMSG_LET_COMP_BINDING); + return null(); + } + if (!tokenStream.matchContextualKeyword(context->names().of)) { + report(ParseError, false, null(), JSMSG_OF_AFTER_FOR_NAME); + return null(); + } + + Node rhs = assignExpr(); + if (!rhs) + return null(); + + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_OF_ITERABLE); + + TokenPos headPos(begin, pos().end); + + StmtInfoPC stmtInfo(context); + BindData data(context); + RootedStaticBlockObject blockObj(context, StaticBlockObject::create(context)); + if (!blockObj) + return null(); + data.initLet(DontHoistVars, *blockObj, JSMSG_TOO_MANY_LOCALS); + Node lhs = newName(name); + if (!lhs) + return null(); + Node decls = handler.newList(PNK_LET, lhs, JSOP_NOP); + if (!decls) + return null(); + data.pn = lhs; + if (!data.binder(&data, name, this)) + return null(); + Node letScope = pushLetScope(blockObj, &stmtInfo); + if (!letScope) + return null(); + handler.setLexicalScopeBody(letScope, decls); + + Node assignLhs = newName(name); + if (!assignLhs) + return null(); + if (!noteNameUse(name, assignLhs)) + return null(); + handler.setOp(assignLhs, JSOP_SETNAME); + + Node head = handler.newForHead(PNK_FOROF, letScope, assignLhs, rhs, headPos); + if (!head) + return null(); + + Node tail = comprehensionTail(comprehensionKind); + if (!tail) + return null(); + + PopStatementPC(tokenStream, pc); + + return handler.newForStatement(begin, head, tail, JSOP_ITER); +} + +template +typename ParseHandler::Node +Parser::comprehensionIf(GeneratorKind comprehensionKind) +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_IF)); + + uint32_t begin = pos().begin; + + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_COND); + Node cond = assignExpr(); + if (!cond) + return null(); + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_COND); + + /* Check for (a = b) and warn about possible (a == b) mistype. */ + if (handler.isOperationWithoutParens(cond, PNK_ASSIGN) && + !report(ParseExtraWarning, false, null(), JSMSG_EQUAL_AS_ASSIGN)) + { + return null(); + } + + Node then = comprehensionTail(comprehensionKind); + if (!then) + return null(); + + return handler.newIfStatement(begin, cond, then, null()); +} + +template +typename ParseHandler::Node +Parser::comprehensionTail(GeneratorKind comprehensionKind) +{ + JS_CHECK_RECURSION(context, return null()); + + if (tokenStream.matchToken(TOK_FOR, TokenStream::Operand)) + return comprehensionFor(comprehensionKind); + + if (tokenStream.matchToken(TOK_IF, TokenStream::Operand)) + return comprehensionIf(comprehensionKind); + + uint32_t begin = pos().begin; + + Node bodyExpr = assignExpr(); + if (!bodyExpr) + return null(); + + if (comprehensionKind == NotGenerator) + return handler.newUnary(PNK_ARRAYPUSH, JSOP_ARRAYPUSH, begin, bodyExpr); + + JS_ASSERT(comprehensionKind == StarGenerator); + Node yieldExpr = handler.newUnary(PNK_YIELD, JSOP_NOP, begin, bodyExpr); + if (!yieldExpr) + return null(); + handler.setInParens(yieldExpr); + + return handler.newExprStatement(yieldExpr, pos().end); +} + +// Parse an ES6 generator or array comprehension, starting at the first 'for'. +// The caller is responsible for matching the ending TOK_RP or TOK_RB. +template +typename ParseHandler::Node +Parser::comprehension(GeneratorKind comprehensionKind) +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR)); + + uint32_t startYieldOffset = pc->lastYieldOffset; + + Node body = comprehensionFor(comprehensionKind); + if (!body) + return null(); + + if (comprehensionKind != NotGenerator && pc->lastYieldOffset != startYieldOffset) { + reportWithOffset(ParseError, false, pc->lastYieldOffset, + JSMSG_BAD_GENEXP_BODY, js_yield_str); + return null(); + } + + return body; +} + +template +typename ParseHandler::Node +Parser::arrayComprehension(uint32_t begin) +{ + Node inner = comprehension(NotGenerator); + if (!inner) + return null(); + + MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_AFTER_ARRAY_COMPREHENSION); + + Node comp = handler.newList(PNK_ARRAYCOMP, inner); + if (!comp) + return null(); + + handler.setBeginPosition(comp, begin); + handler.setEndPosition(comp, pos().end); + + return comp; +} + template typename ParseHandler::Node Parser::assignExprWithoutYield(unsigned msg) @@ -6678,7 +6850,8 @@ Parser::arrayInitializer() { JS_ASSERT(tokenStream.isCurrentTokenType(TOK_LB)); - Node literal = handler.newArrayLiteral(pos().begin, pc->blockidGen); + uint32_t begin = pos().begin; + Node literal = handler.newArrayLiteral(begin, pc->blockidGen); if (!literal) return null(); @@ -6688,6 +6861,9 @@ Parser::arrayInitializer() * determine their type. */ handler.setListFlag(literal, PNX_NONCONST); + } else if (tokenStream.matchToken(TOK_FOR, TokenStream::Operand)) { + // ES6 array comprehension. + return arrayComprehension(begin); } else { bool spread = false, missingTrailingComma = false; uint32_t index = 0; diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 7bf3a04d4c2e..5c943d47bb7b 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -547,6 +547,12 @@ class Parser : private AutoGCRooter, public StrictModeGetter Node legacyArrayComprehension(Node array); Node legacyGeneratorExpr(Node kid); + Node comprehensionTail(GeneratorKind comprehensionKind); + Node comprehensionIf(GeneratorKind comprehensionKind); + Node comprehensionFor(GeneratorKind comprehensionKind); + Node comprehension(GeneratorKind comprehensionKind); + Node arrayComprehension(uint32_t begin); + bool argumentList(Node listNode, bool *isSpread); Node letBlock(LetContext letContext); Node destructuringExpr(BindData *data, TokenKind tt); diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 0191833a76c5..1fea556af96e 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -166,6 +166,14 @@ class SyntaxParseHandler void setFunctionBox(Node pn, FunctionBox *funbox) {} void addFunctionArgument(Node pn, Node argpn) {} + Node newForStatement(uint32_t begin, Node forHead, Node body, unsigned iflags) { + return NodeGeneric; + } + + Node newForHead(ParseNodeKind kind, Node decls, Node lhs, Node rhs, const TokenPos &pos) { + return NodeGeneric; + } + Node newLexicalScope(ObjectBox *blockbox) { return NodeGeneric; } void setLexicalScopeBody(Node block, Node body) {} diff --git a/js/src/js.msg b/js/src/js.msg index 1d40d2222061..146b1136d682 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -226,7 +226,7 @@ MSG_DEF(JSMSG_INVALID_MAP_ITERABLE, 172, 0, JSEXN_TYPEERR, "iterable for map s MSG_DEF(JSMSG_NOT_A_CODEPOINT, 173, 1, JSEXN_RANGEERR, "{0} is not a valid code point") MSG_DEF(JSMSG_BRACKET_AFTER_ARRAY_COMPREHENSION, 174, 0, JSEXN_SYNTAXERR, "missing ] after array comprehension") MSG_DEF(JSMSG_NESTING_GENERATOR, 175, 0, JSEXN_TYPEERR, "already executing generator") -MSG_DEF(JSMSG_UNUSED176, 176, 0, JSEXN_NONE, "") +MSG_DEF(JSMSG_PAREN_AFTER_FOR_OF_ITERABLE, 176, 0, JSEXN_SYNTAXERR, "missing ) after for-of iterable") MSG_DEF(JSMSG_UNUSED177, 177, 0, JSEXN_NONE, "") MSG_DEF(JSMSG_UNUSED178, 178, 0, JSEXN_NONE, "") MSG_DEF(JSMSG_UNUSED179, 179, 0, JSEXN_NONE, "") @@ -263,11 +263,11 @@ MSG_DEF(JSMSG_BAD_ANON_GENERATOR_RETURN, 209, 0, JSEXN_TYPEERR, "anonymous gener MSG_DEF(JSMSG_PROTO_SETTING_SLOW, 210, 0, JSEXN_TYPEERR, "mutating the [[Prototype]] of an object will cause your code to run very slowly; instead create the object with the correct initial [[Prototype]] value using Object.create") MSG_DEF(JSMSG_IN_AFTER_FOR_NAME, 211, 0, JSEXN_SYNTAXERR, "missing 'in' or 'of' after for") MSG_DEF(JSMSG_BAD_TRAP_RETURN_VALUE, 212, 2, JSEXN_TYPEERR,"trap {1} for {0} returned a primitive value") -MSG_DEF(JSMSG_UNUSED213, 213, 0, JSEXN_NONE, "") +MSG_DEF(JSMSG_OF_AFTER_FOR_NAME, 213, 0, JSEXN_SYNTAXERR, "missing 'of' after for") MSG_DEF(JSMSG_BAD_GENERATOR_YIELD, 214, 1, JSEXN_TYPEERR, "yield from closing generator {0}") MSG_DEF(JSMSG_BAD_GENERATOR_SYNTAX, 215, 1, JSEXN_SYNTAXERR, "{0} expression must be parenthesized") MSG_DEF(JSMSG_ARRAY_COMP_LEFTSIDE, 216, 0, JSEXN_SYNTAXERR, "invalid array comprehension left-hand side") -MSG_DEF(JSMSG_UNUSED217, 217, 0, JSEXN_NONE, "") +MSG_DEF(JSMSG_LET_COMP_BINDING, 217, 0, JSEXN_SYNTAXERR, "'let' is not a valid name for a comprehension variable") MSG_DEF(JSMSG_EMPTY_ARRAY_REDUCE, 218, 0, JSEXN_TYPEERR, "reduce of empty array with no initial value") MSG_DEF(JSMSG_BAD_SYMBOL, 219, 1, JSEXN_TYPEERR, "{0} is not a well-known @@-symbol") MSG_DEF(JSMSG_BAD_DELETE_OPERAND, 220, 0, JSEXN_REFERENCEERR, "invalid delete operand") diff --git a/js/src/jsatom.cpp b/js/src/jsatom.cpp index cb232df5a8c7..61af4db32dc5 100644 --- a/js/src/jsatom.cpp +++ b/js/src/jsatom.cpp @@ -86,7 +86,6 @@ const char js_import_str[] = "import"; const char js_in_str[] = "in"; const char js_instanceof_str[] = "instanceof"; const char js_interface_str[] = "interface"; -const char js_let_str[] = "let"; const char js_new_str[] = "new"; const char js_package_str[] = "package"; const char js_private_str[] = "private"; diff --git a/js/src/jsatom.h b/js/src/jsatom.h index 762d97fa9830..025ffe8c0326 100644 --- a/js/src/jsatom.h +++ b/js/src/jsatom.h @@ -146,7 +146,6 @@ extern const char js_import_str[]; extern const char js_in_str[]; extern const char js_instanceof_str[]; extern const char js_interface_str[]; -extern const char js_let_str[]; extern const char js_new_str[]; extern const char js_package_str[]; extern const char js_private_str[]; diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index ea59e6a75a84..8ce1749ec0aa 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -110,6 +110,7 @@ macro(keys, keys, "keys") \ macro(lastIndex, lastIndex, "lastIndex") \ macro(length, length, "length") \ + macro(let, let, "let") \ macro(line, line, "line") \ macro(lineNumber, lineNumber, "lineNumber") \ macro(loc, loc, "loc") \