Bug 979865 - Part 3: Implement ES6 array comprehensions r=jorendorff
authorAndy Wingo <wingo@igalia.com>
Fri, 07 Mar 2014 22:01:13 +0100
changeset 189804 f4216dc177e48775dc50e629f7d156592cf3677a
parent 189803 f0fc695a395dabacfb0bdf86135110f84e500995
child 189805 1cb9462ced3948eaffc018375a8abdfcf1c2ca8a
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs979865
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 979865 - Part 3: Implement ES6 array comprehensions r=jorendorff
js/src/frontend/Parser.cpp
js/src/frontend/Parser.h
js/src/frontend/SyntaxParseHandler.h
js/src/js.msg
js/src/jsatom.cpp
js/src/jsatom.h
js/src/vm/CommonPropertyNames.h
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -6420,16 +6420,188 @@ Parser<SyntaxParseHandler>::legacyGenera
 }
 
 static const char js_generator_str[] = "generator";
 
 #endif /* JS_HAS_GENERATOR_EXPRS */
 
 template <typename ParseHandler>
 typename ParseHandler::Node
+Parser<ParseHandler>::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<ParseHandler> 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>
+typename ParseHandler::Node
+Parser<ParseHandler>::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>
+typename ParseHandler::Node
+Parser<ParseHandler>::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>
+typename ParseHandler::Node
+Parser<ParseHandler>::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>
+typename ParseHandler::Node
+Parser<ParseHandler>::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>
+typename ParseHandler::Node
 Parser<ParseHandler>::assignExprWithoutYield(unsigned msg)
 {
     uint32_t startYieldOffset = pc->lastYieldOffset;
     Node res = assignExpr();
     if (res && pc->lastYieldOffset != startYieldOffset) {
         reportWithOffset(ParseError, false, pc->lastYieldOffset,
                          msg, js_yield_str);
         return null();
@@ -6673,26 +6845,30 @@ Parser<ParseHandler>::newRegExp()
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::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();
 
     if (tokenStream.matchToken(TOK_RB, TokenStream::Operand)) {
         /*
          * Mark empty arrays as non-constant, since we cannot easily
          * 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;
         for (; ; index++) {
             if (index == JSObject::NELEMENTS_LIMIT) {
                 report(ParseError, false, null(), JSMSG_ARRAY_INIT_TOO_BIG);
                 return null();
             }
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -542,16 +542,22 @@ class Parser : private AutoGCRooter, pub
     Node condition();
 
     Node legacyComprehensionTail(Node kid, unsigned blockid, GeneratorKind comprehensionKind,
                                  ParseContext<ParseHandler> *outerpc,
                                  unsigned innerBlockScopeDepth);
     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<ParseHandler> *data, TokenKind tt);
 
     Node identifierName();
 
     bool matchLabel(MutableHandle<PropertyName*> label);
 
--- a/js/src/frontend/SyntaxParseHandler.h
+++ b/js/src/frontend/SyntaxParseHandler.h
@@ -161,16 +161,24 @@ class SyntaxParseHandler
                        Node catchName, Node catchGuard, Node catchBody) { return true; }
 
     void setLastFunctionArgumentDefault(Node funcpn, Node pn) {}
     Node newFunctionDefinition() { return NodeGeneric; }
     void setFunctionBody(Node pn, Node kid) {}
     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) {}
 
     bool isOperationWithoutParens(Node pn, ParseNodeKind kind) {
         // It is OK to return false here, callers should only use this method
         // for reporting strict option warnings and parsing code which the
         // syntax parser does not handle.
         return false;
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -221,17 +221,17 @@ MSG_DEF(JSMSG_CANT_DECODE_PRINCIPALS, 16
 MSG_DEF(JSMSG_CANT_SEAL_OBJECT,       168, 1, JSEXN_ERR, "can't seal {0} objects")
 MSG_DEF(JSMSG_TOO_MANY_CATCH_VARS,    169, 0, JSEXN_SYNTAXERR, "too many catch variables")
 MSG_DEF(JSMSG_NEGATIVE_REPETITION_COUNT, 170, 0, JSEXN_RANGEERR, "repeat count must be non-negative")
 MSG_DEF(JSMSG_INVALID_FOR_OF_INIT,    171, 0, JSEXN_SYNTAXERR, "for-of loop variable declaration may not have an initializer")
 MSG_DEF(JSMSG_INVALID_MAP_ITERABLE,   172, 0, JSEXN_TYPEERR, "iterable for map should have array-like objects")
 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, "")
 MSG_DEF(JSMSG_UNUSED180,              180, 0, JSEXN_NONE, "")
 MSG_DEF(JSMSG_UNUSED181,              181, 0, JSEXN_NONE, "")
 MSG_DEF(JSMSG_BAD_GENERATOR_SEND,     182, 1, JSEXN_TYPEERR, "attempt to send {0} to newborn generator")
 MSG_DEF(JSMSG_SC_NOT_TRANSFERABLE,    183, 0, JSEXN_TYPEERR, "invalid transferable array for structured clone")
 MSG_DEF(JSMSG_SC_DUP_TRANSFERABLE,    184, 0, JSEXN_TYPEERR, "duplicate transferable for structured clone")
@@ -258,21 +258,21 @@ MSG_DEF(JSMSG_UTF8_CHAR_TOO_LARGE,    20
 MSG_DEF(JSMSG_MALFORMED_UTF8_CHAR,    205, 1, JSEXN_TYPEERR, "malformed UTF-8 character sequence at offset {0}")
 MSG_DEF(JSMSG_USER_DEFINED_ERROR,     206, 0, JSEXN_ERR, "JS_ReportError was called")
 MSG_DEF(JSMSG_WRONG_CONSTRUCTOR,      207, 1, JSEXN_TYPEERR, "wrong constructor called for {0}")
 MSG_DEF(JSMSG_BAD_GENERATOR_RETURN,   208, 1, JSEXN_TYPEERR, "generator function {0} returns a value")
 MSG_DEF(JSMSG_BAD_ANON_GENERATOR_RETURN, 209, 0, JSEXN_TYPEERR, "anonymous generator function returns a value")
 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")
 MSG_DEF(JSMSG_BAD_INCOP_OPERAND,      221, 0, JSEXN_REFERENCEERR, "invalid increment/decrement operand")
 MSG_DEF(JSMSG_UNEXPECTED_TYPE,        222, 2, JSEXN_TYPEERR, "{0} is {1}")
 MSG_DEF(JSMSG_LET_DECL_NOT_IN_BLOCK,  223, 0, JSEXN_SYNTAXERR, "let declaration not directly within block")
 MSG_DEF(JSMSG_BAD_OBJECT_INIT,        224, 0, JSEXN_SYNTAXERR, "invalid object initializer")
 MSG_DEF(JSMSG_CANT_SET_ARRAY_ATTRS,   225, 0, JSEXN_INTERNALERR, "can't set attributes on indexed array properties")
--- a/js/src/jsatom.cpp
+++ b/js/src/jsatom.cpp
@@ -81,17 +81,16 @@ const char js_finally_str[]         = "f
 const char js_for_str[]             = "for";
 const char js_getter_str[]          = "getter";
 const char js_if_str[]              = "if";
 const char js_implements_str[]      = "implements";
 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";
 const char js_protected_str[]       = "protected";
 const char js_public_str[]          = "public";
 const char js_send_str[]            = "send";
 const char js_setter_str[]          = "setter";
 const char js_static_str[]          = "static";
--- a/js/src/jsatom.h
+++ b/js/src/jsatom.h
@@ -141,17 +141,16 @@ extern const char js_finally_str[];
 extern const char js_for_str[];
 extern const char js_getter_str[];
 extern const char js_if_str[];
 extern const char js_implements_str[];
 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[];
 extern const char js_protected_str[];
 extern const char js_public_str[];
 extern const char js_send_str[];
 extern const char js_setter_str[];
 extern const char js_static_str[];
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -105,16 +105,17 @@
     macro(int16, int16, "int16") \
     macro(int32, int32, "int32") \
     macro(iterator, iterator, "iterator") \
     macro(iteratorIntrinsic, iteratorIntrinsic, "__iterator__") \
     macro(join, join, "join") \
     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") \
     macro(locale, locale, "locale") \
     macro(lookupGetter, lookupGetter, "__lookupGetter__") \
     macro(lookupSetter, lookupSetter, "__lookupSetter__") \
     macro(maximumFractionDigits, maximumFractionDigits, "maximumFractionDigits") \
     macro(maximumSignificantDigits, maximumSignificantDigits, "maximumSignificantDigits") \