michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * JS parser. michael@0: * michael@0: * This is a recursive-descent parser for the JavaScript language specified by michael@0: * "The JavaScript 1.5 Language Specification". It uses lexical and semantic michael@0: * feedback to disambiguate non-LL(1) structures. It generates trees of nodes michael@0: * induced by the recursive parsing (not precise syntax trees, see Parser.h). michael@0: * After tree construction, it rewrites trees to fold constants and evaluate michael@0: * compile-time expressions. michael@0: * michael@0: * This parser attempts no error recovery. michael@0: */ michael@0: michael@0: #include "frontend/Parser-inl.h" michael@0: michael@0: #include "jsapi.h" michael@0: #include "jsatom.h" michael@0: #include "jscntxt.h" michael@0: #include "jsfun.h" michael@0: #include "jsobj.h" michael@0: #include "jsopcode.h" michael@0: #include "jsscript.h" michael@0: #include "jstypes.h" michael@0: michael@0: #include "frontend/BytecodeCompiler.h" michael@0: #include "frontend/FoldConstants.h" michael@0: #include "frontend/ParseMaps.h" michael@0: #include "frontend/TokenStream.h" michael@0: #include "jit/AsmJS.h" michael@0: #include "vm/Shape.h" michael@0: michael@0: #include "jsatominlines.h" michael@0: #include "jsscriptinlines.h" michael@0: michael@0: #include "frontend/ParseNode-inl.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::gc; michael@0: using mozilla::Maybe; michael@0: michael@0: namespace js { michael@0: namespace frontend { michael@0: michael@0: typedef Rooted RootedStaticBlockObject; michael@0: typedef Handle HandleStaticBlockObject; michael@0: typedef Rooted RootedNestedScopeObject; michael@0: typedef Handle HandleNestedScopeObject; michael@0: michael@0: michael@0: /* michael@0: * Insist that the next token be of type tt, or report errno and return null. michael@0: * NB: this macro uses cx and ts from its lexical environment. michael@0: */ michael@0: #define MUST_MATCH_TOKEN(tt, errno) \ michael@0: JS_BEGIN_MACRO \ michael@0: if (tokenStream.getToken() != tt) { \ michael@0: report(ParseError, false, null(), errno); \ michael@0: return null(); \ michael@0: } \ michael@0: JS_END_MACRO michael@0: michael@0: static const unsigned BlockIdLimit = 1 << ParseNode::NumBlockIdBits; michael@0: michael@0: template michael@0: bool michael@0: GenerateBlockId(TokenStream &ts, ParseContext *pc, uint32_t &blockid) michael@0: { michael@0: if (pc->blockidGen == BlockIdLimit) { michael@0: ts.reportError(JSMSG_NEED_DIET, "program"); michael@0: return false; michael@0: } michael@0: JS_ASSERT(pc->blockidGen < BlockIdLimit); michael@0: blockid = pc->blockidGen++; michael@0: return true; michael@0: } michael@0: michael@0: template bool michael@0: GenerateBlockId(TokenStream &ts, ParseContext *pc, uint32_t &blockid); michael@0: michael@0: template bool michael@0: GenerateBlockId(TokenStream &ts, ParseContext *pc, uint32_t &blockid); michael@0: michael@0: template michael@0: static void michael@0: PushStatementPC(ParseContext *pc, StmtInfoPC *stmt, StmtType type) michael@0: { michael@0: stmt->blockid = pc->blockid(); michael@0: PushStatement(pc, stmt, type); michael@0: } michael@0: michael@0: // See comment on member function declaration. michael@0: template <> michael@0: bool michael@0: ParseContext::define(TokenStream &ts, michael@0: HandlePropertyName name, ParseNode *pn, Definition::Kind kind) michael@0: { michael@0: JS_ASSERT(!pn->isUsed()); michael@0: JS_ASSERT_IF(pn->isDefn(), pn->isPlaceholder()); michael@0: michael@0: Definition *prevDef = nullptr; michael@0: if (kind == Definition::LET) michael@0: prevDef = decls_.lookupFirst(name); michael@0: else michael@0: JS_ASSERT(!decls_.lookupFirst(name)); michael@0: michael@0: if (!prevDef) michael@0: prevDef = lexdeps.lookupDefn(name); michael@0: michael@0: if (prevDef) { michael@0: ParseNode **pnup = &prevDef->dn_uses; michael@0: ParseNode *pnu; michael@0: unsigned start = (kind == Definition::LET) ? pn->pn_blockid : bodyid; michael@0: michael@0: while ((pnu = *pnup) != nullptr && pnu->pn_blockid >= start) { michael@0: JS_ASSERT(pnu->pn_blockid >= bodyid); michael@0: JS_ASSERT(pnu->isUsed()); michael@0: pnu->pn_lexdef = (Definition *) pn; michael@0: pn->pn_dflags |= pnu->pn_dflags & PND_USE2DEF_FLAGS; michael@0: pnup = &pnu->pn_link; michael@0: } michael@0: michael@0: if (!pnu || pnu != prevDef->dn_uses) { michael@0: *pnup = pn->dn_uses; michael@0: pn->dn_uses = prevDef->dn_uses; michael@0: prevDef->dn_uses = pnu; michael@0: michael@0: if (!pnu && prevDef->isPlaceholder()) michael@0: lexdeps->remove(name); michael@0: } michael@0: michael@0: pn->pn_dflags |= prevDef->pn_dflags & PND_CLOSED; michael@0: } michael@0: michael@0: JS_ASSERT_IF(kind != Definition::LET, !lexdeps->lookup(name)); michael@0: pn->setDefn(true); michael@0: pn->pn_dflags &= ~PND_PLACEHOLDER; michael@0: if (kind == Definition::CONST) michael@0: pn->pn_dflags |= PND_CONST; michael@0: michael@0: Definition *dn = (Definition *)pn; michael@0: switch (kind) { michael@0: case Definition::ARG: michael@0: JS_ASSERT(sc->isFunctionBox()); michael@0: dn->setOp(JSOP_GETARG); michael@0: dn->pn_dflags |= PND_BOUND; michael@0: if (!dn->pn_cookie.set(ts, staticLevel, args_.length())) michael@0: return false; michael@0: if (!args_.append(dn)) michael@0: return false; michael@0: if (args_.length() >= ARGNO_LIMIT) { michael@0: ts.reportError(JSMSG_TOO_MANY_FUN_ARGS); michael@0: return false; michael@0: } michael@0: if (name == ts.names().empty) michael@0: break; michael@0: if (!decls_.addUnique(name, dn)) michael@0: return false; michael@0: break; michael@0: michael@0: case Definition::CONST: michael@0: case Definition::VAR: michael@0: if (sc->isFunctionBox()) { michael@0: dn->setOp(JSOP_GETLOCAL); michael@0: dn->pn_dflags |= PND_BOUND; michael@0: if (!dn->pn_cookie.set(ts, staticLevel, vars_.length())) michael@0: return false; michael@0: if (!vars_.append(dn)) michael@0: return false; michael@0: if (vars_.length() >= LOCALNO_LIMIT) { michael@0: ts.reportError(JSMSG_TOO_MANY_LOCALS); michael@0: return false; michael@0: } michael@0: } michael@0: if (!decls_.addUnique(name, dn)) michael@0: return false; michael@0: break; michael@0: michael@0: case Definition::LET: michael@0: dn->setOp(JSOP_GETLOCAL); michael@0: dn->pn_dflags |= (PND_LET | PND_BOUND); michael@0: JS_ASSERT(dn->pn_cookie.level() == staticLevel); /* see bindLet */ michael@0: if (!decls_.addShadow(name, dn)) michael@0: return false; michael@0: break; michael@0: michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("unexpected kind"); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template <> michael@0: bool michael@0: ParseContext::define(TokenStream &ts, HandlePropertyName name, Node pn, michael@0: Definition::Kind kind) michael@0: { michael@0: JS_ASSERT(!decls_.lookupFirst(name)); michael@0: michael@0: if (lexdeps.lookupDefn(name)) michael@0: lexdeps->remove(name); michael@0: michael@0: // Keep track of the number of arguments in args_, for fun->nargs. michael@0: if (kind == Definition::ARG) { michael@0: if (!args_.append((Definition *) nullptr)) michael@0: return false; michael@0: if (args_.length() >= ARGNO_LIMIT) { michael@0: ts.reportError(JSMSG_TOO_MANY_FUN_ARGS); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return decls_.addUnique(name, kind); michael@0: } michael@0: michael@0: template michael@0: void michael@0: ParseContext::prepareToAddDuplicateArg(HandlePropertyName name, DefinitionNode prevDecl) michael@0: { michael@0: JS_ASSERT(decls_.lookupFirst(name) == prevDecl); michael@0: decls_.remove(name); michael@0: } michael@0: michael@0: template michael@0: void michael@0: ParseContext::updateDecl(JSAtom *atom, Node pn) michael@0: { michael@0: Definition *oldDecl = decls_.lookupFirst(atom); michael@0: michael@0: pn->setDefn(true); michael@0: Definition *newDecl = (Definition *)pn; michael@0: decls_.updateFirst(atom, newDecl); michael@0: michael@0: if (!sc->isFunctionBox()) { michael@0: JS_ASSERT(newDecl->isFreeVar()); michael@0: return; michael@0: } michael@0: michael@0: JS_ASSERT(oldDecl->isBound()); michael@0: JS_ASSERT(!oldDecl->pn_cookie.isFree()); michael@0: newDecl->pn_cookie = oldDecl->pn_cookie; michael@0: newDecl->pn_dflags |= PND_BOUND; michael@0: if (IsArgOp(oldDecl->getOp())) { michael@0: newDecl->setOp(JSOP_GETARG); michael@0: JS_ASSERT(args_[oldDecl->pn_cookie.slot()] == oldDecl); michael@0: args_[oldDecl->pn_cookie.slot()] = newDecl; michael@0: } else { michael@0: JS_ASSERT(IsLocalOp(oldDecl->getOp())); michael@0: newDecl->setOp(JSOP_GETLOCAL); michael@0: JS_ASSERT(vars_[oldDecl->pn_cookie.slot()] == oldDecl); michael@0: vars_[oldDecl->pn_cookie.slot()] = newDecl; michael@0: } michael@0: } michael@0: michael@0: template michael@0: void michael@0: ParseContext::popLetDecl(JSAtom *atom) michael@0: { michael@0: JS_ASSERT(ParseHandler::getDefinitionKind(decls_.lookupFirst(atom)) == Definition::LET); michael@0: decls_.remove(atom); michael@0: } michael@0: michael@0: template michael@0: static void michael@0: AppendPackedBindings(const ParseContext *pc, const DeclVector &vec, Binding *dst) michael@0: { michael@0: for (size_t i = 0; i < vec.length(); ++i, ++dst) { michael@0: Definition *dn = vec[i]; michael@0: PropertyName *name = dn->name(); michael@0: michael@0: Binding::Kind kind; michael@0: switch (dn->kind()) { michael@0: case Definition::VAR: michael@0: kind = Binding::VARIABLE; michael@0: break; michael@0: case Definition::CONST: michael@0: kind = Binding::CONSTANT; michael@0: break; michael@0: case Definition::ARG: michael@0: kind = Binding::ARGUMENT; michael@0: break; michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("unexpected dn->kind"); michael@0: } michael@0: michael@0: /* michael@0: * Bindings::init does not check for duplicates so we must ensure that michael@0: * only one binding with a given name is marked aliased. pc->decls michael@0: * maintains the canonical definition for each name, so use that. michael@0: */ michael@0: JS_ASSERT_IF(dn->isClosed(), pc->decls().lookupFirst(name) == dn); michael@0: bool aliased = dn->isClosed() || michael@0: (pc->sc->allLocalsAliased() && michael@0: pc->decls().lookupFirst(name) == dn); michael@0: michael@0: *dst = Binding(name, kind, aliased); michael@0: } michael@0: } michael@0: michael@0: template michael@0: bool michael@0: ParseContext::generateFunctionBindings(ExclusiveContext *cx, TokenStream &ts, michael@0: LifoAlloc &alloc, michael@0: InternalHandle bindings) const michael@0: { michael@0: JS_ASSERT(sc->isFunctionBox()); michael@0: JS_ASSERT(args_.length() < ARGNO_LIMIT); michael@0: JS_ASSERT(vars_.length() < LOCALNO_LIMIT); michael@0: michael@0: /* michael@0: * Avoid pathological edge cases by explicitly limiting the total number of michael@0: * bindings to what will fit in a uint32_t. michael@0: */ michael@0: if (UINT32_MAX - args_.length() <= vars_.length()) michael@0: return ts.reportError(JSMSG_TOO_MANY_LOCALS); michael@0: michael@0: uint32_t count = args_.length() + vars_.length(); michael@0: Binding *packedBindings = alloc.newArrayUninitialized(count); michael@0: if (!packedBindings) { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: michael@0: AppendPackedBindings(this, args_, packedBindings); michael@0: AppendPackedBindings(this, vars_, packedBindings + args_.length()); michael@0: michael@0: return Bindings::initWithTemporaryStorage(cx, bindings, args_.length(), vars_.length(), michael@0: packedBindings, blockScopeDepth); michael@0: } michael@0: michael@0: template michael@0: bool michael@0: Parser::reportHelper(ParseReportKind kind, bool strict, uint32_t offset, michael@0: unsigned errorNumber, va_list args) michael@0: { michael@0: bool result = false; michael@0: switch (kind) { michael@0: case ParseError: michael@0: result = tokenStream.reportCompileErrorNumberVA(offset, JSREPORT_ERROR, errorNumber, args); michael@0: break; michael@0: case ParseWarning: michael@0: result = michael@0: tokenStream.reportCompileErrorNumberVA(offset, JSREPORT_WARNING, errorNumber, args); michael@0: break; michael@0: case ParseExtraWarning: michael@0: result = tokenStream.reportStrictWarningErrorNumberVA(offset, errorNumber, args); michael@0: break; michael@0: case ParseStrictError: michael@0: result = tokenStream.reportStrictModeErrorNumberVA(offset, strict, errorNumber, args); michael@0: break; michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: template michael@0: bool michael@0: Parser::report(ParseReportKind kind, bool strict, Node pn, unsigned errorNumber, ...) michael@0: { michael@0: uint32_t offset = (pn ? handler.getPosition(pn) : pos()).begin; michael@0: michael@0: va_list args; michael@0: va_start(args, errorNumber); michael@0: bool result = reportHelper(kind, strict, offset, errorNumber, args); michael@0: va_end(args); michael@0: return result; michael@0: } michael@0: michael@0: template michael@0: bool michael@0: Parser::reportNoOffset(ParseReportKind kind, bool strict, unsigned errorNumber, ...) michael@0: { michael@0: va_list args; michael@0: va_start(args, errorNumber); michael@0: bool result = reportHelper(kind, strict, TokenStream::NoOffset, errorNumber, args); michael@0: va_end(args); michael@0: return result; michael@0: } michael@0: michael@0: template michael@0: bool michael@0: Parser::reportWithOffset(ParseReportKind kind, bool strict, uint32_t offset, michael@0: unsigned errorNumber, ...) michael@0: { michael@0: va_list args; michael@0: va_start(args, errorNumber); michael@0: bool result = reportHelper(kind, strict, offset, errorNumber, args); michael@0: va_end(args); michael@0: return result; michael@0: } michael@0: michael@0: template <> michael@0: bool michael@0: Parser::abortIfSyntaxParser() michael@0: { michael@0: handler.disableSyntaxParser(); michael@0: return true; michael@0: } michael@0: michael@0: template <> michael@0: bool michael@0: Parser::abortIfSyntaxParser() michael@0: { michael@0: abortedSyntaxParse = true; michael@0: return false; michael@0: } michael@0: michael@0: template michael@0: Parser::Parser(ExclusiveContext *cx, LifoAlloc *alloc, michael@0: const ReadOnlyCompileOptions &options, michael@0: const jschar *chars, size_t length, bool foldConstants, michael@0: Parser *syntaxParser, michael@0: LazyScript *lazyOuterFunction) michael@0: : AutoGCRooter(cx, PARSER), michael@0: context(cx), michael@0: alloc(*alloc), michael@0: tokenStream(cx, options, chars, length, thisForCtor()), michael@0: traceListHead(nullptr), michael@0: pc(nullptr), michael@0: sct(nullptr), michael@0: ss(nullptr), michael@0: keepAtoms(cx->perThreadData), michael@0: foldConstants(foldConstants), michael@0: abortedSyntaxParse(false), michael@0: isUnexpectedEOF_(false), michael@0: handler(cx, *alloc, tokenStream, foldConstants, syntaxParser, lazyOuterFunction) michael@0: { michael@0: { michael@0: AutoLockForExclusiveAccess lock(cx); michael@0: cx->perThreadData->addActiveCompilation(); michael@0: } michael@0: michael@0: // The Mozilla specific JSOPTION_EXTRA_WARNINGS option adds extra warnings michael@0: // which are not generated if functions are parsed lazily. Note that the michael@0: // standard "use strict" does not inhibit lazy parsing. michael@0: if (options.extraWarningsOption) michael@0: handler.disableSyntaxParser(); michael@0: michael@0: tempPoolMark = alloc->mark(); michael@0: } michael@0: michael@0: template michael@0: Parser::~Parser() michael@0: { michael@0: alloc.release(tempPoolMark); michael@0: michael@0: /* michael@0: * The parser can allocate enormous amounts of memory for large functions. michael@0: * Eagerly free the memory now (which otherwise won't be freed until the michael@0: * next GC) to avoid unnecessary OOMs. michael@0: */ michael@0: alloc.freeAllIfHugeAndUnused(); michael@0: michael@0: { michael@0: AutoLockForExclusiveAccess lock(context); michael@0: context->perThreadData->removeActiveCompilation(); michael@0: } michael@0: } michael@0: michael@0: template michael@0: ObjectBox * michael@0: Parser::newObjectBox(JSObject *obj) michael@0: { michael@0: JS_ASSERT(obj && !IsPoisonedPtr(obj)); michael@0: michael@0: /* michael@0: * We use JSContext.tempLifoAlloc to allocate parsed objects and place them michael@0: * on a list in this Parser to ensure GC safety. Thus the tempLifoAlloc michael@0: * arenas containing the entries must be alive until we are done with michael@0: * scanning, parsing and code generation for the whole script or top-level michael@0: * function. michael@0: */ michael@0: michael@0: ObjectBox *objbox = alloc.new_(obj, traceListHead); michael@0: if (!objbox) { michael@0: js_ReportOutOfMemory(context); michael@0: return nullptr; michael@0: } michael@0: michael@0: traceListHead = objbox; michael@0: michael@0: return objbox; michael@0: } michael@0: michael@0: template michael@0: FunctionBox::FunctionBox(ExclusiveContext *cx, ObjectBox* traceListHead, JSFunction *fun, michael@0: ParseContext *outerpc, Directives directives, michael@0: bool extraWarnings, GeneratorKind generatorKind) michael@0: : ObjectBox(fun, traceListHead), michael@0: SharedContext(cx, directives, extraWarnings), michael@0: bindings(), michael@0: bufStart(0), michael@0: bufEnd(0), michael@0: length(0), michael@0: generatorKindBits_(GeneratorKindAsBits(generatorKind)), michael@0: inWith(false), // initialized below michael@0: inGenexpLambda(false), michael@0: hasDestructuringArgs(false), michael@0: useAsm(directives.asmJS()), michael@0: insideUseAsm(outerpc && outerpc->useAsmOrInsideUseAsm()), michael@0: usesArguments(false), michael@0: usesApply(false), michael@0: funCxFlags() michael@0: { michael@0: // Functions created at parse time may be set singleton after parsing and michael@0: // baked into JIT code, so they must be allocated tenured. They are held by michael@0: // the JSScript so cannot be collected during a minor GC anyway. michael@0: JS_ASSERT(fun->isTenured()); michael@0: michael@0: if (!outerpc) { michael@0: inWith = false; michael@0: michael@0: } else if (outerpc->parsingWith) { michael@0: // This covers cases that don't involve eval(). For example: michael@0: // michael@0: // with (o) { (function() { g(); })(); } michael@0: // michael@0: // In this case, |outerpc| corresponds to global code, and michael@0: // outerpc->parsingWith is true. michael@0: inWith = true; michael@0: michael@0: } else if (outerpc->sc->isGlobalSharedContext()) { michael@0: // This covers the case where a function is nested within an eval() michael@0: // within a |with| statement. michael@0: // michael@0: // with (o) { eval("(function() { g(); })();"); } michael@0: // michael@0: // In this case, |outerpc| corresponds to the eval(), michael@0: // outerpc->parsingWith is false because the eval() breaks the michael@0: // ParseContext chain, and |parent| is nullptr (again because of the michael@0: // eval(), so we have to look at |outerpc|'s scopeChain. michael@0: // michael@0: JSObject *scope = outerpc->sc->asGlobalSharedContext()->scopeChain(); michael@0: while (scope) { michael@0: if (scope->is()) michael@0: inWith = true; michael@0: scope = scope->enclosingScope(); michael@0: } michael@0: } else if (outerpc->sc->isFunctionBox()) { michael@0: // This is like the above case, but for more deeply nested functions. michael@0: // For example: michael@0: // michael@0: // with (o) { eval("(function() { (function() { g(); })(); })();"); } } michael@0: // michael@0: // In this case, the inner anonymous function needs to inherit the michael@0: // setting of |inWith| from the outer one. michael@0: FunctionBox *parent = outerpc->sc->asFunctionBox(); michael@0: if (parent && parent->inWith) michael@0: inWith = true; michael@0: } michael@0: } michael@0: michael@0: template michael@0: FunctionBox * michael@0: Parser::newFunctionBox(Node fn, JSFunction *fun, ParseContext *outerpc, michael@0: Directives inheritedDirectives, GeneratorKind generatorKind) michael@0: { michael@0: JS_ASSERT(fun && !IsPoisonedPtr(fun)); michael@0: michael@0: /* michael@0: * We use JSContext.tempLifoAlloc to allocate parsed objects and place them michael@0: * on a list in this Parser to ensure GC safety. Thus the tempLifoAlloc michael@0: * arenas containing the entries must be alive until we are done with michael@0: * scanning, parsing and code generation for the whole script or top-level michael@0: * function. michael@0: */ michael@0: FunctionBox *funbox = michael@0: alloc.new_(context, traceListHead, fun, outerpc, michael@0: inheritedDirectives, options().extraWarningsOption, michael@0: generatorKind); michael@0: if (!funbox) { michael@0: js_ReportOutOfMemory(context); michael@0: return nullptr; michael@0: } michael@0: michael@0: traceListHead = funbox; michael@0: if (fn) michael@0: handler.setFunctionBox(fn, funbox); michael@0: michael@0: return funbox; michael@0: } michael@0: michael@0: template michael@0: void michael@0: Parser::trace(JSTracer *trc) michael@0: { michael@0: traceListHead->trace(trc); michael@0: } michael@0: michael@0: void michael@0: MarkParser(JSTracer *trc, AutoGCRooter *parser) michael@0: { michael@0: static_cast *>(parser)->trace(trc); michael@0: } michael@0: michael@0: /* michael@0: * Parse a top-level JS script. michael@0: */ michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::parse(JSObject *chain) michael@0: { michael@0: /* michael@0: * Protect atoms from being collected by a GC activation, which might michael@0: * - nest on this thread due to out of memory (the so-called "last ditch" michael@0: * GC attempted within js_NewGCThing), or michael@0: * - run for any reason on another thread if this thread is suspended on michael@0: * an object lock before it finishes generating bytecode into a script michael@0: * protected from the GC by a root or a stack frame reference. michael@0: */ michael@0: Directives directives(options().strictOption); michael@0: GlobalSharedContext globalsc(context, chain, directives, options().extraWarningsOption); michael@0: ParseContext globalpc(this, /* parent = */ nullptr, ParseHandler::null(), michael@0: &globalsc, /* newDirectives = */ nullptr, michael@0: /* staticLevel = */ 0, /* bodyid = */ 0, michael@0: /* blockScopeDepth = */ 0); michael@0: if (!globalpc.init(tokenStream)) michael@0: return null(); michael@0: michael@0: Node pn = statements(); michael@0: if (pn) { michael@0: if (!tokenStream.matchToken(TOK_EOF)) { michael@0: report(ParseError, false, null(), JSMSG_SYNTAX_ERROR); michael@0: return null(); michael@0: } michael@0: if (foldConstants) { michael@0: if (!FoldConstants(context, &pn, this)) michael@0: return null(); michael@0: } michael@0: } michael@0: return pn; michael@0: } michael@0: michael@0: /* michael@0: * Insist on a final return before control flows out of pn. Try to be a bit michael@0: * smart about loops: do {...; return e2;} while(0) at the end of a function michael@0: * that contains an early return e1 will get a strict warning. Similarly for michael@0: * iloops: while (true){...} is treated as though ... returns. michael@0: */ michael@0: enum { michael@0: ENDS_IN_OTHER = 0, michael@0: ENDS_IN_RETURN = 1, michael@0: ENDS_IN_BREAK = 2 michael@0: }; michael@0: michael@0: static int michael@0: HasFinalReturn(ParseNode *pn) michael@0: { michael@0: ParseNode *pn2, *pn3; michael@0: unsigned rv, rv2, hasDefault; michael@0: michael@0: switch (pn->getKind()) { michael@0: case PNK_STATEMENTLIST: michael@0: if (!pn->pn_head) michael@0: return ENDS_IN_OTHER; michael@0: return HasFinalReturn(pn->last()); michael@0: michael@0: case PNK_IF: michael@0: if (!pn->pn_kid3) michael@0: return ENDS_IN_OTHER; michael@0: return HasFinalReturn(pn->pn_kid2) & HasFinalReturn(pn->pn_kid3); michael@0: michael@0: case PNK_WHILE: michael@0: pn2 = pn->pn_left; michael@0: if (pn2->isKind(PNK_TRUE)) michael@0: return ENDS_IN_RETURN; michael@0: if (pn2->isKind(PNK_NUMBER) && pn2->pn_dval) michael@0: return ENDS_IN_RETURN; michael@0: return ENDS_IN_OTHER; michael@0: michael@0: case PNK_DOWHILE: michael@0: pn2 = pn->pn_right; michael@0: if (pn2->isKind(PNK_FALSE)) michael@0: return HasFinalReturn(pn->pn_left); michael@0: if (pn2->isKind(PNK_TRUE)) michael@0: return ENDS_IN_RETURN; michael@0: if (pn2->isKind(PNK_NUMBER)) { michael@0: if (pn2->pn_dval == 0) michael@0: return HasFinalReturn(pn->pn_left); michael@0: return ENDS_IN_RETURN; michael@0: } michael@0: return ENDS_IN_OTHER; michael@0: michael@0: case PNK_FOR: michael@0: pn2 = pn->pn_left; michael@0: if (pn2->isArity(PN_TERNARY) && !pn2->pn_kid2) michael@0: return ENDS_IN_RETURN; michael@0: return ENDS_IN_OTHER; michael@0: michael@0: case PNK_SWITCH: michael@0: rv = ENDS_IN_RETURN; michael@0: hasDefault = ENDS_IN_OTHER; michael@0: pn2 = pn->pn_right; michael@0: if (pn2->isKind(PNK_LEXICALSCOPE)) michael@0: pn2 = pn2->expr(); michael@0: for (pn2 = pn2->pn_head; rv && pn2; pn2 = pn2->pn_next) { michael@0: if (pn2->isKind(PNK_DEFAULT)) michael@0: hasDefault = ENDS_IN_RETURN; michael@0: pn3 = pn2->pn_right; michael@0: JS_ASSERT(pn3->isKind(PNK_STATEMENTLIST)); michael@0: if (pn3->pn_head) { michael@0: rv2 = HasFinalReturn(pn3->last()); michael@0: if (rv2 == ENDS_IN_OTHER && pn2->pn_next) michael@0: /* Falling through to next case or default. */; michael@0: else michael@0: rv &= rv2; michael@0: } michael@0: } michael@0: /* If a final switch has no default case, we judge it harshly. */ michael@0: rv &= hasDefault; michael@0: return rv; michael@0: michael@0: case PNK_BREAK: michael@0: return ENDS_IN_BREAK; michael@0: michael@0: case PNK_WITH: michael@0: return HasFinalReturn(pn->pn_right); michael@0: michael@0: case PNK_RETURN: michael@0: return ENDS_IN_RETURN; michael@0: michael@0: case PNK_COLON: michael@0: case PNK_LEXICALSCOPE: michael@0: return HasFinalReturn(pn->expr()); michael@0: michael@0: case PNK_THROW: michael@0: return ENDS_IN_RETURN; michael@0: michael@0: case PNK_TRY: michael@0: /* If we have a finally block that returns, we are done. */ michael@0: if (pn->pn_kid3) { michael@0: rv = HasFinalReturn(pn->pn_kid3); michael@0: if (rv == ENDS_IN_RETURN) michael@0: return rv; michael@0: } michael@0: michael@0: /* Else check the try block and any and all catch statements. */ michael@0: rv = HasFinalReturn(pn->pn_kid1); michael@0: if (pn->pn_kid2) { michael@0: JS_ASSERT(pn->pn_kid2->isArity(PN_LIST)); michael@0: for (pn2 = pn->pn_kid2->pn_head; pn2; pn2 = pn2->pn_next) michael@0: rv &= HasFinalReturn(pn2); michael@0: } michael@0: return rv; michael@0: michael@0: case PNK_CATCH: michael@0: /* Check this catch block's body. */ michael@0: return HasFinalReturn(pn->pn_kid3); michael@0: michael@0: case PNK_LET: michael@0: /* Non-binary let statements are let declarations. */ michael@0: if (!pn->isArity(PN_BINARY)) michael@0: return ENDS_IN_OTHER; michael@0: return HasFinalReturn(pn->pn_right); michael@0: michael@0: default: michael@0: return ENDS_IN_OTHER; michael@0: } michael@0: } michael@0: michael@0: static int michael@0: HasFinalReturn(SyntaxParseHandler::Node pn) michael@0: { michael@0: return ENDS_IN_RETURN; michael@0: } michael@0: michael@0: template michael@0: bool michael@0: Parser::reportBadReturn(Node pn, ParseReportKind kind, michael@0: unsigned errnum, unsigned anonerrnum) michael@0: { michael@0: JSAutoByteString name; michael@0: JSAtom *atom = pc->sc->asFunctionBox()->function()->atom(); michael@0: if (atom) { michael@0: if (!AtomToPrintableString(context, atom, &name)) michael@0: return false; michael@0: } else { michael@0: errnum = anonerrnum; michael@0: } michael@0: return report(kind, pc->sc->strict, pn, errnum, name.ptr()); michael@0: } michael@0: michael@0: template michael@0: bool michael@0: Parser::checkFinalReturn(Node pn) michael@0: { michael@0: JS_ASSERT(pc->sc->isFunctionBox()); michael@0: return HasFinalReturn(pn) == ENDS_IN_RETURN || michael@0: reportBadReturn(pn, ParseExtraWarning, michael@0: JSMSG_NO_RETURN_VALUE, JSMSG_ANON_NO_RETURN_VALUE); michael@0: } michael@0: michael@0: /* michael@0: * Check that assigning to lhs is permitted. Assigning to 'eval' or michael@0: * 'arguments' is banned in strict mode and in destructuring assignment. michael@0: */ michael@0: template michael@0: bool michael@0: Parser::checkStrictAssignment(Node lhs, AssignmentFlavor flavor) michael@0: { michael@0: if (!pc->sc->needStrictChecks() && flavor != KeyedDestructuringAssignment) michael@0: return true; michael@0: michael@0: JSAtom *atom = handler.isName(lhs); michael@0: if (!atom) michael@0: return true; michael@0: michael@0: if (atom == context->names().eval || atom == context->names().arguments) { michael@0: JSAutoByteString name; michael@0: if (!AtomToPrintableString(context, atom, &name)) michael@0: return false; michael@0: michael@0: ParseReportKind kind; michael@0: unsigned errnum; michael@0: if (pc->sc->strict || flavor != KeyedDestructuringAssignment) { michael@0: kind = ParseStrictError; michael@0: errnum = JSMSG_BAD_STRICT_ASSIGN; michael@0: } else { michael@0: kind = ParseError; michael@0: errnum = JSMSG_BAD_DESTRUCT_ASSIGN; michael@0: } michael@0: if (!report(kind, pc->sc->strict, lhs, errnum, name.ptr())) michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Check that it is permitted to introduce a binding for atom. Strict mode michael@0: * forbids introducing new definitions for 'eval', 'arguments', or for any michael@0: * strict mode reserved keyword. Use pn for reporting error locations, or use michael@0: * pc's token stream if pn is nullptr. michael@0: */ michael@0: template michael@0: bool michael@0: Parser::checkStrictBinding(PropertyName *name, Node pn) michael@0: { michael@0: if (!pc->sc->needStrictChecks()) michael@0: return true; michael@0: michael@0: if (name == context->names().eval || name == context->names().arguments || IsKeyword(name)) { michael@0: JSAutoByteString bytes; michael@0: if (!AtomToPrintableString(context, name, &bytes)) michael@0: return false; michael@0: return report(ParseStrictError, pc->sc->strict, pn, michael@0: JSMSG_BAD_BINDING, bytes.ptr()); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template <> michael@0: ParseNode * michael@0: Parser::standaloneFunctionBody(HandleFunction fun, const AutoNameVector &formals, michael@0: GeneratorKind generatorKind, michael@0: Directives inheritedDirectives, michael@0: Directives *newDirectives) michael@0: { michael@0: Node fn = handler.newFunctionDefinition(); michael@0: if (!fn) michael@0: return null(); michael@0: michael@0: ParseNode *argsbody = ListNode::create(PNK_ARGSBODY, &handler); michael@0: if (!argsbody) michael@0: return null(); michael@0: argsbody->setOp(JSOP_NOP); michael@0: argsbody->makeEmpty(); michael@0: fn->pn_body = argsbody; michael@0: michael@0: FunctionBox *funbox = newFunctionBox(fn, fun, /* outerpc = */ nullptr, inheritedDirectives, michael@0: generatorKind); michael@0: if (!funbox) michael@0: return null(); michael@0: funbox->length = fun->nargs() - fun->hasRest(); michael@0: handler.setFunctionBox(fn, funbox); michael@0: michael@0: ParseContext funpc(this, pc, fn, funbox, newDirectives, michael@0: /* staticLevel = */ 0, /* bodyid = */ 0, michael@0: /* blockScopeDepth = */ 0); michael@0: if (!funpc.init(tokenStream)) michael@0: return null(); michael@0: michael@0: for (unsigned i = 0; i < formals.length(); i++) { michael@0: if (!defineArg(fn, formals[i])) michael@0: return null(); michael@0: } michael@0: michael@0: ParseNode *pn = functionBody(Statement, StatementListBody); michael@0: if (!pn) michael@0: return null(); michael@0: michael@0: if (!tokenStream.matchToken(TOK_EOF)) { michael@0: report(ParseError, false, null(), JSMSG_SYNTAX_ERROR); michael@0: return null(); michael@0: } michael@0: michael@0: if (!FoldConstants(context, &pn, this)) michael@0: return null(); michael@0: michael@0: InternalHandle funboxBindings = michael@0: InternalHandle::fromMarkedLocation(&funbox->bindings); michael@0: if (!funpc.generateFunctionBindings(context, tokenStream, alloc, funboxBindings)) michael@0: return null(); michael@0: michael@0: JS_ASSERT(fn->pn_body->isKind(PNK_ARGSBODY)); michael@0: fn->pn_body->append(pn); michael@0: fn->pn_body->pn_pos = pn->pn_pos; michael@0: return fn; michael@0: } michael@0: michael@0: template <> michael@0: bool michael@0: Parser::checkFunctionArguments() michael@0: { michael@0: /* michael@0: * Non-top-level functions use JSOP_DEFFUN which is a dynamic scope michael@0: * operation which means it aliases any bindings with the same name. michael@0: */ michael@0: if (FuncStmtSet *set = pc->funcStmts) { michael@0: for (FuncStmtSet::Range r = set->all(); !r.empty(); r.popFront()) { michael@0: PropertyName *name = r.front()->asPropertyName(); michael@0: if (Definition *dn = pc->decls().lookupFirst(name)) michael@0: dn->pn_dflags |= PND_CLOSED; michael@0: } michael@0: } michael@0: michael@0: /* Time to implement the odd semantics of 'arguments'. */ michael@0: HandlePropertyName arguments = context->names().arguments; michael@0: michael@0: /* michael@0: * As explained by the ContextFlags::funArgumentsHasLocalBinding comment, michael@0: * create a declaration for 'arguments' if there are any unbound uses in michael@0: * the function body. michael@0: */ michael@0: for (AtomDefnRange r = pc->lexdeps->all(); !r.empty(); r.popFront()) { michael@0: if (r.front().key() == arguments) { michael@0: Definition *dn = r.front().value().get(); michael@0: pc->lexdeps->remove(arguments); michael@0: dn->pn_dflags |= PND_IMPLICITARGUMENTS; michael@0: if (!pc->define(tokenStream, arguments, dn, Definition::VAR)) michael@0: return false; michael@0: pc->sc->asFunctionBox()->usesArguments = true; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Report error if both rest parameters and 'arguments' are used. Do this michael@0: * check before adding artificial 'arguments' below. michael@0: */ michael@0: Definition *maybeArgDef = pc->decls().lookupFirst(arguments); michael@0: bool argumentsHasBinding = !!maybeArgDef; michael@0: bool argumentsHasLocalBinding = maybeArgDef && maybeArgDef->kind() != Definition::ARG; michael@0: bool hasRest = pc->sc->asFunctionBox()->function()->hasRest(); michael@0: if (hasRest && argumentsHasLocalBinding) { michael@0: report(ParseError, false, nullptr, JSMSG_ARGUMENTS_AND_REST); michael@0: return false; michael@0: } michael@0: michael@0: /* michael@0: * Even if 'arguments' isn't explicitly mentioned, dynamic name lookup michael@0: * forces an 'arguments' binding. The exception is that functions with rest michael@0: * parameters are free from 'arguments'. michael@0: */ michael@0: if (!argumentsHasBinding && pc->sc->bindingsAccessedDynamically() && !hasRest) { michael@0: ParseNode *pn = newName(arguments); michael@0: if (!pn) michael@0: return false; michael@0: if (!pc->define(tokenStream, arguments, pn, Definition::VAR)) michael@0: return false; michael@0: argumentsHasBinding = true; michael@0: argumentsHasLocalBinding = true; michael@0: } michael@0: michael@0: /* michael@0: * Now that all possible 'arguments' bindings have been added, note whether michael@0: * 'arguments' has a local binding and whether it unconditionally needs an michael@0: * arguments object. (Also see the flags' comments in ContextFlags.) michael@0: */ michael@0: if (argumentsHasLocalBinding) { michael@0: FunctionBox *funbox = pc->sc->asFunctionBox(); michael@0: funbox->setArgumentsHasLocalBinding(); michael@0: michael@0: /* michael@0: * If a script has both explicit mentions of 'arguments' and dynamic michael@0: * name lookups which could access the arguments, an arguments object michael@0: * must be created eagerly. The SSA analysis used for lazy arguments michael@0: * cannot cope with dynamic name accesses, so any 'arguments' accessed michael@0: * via a NAME opcode must force construction of the arguments object. michael@0: */ michael@0: if (pc->sc->bindingsAccessedDynamically() && maybeArgDef) michael@0: funbox->setDefinitelyNeedsArgsObj(); michael@0: michael@0: /* michael@0: * If a script contains the debugger statement either directly or michael@0: * within an inner function, the arguments object must be created michael@0: * eagerly. The debugger can walk the scope chain and observe any michael@0: * values along it. michael@0: */ michael@0: if (pc->sc->hasDebuggerStatement()) michael@0: funbox->setDefinitelyNeedsArgsObj(); michael@0: michael@0: /* michael@0: * Check whether any parameters have been assigned within this michael@0: * function. In strict mode parameters do not alias arguments[i], and michael@0: * to make the arguments object reflect initial parameter values prior michael@0: * to any mutation we create it eagerly whenever parameters are (or michael@0: * might, in the case of calls to eval) be assigned. michael@0: */ michael@0: if (pc->sc->needStrictChecks()) { michael@0: for (AtomDefnListMap::Range r = pc->decls().all(); !r.empty(); r.popFront()) { michael@0: DefinitionList &dlist = r.front().value(); michael@0: for (DefinitionList::Range dr = dlist.all(); !dr.empty(); dr.popFront()) { michael@0: Definition *dn = dr.front(); michael@0: if (dn->kind() == Definition::ARG && dn->isAssigned()) michael@0: funbox->setDefinitelyNeedsArgsObj(); michael@0: } michael@0: } michael@0: /* Watch for mutation of arguments through e.g. eval(). */ michael@0: if (pc->sc->bindingsAccessedDynamically()) michael@0: funbox->setDefinitelyNeedsArgsObj(); michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template <> michael@0: bool michael@0: Parser::checkFunctionArguments() michael@0: { michael@0: bool hasRest = pc->sc->asFunctionBox()->function()->hasRest(); michael@0: michael@0: if (pc->lexdeps->lookup(context->names().arguments)) { michael@0: pc->sc->asFunctionBox()->usesArguments = true; michael@0: if (hasRest) { michael@0: report(ParseError, false, null(), JSMSG_ARGUMENTS_AND_REST); michael@0: return false; michael@0: } michael@0: } else if (hasRest) { michael@0: DefinitionNode maybeArgDef = pc->decls().lookupFirst(context->names().arguments); michael@0: if (maybeArgDef && handler.getDefinitionKind(maybeArgDef) != Definition::ARG) { michael@0: report(ParseError, false, null(), JSMSG_ARGUMENTS_AND_REST); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::functionBody(FunctionSyntaxKind kind, FunctionBodyType type) michael@0: { michael@0: JS_ASSERT(pc->sc->isFunctionBox()); michael@0: JS_ASSERT(!pc->funHasReturnExpr && !pc->funHasReturnVoid); michael@0: michael@0: #ifdef DEBUG michael@0: uint32_t startYieldOffset = pc->lastYieldOffset; michael@0: #endif michael@0: michael@0: Node pn; michael@0: if (type == StatementListBody) { michael@0: pn = statements(); michael@0: if (!pn) michael@0: return null(); michael@0: } else { michael@0: JS_ASSERT(type == ExpressionBody); michael@0: JS_ASSERT(JS_HAS_EXPR_CLOSURES); michael@0: michael@0: Node kid = assignExpr(); michael@0: if (!kid) michael@0: return null(); michael@0: michael@0: pn = handler.newReturnStatement(kid, handler.getPosition(kid)); michael@0: if (!pn) michael@0: return null(); michael@0: } michael@0: michael@0: switch (pc->generatorKind()) { michael@0: case NotGenerator: michael@0: JS_ASSERT(pc->lastYieldOffset == startYieldOffset); michael@0: break; michael@0: michael@0: case LegacyGenerator: michael@0: // FIXME: Catch these errors eagerly, in yieldExpression(). michael@0: JS_ASSERT(pc->lastYieldOffset != startYieldOffset); michael@0: if (kind == Arrow) { michael@0: reportWithOffset(ParseError, false, pc->lastYieldOffset, michael@0: JSMSG_YIELD_IN_ARROW, js_yield_str); michael@0: return null(); michael@0: } michael@0: if (type == ExpressionBody) { michael@0: reportBadReturn(pn, ParseError, michael@0: JSMSG_BAD_GENERATOR_RETURN, michael@0: JSMSG_BAD_ANON_GENERATOR_RETURN); michael@0: return null(); michael@0: } michael@0: break; michael@0: michael@0: case StarGenerator: michael@0: JS_ASSERT(kind != Arrow); michael@0: JS_ASSERT(type == StatementListBody); michael@0: break; michael@0: } michael@0: michael@0: /* Check for falling off the end of a function that returns a value. */ michael@0: if (options().extraWarningsOption && pc->funHasReturnExpr && !checkFinalReturn(pn)) michael@0: return null(); michael@0: michael@0: /* Define the 'arguments' binding if necessary. */ michael@0: if (!checkFunctionArguments()) michael@0: return null(); michael@0: michael@0: return pn; michael@0: } michael@0: michael@0: /* See comment for use in Parser::functionDef. */ michael@0: template <> michael@0: bool michael@0: Parser::makeDefIntoUse(Definition *dn, ParseNode *pn, JSAtom *atom) michael@0: { michael@0: /* Turn pn into a definition. */ michael@0: pc->updateDecl(atom, pn); michael@0: michael@0: /* Change all uses of dn to be uses of pn. */ michael@0: for (ParseNode *pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) { michael@0: JS_ASSERT(pnu->isUsed()); michael@0: JS_ASSERT(!pnu->isDefn()); michael@0: pnu->pn_lexdef = (Definition *) pn; michael@0: pn->pn_dflags |= pnu->pn_dflags & PND_USE2DEF_FLAGS; michael@0: } michael@0: pn->pn_dflags |= dn->pn_dflags & PND_USE2DEF_FLAGS; michael@0: pn->dn_uses = dn; michael@0: michael@0: /* michael@0: * A PNK_FUNCTION node must be a definition, so convert shadowed function michael@0: * statements into nops. This is valid since all body-level function michael@0: * statement initialization happens at the beginning of the function michael@0: * (thus, only the last statement's effect is visible). E.g., in michael@0: * michael@0: * function outer() { michael@0: * function g() { return 1 } michael@0: * assertEq(g(), 2); michael@0: * function g() { return 2 } michael@0: * assertEq(g(), 2); michael@0: * } michael@0: * michael@0: * both asserts are valid. michael@0: */ michael@0: if (dn->getKind() == PNK_FUNCTION) { michael@0: JS_ASSERT(dn->functionIsHoisted()); michael@0: pn->dn_uses = dn->pn_link; michael@0: handler.prepareNodeForMutation(dn); michael@0: dn->setKind(PNK_NOP); michael@0: dn->setArity(PN_NULLARY); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * If dn is arg, or in [var, const, let] and has an initializer, then we michael@0: * must rewrite it to be an assignment node, whose freshly allocated michael@0: * left-hand side becomes a use of pn. michael@0: */ michael@0: if (dn->canHaveInitializer()) { michael@0: if (ParseNode *rhs = dn->expr()) { michael@0: ParseNode *lhs = handler.makeAssignment(dn, rhs); michael@0: if (!lhs) michael@0: return false; michael@0: pn->dn_uses = lhs; michael@0: dn->pn_link = nullptr; michael@0: dn = (Definition *) lhs; michael@0: } michael@0: } michael@0: michael@0: /* Turn dn into a use of pn. */ michael@0: JS_ASSERT(dn->isKind(PNK_NAME)); michael@0: JS_ASSERT(dn->isArity(PN_NAME)); michael@0: JS_ASSERT(dn->pn_atom == atom); michael@0: dn->setOp((js_CodeSpec[dn->getOp()].format & JOF_SET) ? JSOP_SETNAME : JSOP_NAME); michael@0: dn->setDefn(false); michael@0: dn->setUsed(true); michael@0: dn->pn_lexdef = (Definition *) pn; michael@0: dn->pn_cookie.makeFree(); michael@0: dn->pn_dflags &= ~PND_BOUND; michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Parameter block types for the several Binder functions. We use a common michael@0: * helper function signature in order to share code among destructuring and michael@0: * simple variable declaration parsers. In the destructuring case, the binder michael@0: * function is called indirectly from the variable declaration parser by way michael@0: * of CheckDestructuring and its friends. michael@0: */ michael@0: michael@0: template michael@0: struct BindData michael@0: { michael@0: BindData(ExclusiveContext *cx) : let(cx) {} michael@0: michael@0: typedef bool michael@0: (*Binder)(BindData *data, HandlePropertyName name, Parser *parser); michael@0: michael@0: /* name node for definition processing and error source coordinates */ michael@0: typename ParseHandler::Node pn; michael@0: michael@0: JSOp op; /* prolog bytecode or nop */ michael@0: Binder binder; /* binder, discriminates u */ michael@0: michael@0: struct LetData { michael@0: LetData(ExclusiveContext *cx) : blockObj(cx) {} michael@0: VarContext varContext; michael@0: RootedStaticBlockObject blockObj; michael@0: unsigned overflow; michael@0: } let; michael@0: michael@0: void initLet(VarContext varContext, StaticBlockObject &blockObj, unsigned overflow) { michael@0: this->pn = ParseHandler::null(); michael@0: this->op = JSOP_NOP; michael@0: this->binder = Parser::bindLet; michael@0: this->let.varContext = varContext; michael@0: this->let.blockObj = &blockObj; michael@0: this->let.overflow = overflow; michael@0: } michael@0: michael@0: void initVarOrConst(JSOp op) { michael@0: this->op = op; michael@0: this->binder = Parser::bindVarOrConst; michael@0: } michael@0: }; michael@0: michael@0: template michael@0: JSFunction * michael@0: Parser::newFunction(GenericParseContext *pc, HandleAtom atom, michael@0: FunctionSyntaxKind kind, JSObject *proto) michael@0: { michael@0: JS_ASSERT_IF(kind == Statement, atom != nullptr); michael@0: michael@0: /* michael@0: * Find the global compilation context in order to pre-set the newborn michael@0: * function's parent slot to pc->sc->as()->scopeChain. If the michael@0: * global context is a compile-and-go one, we leave the pre-set parent michael@0: * intact; otherwise we clear parent and proto. michael@0: */ michael@0: while (pc->parent) michael@0: pc = pc->parent; michael@0: michael@0: RootedFunction fun(context); michael@0: JSFunction::Flags flags = (kind == Expression) michael@0: ? JSFunction::INTERPRETED_LAMBDA michael@0: : (kind == Arrow) michael@0: ? JSFunction::INTERPRETED_LAMBDA_ARROW michael@0: : JSFunction::INTERPRETED; michael@0: gc::AllocKind allocKind = JSFunction::FinalizeKind; michael@0: if (kind == Arrow) michael@0: allocKind = JSFunction::ExtendedFinalizeKind; michael@0: fun = NewFunctionWithProto(context, NullPtr(), nullptr, 0, flags, NullPtr(), atom, proto, michael@0: allocKind, MaybeSingletonObject); michael@0: if (!fun) michael@0: return nullptr; michael@0: if (options().selfHostingMode) michael@0: fun->setIsSelfHostedBuiltin(); michael@0: return fun; michael@0: } michael@0: michael@0: static bool michael@0: MatchOrInsertSemicolon(TokenStream &ts) michael@0: { michael@0: TokenKind tt = ts.peekTokenSameLine(TokenStream::Operand); michael@0: if (tt == TOK_ERROR) michael@0: return false; michael@0: if (tt != TOK_EOF && tt != TOK_EOL && tt != TOK_SEMI && tt != TOK_RC) { michael@0: /* Advance the scanner for proper error location reporting. */ michael@0: ts.getToken(TokenStream::Operand); michael@0: ts.reportError(JSMSG_SEMI_BEFORE_STMNT); michael@0: return false; michael@0: } michael@0: (void) ts.matchToken(TOK_SEMI); michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::DefinitionNode michael@0: Parser::getOrCreateLexicalDependency(ParseContext *pc, JSAtom *atom) michael@0: { michael@0: AtomDefnAddPtr p = pc->lexdeps->lookupForAdd(atom); michael@0: if (p) michael@0: return p.value().get(); michael@0: michael@0: DefinitionNode dn = handler.newPlaceholder(atom, pc->blockid(), pos()); michael@0: if (!dn) michael@0: return ParseHandler::nullDefinition(); michael@0: DefinitionSingle def = DefinitionSingle::new_(dn); michael@0: if (!pc->lexdeps->add(p, atom, def)) michael@0: return ParseHandler::nullDefinition(); michael@0: return dn; michael@0: } michael@0: michael@0: static bool michael@0: ConvertDefinitionToNamedLambdaUse(TokenStream &ts, ParseContext *pc, michael@0: FunctionBox *funbox, Definition *dn) michael@0: { michael@0: dn->setOp(JSOP_CALLEE); michael@0: if (!dn->pn_cookie.set(ts, pc->staticLevel, 0)) michael@0: return false; michael@0: dn->pn_dflags |= PND_BOUND; michael@0: JS_ASSERT(dn->kind() == Definition::NAMED_LAMBDA); michael@0: michael@0: /* michael@0: * Since 'dn' is a placeholder, it has not been defined in the michael@0: * ParseContext and hence we must manually flag a closed-over michael@0: * callee name as needing a dynamic scope (this is done for all michael@0: * definitions in the ParseContext by generateFunctionBindings). michael@0: * michael@0: * If 'dn' has been assigned to, then we also flag the function michael@0: * scope has needing a dynamic scope so that dynamic scope michael@0: * setter can either ignore the set (in non-strict mode) or michael@0: * produce an error (in strict mode). michael@0: */ michael@0: if (dn->isClosed() || dn->isAssigned()) michael@0: funbox->setNeedsDeclEnvObject(); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Beware: this function is called for functions nested in other functions or michael@0: * global scripts but not for functions compiled through the Function michael@0: * constructor or JSAPI. To always execute code when a function has finished michael@0: * parsing, use Parser::functionBody. michael@0: */ michael@0: template <> michael@0: bool michael@0: Parser::leaveFunction(ParseNode *fn, ParseContext *outerpc, michael@0: FunctionSyntaxKind kind) michael@0: { michael@0: outerpc->blockidGen = pc->blockidGen; michael@0: michael@0: FunctionBox *funbox = fn->pn_funbox; michael@0: JS_ASSERT(funbox == pc->sc->asFunctionBox()); michael@0: michael@0: /* Propagate unresolved lexical names up to outerpc->lexdeps. */ michael@0: if (pc->lexdeps->count()) { michael@0: for (AtomDefnRange r = pc->lexdeps->all(); !r.empty(); r.popFront()) { michael@0: JSAtom *atom = r.front().key(); michael@0: Definition *dn = r.front().value().get(); michael@0: JS_ASSERT(dn->isPlaceholder()); michael@0: michael@0: if (atom == funbox->function()->name() && kind == Expression) { michael@0: if (!ConvertDefinitionToNamedLambdaUse(tokenStream, pc, funbox, dn)) michael@0: return false; michael@0: continue; michael@0: } michael@0: michael@0: Definition *outer_dn = outerpc->decls().lookupFirst(atom); michael@0: michael@0: /* michael@0: * Make sure to deoptimize lexical dependencies that are polluted michael@0: * by eval and function statements (which both flag the function as michael@0: * having an extensible scope) or any enclosing 'with'. michael@0: */ michael@0: if (funbox->hasExtensibleScope() || outerpc->parsingWith) michael@0: handler.deoptimizeUsesWithin(dn, fn->pn_pos); michael@0: michael@0: if (!outer_dn) { michael@0: /* michael@0: * Create a new placeholder for our outer lexdep. We could michael@0: * simply re-use the inner placeholder, but that introduces michael@0: * subtleties in the case where we find a later definition michael@0: * that captures an existing lexdep. For example: michael@0: * michael@0: * function f() { function g() { x; } let x; } michael@0: * michael@0: * Here, g's TOK_UPVARS node lists the placeholder for x, michael@0: * which must be captured by the 'let' declaration later, michael@0: * since 'let's are hoisted. Taking g's placeholder as our michael@0: * own would work fine. But consider: michael@0: * michael@0: * function f() { x; { function g() { x; } let x; } } michael@0: * michael@0: * Here, the 'let' must not capture all the uses of f's michael@0: * lexdep entry for x, but it must capture the x node michael@0: * referred to from g's TOK_UPVARS node. Always turning michael@0: * inherited lexdeps into uses of a new outer definition michael@0: * allows us to handle both these cases in a natural way. michael@0: */ michael@0: outer_dn = getOrCreateLexicalDependency(outerpc, atom); michael@0: if (!outer_dn) michael@0: return false; michael@0: } michael@0: michael@0: /* michael@0: * Insert dn's uses list at the front of outer_dn's list. michael@0: * michael@0: * Without loss of generality or correctness, we allow a dn to michael@0: * be in inner and outer lexdeps, since the purpose of lexdeps michael@0: * is one-pass coordination of name use and definition across michael@0: * functions, and if different dn's are used we'll merge lists michael@0: * when leaving the inner function. michael@0: * michael@0: * The dn == outer_dn case arises with generator expressions michael@0: * (see LegacyCompExprTransplanter::transplant, the PN_CODE/PN_NAME michael@0: * case), and nowhere else, currently. michael@0: */ michael@0: if (dn != outer_dn) { michael@0: if (ParseNode *pnu = dn->dn_uses) { michael@0: while (true) { michael@0: pnu->pn_lexdef = outer_dn; michael@0: if (!pnu->pn_link) michael@0: break; michael@0: pnu = pnu->pn_link; michael@0: } michael@0: pnu->pn_link = outer_dn->dn_uses; michael@0: outer_dn->dn_uses = dn->dn_uses; michael@0: dn->dn_uses = nullptr; michael@0: } michael@0: michael@0: outer_dn->pn_dflags |= dn->pn_dflags & ~PND_PLACEHOLDER; michael@0: } michael@0: michael@0: /* Mark the outer dn as escaping. */ michael@0: outer_dn->pn_dflags |= PND_CLOSED; michael@0: } michael@0: } michael@0: michael@0: InternalHandle bindings = michael@0: InternalHandle::fromMarkedLocation(&funbox->bindings); michael@0: return pc->generateFunctionBindings(context, tokenStream, alloc, bindings); michael@0: } michael@0: michael@0: template <> michael@0: bool michael@0: Parser::leaveFunction(Node fn, ParseContext *outerpc, michael@0: FunctionSyntaxKind kind) michael@0: { michael@0: outerpc->blockidGen = pc->blockidGen; michael@0: michael@0: FunctionBox *funbox = pc->sc->asFunctionBox(); michael@0: return addFreeVariablesFromLazyFunction(funbox->function(), outerpc); michael@0: } michael@0: michael@0: /* michael@0: * defineArg is called for both the arguments of a regular function definition michael@0: * and the arguments specified by the Function constructor. michael@0: * michael@0: * The 'disallowDuplicateArgs' bool indicates whether the use of another michael@0: * feature (destructuring or default arguments) disables duplicate arguments. michael@0: * (ECMA-262 requires us to support duplicate parameter names, but, for newer michael@0: * features, we consider the code to have "opted in" to higher standards and michael@0: * forbid duplicates.) michael@0: * michael@0: * If 'duplicatedArg' is non-null, then DefineArg assigns to it any previous michael@0: * argument with the same name. The caller may use this to report an error when michael@0: * one of the abovementioned features occurs after a duplicate. michael@0: */ michael@0: template michael@0: bool michael@0: Parser::defineArg(Node funcpn, HandlePropertyName name, michael@0: bool disallowDuplicateArgs, Node *duplicatedArg) michael@0: { michael@0: SharedContext *sc = pc->sc; michael@0: michael@0: /* Handle duplicate argument names. */ michael@0: if (DefinitionNode prevDecl = pc->decls().lookupFirst(name)) { michael@0: Node pn = handler.getDefinitionNode(prevDecl); michael@0: michael@0: /* michael@0: * Strict-mode disallows duplicate args. We may not know whether we are michael@0: * in strict mode or not (since the function body hasn't been parsed). michael@0: * In such cases, report will queue up the potential error and return michael@0: * 'true'. michael@0: */ michael@0: if (sc->needStrictChecks()) { michael@0: JSAutoByteString bytes; michael@0: if (!AtomToPrintableString(context, name, &bytes)) michael@0: return false; michael@0: if (!report(ParseStrictError, pc->sc->strict, pn, michael@0: JSMSG_DUPLICATE_FORMAL, bytes.ptr())) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (disallowDuplicateArgs) { michael@0: report(ParseError, false, pn, JSMSG_BAD_DUP_ARGS); michael@0: return false; michael@0: } michael@0: michael@0: if (duplicatedArg) michael@0: *duplicatedArg = pn; michael@0: michael@0: /* ParseContext::define assumes and asserts prevDecl is not in decls. */ michael@0: JS_ASSERT(handler.getDefinitionKind(prevDecl) == Definition::ARG); michael@0: pc->prepareToAddDuplicateArg(name, prevDecl); michael@0: } michael@0: michael@0: Node argpn = newName(name); michael@0: if (!argpn) michael@0: return false; michael@0: michael@0: if (!checkStrictBinding(name, argpn)) michael@0: return false; michael@0: michael@0: handler.addFunctionArgument(funcpn, argpn); michael@0: return pc->define(tokenStream, name, argpn, Definition::ARG); michael@0: } michael@0: michael@0: template michael@0: /* static */ bool michael@0: Parser::bindDestructuringArg(BindData *data, michael@0: HandlePropertyName name, Parser *parser) michael@0: { michael@0: ParseContext *pc = parser->pc; michael@0: JS_ASSERT(pc->sc->isFunctionBox()); michael@0: michael@0: if (pc->decls().lookupFirst(name)) { michael@0: parser->report(ParseError, false, null(), JSMSG_BAD_DUP_ARGS); michael@0: return false; michael@0: } michael@0: michael@0: if (!parser->checkStrictBinding(name, data->pn)) michael@0: return false; michael@0: michael@0: return pc->define(parser->tokenStream, name, data->pn, Definition::VAR); michael@0: } michael@0: michael@0: template michael@0: bool michael@0: Parser::functionArguments(FunctionSyntaxKind kind, Node *listp, Node funcpn, michael@0: bool *hasRest) michael@0: { michael@0: FunctionBox *funbox = pc->sc->asFunctionBox(); michael@0: michael@0: *hasRest = false; michael@0: michael@0: bool parenFreeArrow = false; michael@0: if (kind == Arrow && tokenStream.peekToken() == TOK_NAME) { michael@0: parenFreeArrow = true; michael@0: } else { michael@0: if (tokenStream.getToken() != TOK_LP) { michael@0: report(ParseError, false, null(), michael@0: kind == Arrow ? JSMSG_BAD_ARROW_ARGS : JSMSG_PAREN_BEFORE_FORMAL); michael@0: return false; michael@0: } michael@0: michael@0: // Record the start of function source (for FunctionToString). If we michael@0: // are parenFreeArrow, we will set this below, after consuming the NAME. michael@0: funbox->setStart(tokenStream); michael@0: } michael@0: michael@0: Node argsbody = handler.newList(PNK_ARGSBODY); michael@0: if (!argsbody) michael@0: return false; michael@0: handler.setFunctionBody(funcpn, argsbody); michael@0: michael@0: if (parenFreeArrow || !tokenStream.matchToken(TOK_RP)) { michael@0: bool hasDefaults = false; michael@0: Node duplicatedArg = null(); michael@0: Node list = null(); michael@0: michael@0: do { michael@0: if (*hasRest) { michael@0: report(ParseError, false, null(), JSMSG_PARAMETER_AFTER_REST); michael@0: return false; michael@0: } michael@0: michael@0: TokenKind tt = tokenStream.getToken(); michael@0: JS_ASSERT_IF(parenFreeArrow, tt == TOK_NAME); michael@0: switch (tt) { michael@0: case TOK_LB: michael@0: case TOK_LC: michael@0: { michael@0: /* See comment below in the TOK_NAME case. */ michael@0: if (duplicatedArg) { michael@0: report(ParseError, false, duplicatedArg, JSMSG_BAD_DUP_ARGS); michael@0: return false; michael@0: } michael@0: michael@0: if (hasDefaults) { michael@0: report(ParseError, false, null(), JSMSG_NONDEFAULT_FORMAL_AFTER_DEFAULT); michael@0: return false; michael@0: } michael@0: michael@0: funbox->hasDestructuringArgs = true; michael@0: michael@0: /* michael@0: * A destructuring formal parameter turns into one or more michael@0: * local variables initialized from properties of a single michael@0: * anonymous positional parameter, so here we must tweak our michael@0: * binder and its data. michael@0: */ michael@0: BindData data(context); michael@0: data.pn = ParseHandler::null(); michael@0: data.op = JSOP_DEFVAR; michael@0: data.binder = bindDestructuringArg; michael@0: Node lhs = destructuringExpr(&data, tt); michael@0: if (!lhs) michael@0: return false; michael@0: michael@0: /* michael@0: * Synthesize a destructuring assignment from the single michael@0: * anonymous positional parameter into the destructuring michael@0: * left-hand-side expression and accumulate it in list. michael@0: */ michael@0: HandlePropertyName name = context->names().empty; michael@0: Node rhs = newName(name); michael@0: if (!rhs) michael@0: return false; michael@0: michael@0: if (!pc->define(tokenStream, name, rhs, Definition::ARG)) michael@0: return false; michael@0: michael@0: Node item = handler.newBinary(PNK_ASSIGN, lhs, rhs); michael@0: if (!item) michael@0: return false; michael@0: if (list) { michael@0: handler.addList(list, item); michael@0: } else { michael@0: list = handler.newList(PNK_VAR, item); michael@0: if (!list) michael@0: return false; michael@0: *listp = list; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: case TOK_YIELD: michael@0: if (!checkYieldNameValidity()) michael@0: return false; michael@0: goto TOK_NAME; michael@0: michael@0: case TOK_TRIPLEDOT: michael@0: { michael@0: *hasRest = true; michael@0: tt = tokenStream.getToken(); michael@0: if (tt != TOK_NAME) { michael@0: if (tt != TOK_ERROR) michael@0: report(ParseError, false, null(), JSMSG_NO_REST_NAME); michael@0: return false; michael@0: } michael@0: goto TOK_NAME; michael@0: } michael@0: michael@0: TOK_NAME: michael@0: case TOK_NAME: michael@0: { michael@0: if (parenFreeArrow) michael@0: funbox->setStart(tokenStream); michael@0: michael@0: RootedPropertyName name(context, tokenStream.currentName()); michael@0: bool disallowDuplicateArgs = funbox->hasDestructuringArgs || hasDefaults; michael@0: if (!defineArg(funcpn, name, disallowDuplicateArgs, &duplicatedArg)) michael@0: return false; michael@0: michael@0: if (tokenStream.matchToken(TOK_ASSIGN)) { michael@0: // A default argument without parentheses would look like: michael@0: // a = expr => body, but both operators are right-associative, so michael@0: // that would have been parsed as a = (expr => body) instead. michael@0: // Therefore it's impossible to get here with parenFreeArrow. michael@0: JS_ASSERT(!parenFreeArrow); michael@0: michael@0: if (*hasRest) { michael@0: report(ParseError, false, null(), JSMSG_REST_WITH_DEFAULT); michael@0: return false; michael@0: } michael@0: if (duplicatedArg) { michael@0: report(ParseError, false, duplicatedArg, JSMSG_BAD_DUP_ARGS); michael@0: return false; michael@0: } michael@0: if (!hasDefaults) { michael@0: hasDefaults = true; michael@0: michael@0: // The Function.length property is the number of formals michael@0: // before the first default argument. michael@0: funbox->length = pc->numArgs() - 1; michael@0: } michael@0: Node def_expr = assignExprWithoutYield(JSMSG_YIELD_IN_DEFAULT); michael@0: if (!def_expr) michael@0: return false; michael@0: handler.setLastFunctionArgumentDefault(funcpn, def_expr); michael@0: } michael@0: michael@0: break; michael@0: } michael@0: michael@0: default: michael@0: report(ParseError, false, null(), JSMSG_MISSING_FORMAL); michael@0: /* FALL THROUGH */ michael@0: case TOK_ERROR: michael@0: return false; michael@0: } michael@0: } while (!parenFreeArrow && tokenStream.matchToken(TOK_COMMA)); michael@0: michael@0: if (!parenFreeArrow && tokenStream.getToken() != TOK_RP) { michael@0: report(ParseError, false, null(), JSMSG_PAREN_AFTER_FORMAL); michael@0: return false; michael@0: } michael@0: michael@0: if (!hasDefaults) michael@0: funbox->length = pc->numArgs() - *hasRest; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template <> michael@0: bool michael@0: Parser::checkFunctionDefinition(HandlePropertyName funName, michael@0: ParseNode **pn_, FunctionSyntaxKind kind, michael@0: bool *pbodyProcessed) michael@0: { michael@0: ParseNode *&pn = *pn_; michael@0: *pbodyProcessed = false; michael@0: michael@0: /* Function statements add a binding to the enclosing scope. */ michael@0: bool bodyLevel = pc->atBodyLevel(); michael@0: michael@0: if (kind == Statement) { michael@0: /* michael@0: * Handle redeclaration and optimize cases where we can statically bind the michael@0: * function (thereby avoiding JSOP_DEFFUN and dynamic name lookup). michael@0: */ michael@0: if (Definition *dn = pc->decls().lookupFirst(funName)) { michael@0: JS_ASSERT(!dn->isUsed()); michael@0: JS_ASSERT(dn->isDefn()); michael@0: michael@0: if (options().extraWarningsOption || dn->kind() == Definition::CONST) { michael@0: JSAutoByteString name; michael@0: ParseReportKind reporter = (dn->kind() != Definition::CONST) michael@0: ? ParseExtraWarning michael@0: : ParseError; michael@0: if (!AtomToPrintableString(context, funName, &name) || michael@0: !report(reporter, false, nullptr, JSMSG_REDECLARED_VAR, michael@0: Definition::kindString(dn->kind()), name.ptr())) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Body-level function statements are effectively variable michael@0: * declarations where the initialization is hoisted to the michael@0: * beginning of the block. This means that any other variable michael@0: * declaration with the same name is really just an assignment to michael@0: * the function's binding (which is mutable), so turn any existing michael@0: * declaration into a use. michael@0: */ michael@0: if (bodyLevel && !makeDefIntoUse(dn, pn, funName)) michael@0: return false; michael@0: } else if (bodyLevel) { michael@0: /* michael@0: * If this function was used before it was defined, claim the michael@0: * pre-created definition node for this function that primaryExpr michael@0: * put in pc->lexdeps on first forward reference, and recycle pn. michael@0: */ michael@0: if (Definition *fn = pc->lexdeps.lookupDefn(funName)) { michael@0: JS_ASSERT(fn->isDefn()); michael@0: fn->setKind(PNK_FUNCTION); michael@0: fn->setArity(PN_CODE); michael@0: fn->pn_pos.begin = pn->pn_pos.begin; michael@0: fn->pn_pos.end = pn->pn_pos.end; michael@0: michael@0: fn->pn_body = nullptr; michael@0: fn->pn_cookie.makeFree(); michael@0: michael@0: pc->lexdeps->remove(funName); michael@0: handler.freeTree(pn); michael@0: pn = fn; michael@0: } michael@0: michael@0: if (!pc->define(tokenStream, funName, pn, Definition::VAR)) michael@0: return false; michael@0: } michael@0: michael@0: if (bodyLevel) { michael@0: JS_ASSERT(pn->functionIsHoisted()); michael@0: JS_ASSERT_IF(pc->sc->isFunctionBox(), !pn->pn_cookie.isFree()); michael@0: JS_ASSERT_IF(!pc->sc->isFunctionBox(), pn->pn_cookie.isFree()); michael@0: } else { michael@0: /* michael@0: * As a SpiderMonkey-specific extension, non-body-level function michael@0: * statements (e.g., functions in an "if" or "while" block) are michael@0: * dynamically bound when control flow reaches the statement. michael@0: */ michael@0: JS_ASSERT(!pc->sc->strict); michael@0: JS_ASSERT(pn->pn_cookie.isFree()); michael@0: if (pc->sc->isFunctionBox()) { michael@0: FunctionBox *funbox = pc->sc->asFunctionBox(); michael@0: funbox->setMightAliasLocals(); michael@0: funbox->setHasExtensibleScope(); michael@0: } michael@0: pn->setOp(JSOP_DEFFUN); michael@0: michael@0: /* michael@0: * Instead of setting bindingsAccessedDynamically, which would be michael@0: * overly conservative, remember the names of all function michael@0: * statements and mark any bindings with the same as aliased at the michael@0: * end of functionBody. michael@0: */ michael@0: if (!pc->funcStmts) { michael@0: pc->funcStmts = context->new_(context); michael@0: if (!pc->funcStmts || !pc->funcStmts->init()) michael@0: return false; michael@0: } michael@0: if (!pc->funcStmts->put(funName)) michael@0: return false; michael@0: michael@0: /* michael@0: * Due to the implicit declaration mechanism, 'arguments' will not michael@0: * have decls and, even if it did, they will not be noted as closed michael@0: * in the emitter. Thus, in the corner case of function statements michael@0: * overridding arguments, flag the whole scope as dynamic. michael@0: */ michael@0: if (funName == context->names().arguments) michael@0: pc->sc->setBindingsAccessedDynamically(); michael@0: } michael@0: michael@0: /* No further binding (in BindNameToSlot) is needed for functions. */ michael@0: pn->pn_dflags |= PND_BOUND; michael@0: } else { michael@0: /* A function expression does not introduce any binding. */ michael@0: pn->setOp(kind == Arrow ? JSOP_LAMBDA_ARROW : JSOP_LAMBDA); michael@0: } michael@0: michael@0: // When a lazily-parsed function is called, we only fully parse (and emit) michael@0: // that function, not any of its nested children. The initial syntax-only michael@0: // parse recorded the free variables of nested functions and their extents, michael@0: // so we can skip over them after accounting for their free variables. michael@0: if (LazyScript *lazyOuter = handler.lazyOuterFunction()) { michael@0: JSFunction *fun = handler.nextLazyInnerFunction(); michael@0: JS_ASSERT(!fun->isLegacyGenerator()); michael@0: FunctionBox *funbox = newFunctionBox(pn, fun, pc, Directives(/* strict = */ false), michael@0: fun->generatorKind()); michael@0: if (!funbox) michael@0: return false; michael@0: michael@0: if (!addFreeVariablesFromLazyFunction(fun, pc)) michael@0: return false; michael@0: michael@0: // The position passed to tokenStream.advance() is relative to michael@0: // userbuf.base() while LazyScript::{begin,end} offsets are relative to michael@0: // the outermost script source. N.B: userbuf.base() is initialized michael@0: // (in TokenStream()) to begin() - column() so that column numbers in michael@0: // the lazily parsed script are correct. michael@0: uint32_t userbufBase = lazyOuter->begin() - lazyOuter->column(); michael@0: tokenStream.advance(fun->lazyScript()->end() - userbufBase); michael@0: michael@0: *pbodyProcessed = true; michael@0: return true; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: static inline void michael@0: PropagateTransitiveParseFlags(const T *inner, U *outer) michael@0: { michael@0: if (inner->bindingsAccessedDynamically()) michael@0: outer->setBindingsAccessedDynamically(); michael@0: if (inner->hasDebuggerStatement()) michael@0: outer->setHasDebuggerStatement(); michael@0: } michael@0: michael@0: template michael@0: bool michael@0: Parser::addFreeVariablesFromLazyFunction(JSFunction *fun, michael@0: ParseContext *pc) michael@0: { michael@0: // Update any definition nodes in this context according to free variables michael@0: // in a lazily parsed inner function. michael@0: michael@0: LazyScript *lazy = fun->lazyScript(); michael@0: HeapPtrAtom *freeVariables = lazy->freeVariables(); michael@0: for (size_t i = 0; i < lazy->numFreeVariables(); i++) { michael@0: JSAtom *atom = freeVariables[i]; michael@0: michael@0: // 'arguments' will be implicitly bound within the inner function. michael@0: if (atom == context->names().arguments) michael@0: continue; michael@0: michael@0: DefinitionNode dn = pc->decls().lookupFirst(atom); michael@0: michael@0: if (!dn) { michael@0: dn = getOrCreateLexicalDependency(pc, atom); michael@0: if (!dn) michael@0: return false; michael@0: } michael@0: michael@0: /* Mark the outer dn as escaping. */ michael@0: handler.setFlag(handler.getDefinitionNode(dn), PND_CLOSED); michael@0: } michael@0: michael@0: PropagateTransitiveParseFlags(lazy, pc->sc); michael@0: return true; michael@0: } michael@0: michael@0: template <> michael@0: bool michael@0: Parser::checkFunctionDefinition(HandlePropertyName funName, michael@0: Node *pn, FunctionSyntaxKind kind, michael@0: bool *pbodyProcessed) michael@0: { michael@0: *pbodyProcessed = false; michael@0: michael@0: /* Function statements add a binding to the enclosing scope. */ michael@0: bool bodyLevel = pc->atBodyLevel(); michael@0: michael@0: if (kind == Statement) { michael@0: /* michael@0: * Handle redeclaration and optimize cases where we can statically bind the michael@0: * function (thereby avoiding JSOP_DEFFUN and dynamic name lookup). michael@0: */ michael@0: if (DefinitionNode dn = pc->decls().lookupFirst(funName)) { michael@0: if (dn == Definition::CONST) { michael@0: JSAutoByteString name; michael@0: if (!AtomToPrintableString(context, funName, &name) || michael@0: !report(ParseError, false, null(), JSMSG_REDECLARED_VAR, michael@0: Definition::kindString(dn), name.ptr())) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: } else if (bodyLevel) { michael@0: if (pc->lexdeps.lookupDefn(funName)) michael@0: pc->lexdeps->remove(funName); michael@0: michael@0: if (!pc->define(tokenStream, funName, *pn, Definition::VAR)) michael@0: return false; michael@0: } michael@0: michael@0: if (!bodyLevel && funName == context->names().arguments) michael@0: pc->sc->setBindingsAccessedDynamically(); michael@0: } michael@0: michael@0: if (kind == Arrow) { michael@0: /* Arrow functions cannot yet be parsed lazily. */ michael@0: return abortIfSyntaxParser(); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::functionDef(HandlePropertyName funName, const TokenStream::Position &start, michael@0: FunctionType type, FunctionSyntaxKind kind, michael@0: GeneratorKind generatorKind) michael@0: { michael@0: JS_ASSERT_IF(kind == Statement, funName); michael@0: michael@0: /* Make a TOK_FUNCTION node. */ michael@0: Node pn = handler.newFunctionDefinition(); michael@0: if (!pn) michael@0: return null(); michael@0: michael@0: bool bodyProcessed; michael@0: if (!checkFunctionDefinition(funName, &pn, kind, &bodyProcessed)) michael@0: return null(); michael@0: michael@0: if (bodyProcessed) michael@0: return pn; michael@0: michael@0: RootedObject proto(context); michael@0: if (generatorKind == StarGenerator) { michael@0: // If we are off the main thread, the generator meta-objects have michael@0: // already been created by js::StartOffThreadParseScript, so cx will not michael@0: // be necessary. michael@0: JSContext *cx = context->maybeJSContext(); michael@0: proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, context->global()); michael@0: if (!proto) michael@0: return null(); michael@0: } michael@0: RootedFunction fun(context, newFunction(pc, funName, kind, proto)); michael@0: if (!fun) michael@0: return null(); michael@0: michael@0: // Speculatively parse using the directives of the parent parsing context. michael@0: // If a directive is encountered (e.g., "use strict") that changes how the michael@0: // function should have been parsed, we backup and reparse with the new set michael@0: // of directives. michael@0: Directives directives(pc); michael@0: Directives newDirectives = directives; michael@0: michael@0: while (true) { michael@0: if (functionArgsAndBody(pn, fun, type, kind, generatorKind, directives, &newDirectives)) michael@0: break; michael@0: if (tokenStream.hadError() || directives == newDirectives) michael@0: return null(); michael@0: michael@0: // Assignment must be monotonic to prevent reparsing iloops michael@0: JS_ASSERT_IF(directives.strict(), newDirectives.strict()); michael@0: JS_ASSERT_IF(directives.asmJS(), newDirectives.asmJS()); michael@0: directives = newDirectives; michael@0: michael@0: tokenStream.seek(start); michael@0: if (funName && tokenStream.getToken() == TOK_ERROR) michael@0: return null(); michael@0: michael@0: // functionArgsAndBody may have already set pn->pn_body before failing. michael@0: handler.setFunctionBody(pn, null()); michael@0: } michael@0: michael@0: return pn; michael@0: } michael@0: michael@0: template <> michael@0: bool michael@0: Parser::finishFunctionDefinition(ParseNode *pn, FunctionBox *funbox, michael@0: ParseNode *prelude, ParseNode *body) michael@0: { michael@0: pn->pn_pos.end = pos().end; michael@0: michael@0: /* michael@0: * If there were destructuring formal parameters, prepend the initializing michael@0: * comma expression that we synthesized to body. If the body is a return michael@0: * node, we must make a special PNK_SEQ node, to prepend the destructuring michael@0: * code without bracing the decompilation of the function body. michael@0: */ michael@0: if (prelude) { michael@0: if (!body->isArity(PN_LIST)) { michael@0: ParseNode *block; michael@0: michael@0: block = ListNode::create(PNK_SEQ, &handler); michael@0: if (!block) michael@0: return false; michael@0: block->pn_pos = body->pn_pos; michael@0: block->initList(body); michael@0: michael@0: body = block; michael@0: } michael@0: michael@0: ParseNode *item = UnaryNode::create(PNK_SEMI, &handler); michael@0: if (!item) michael@0: return false; michael@0: michael@0: item->pn_pos.begin = item->pn_pos.end = body->pn_pos.begin; michael@0: item->pn_kid = prelude; michael@0: item->pn_next = body->pn_head; michael@0: body->pn_head = item; michael@0: if (body->pn_tail == &body->pn_head) michael@0: body->pn_tail = &item->pn_next; michael@0: ++body->pn_count; michael@0: body->pn_xflags |= PNX_DESTRUCT; michael@0: } michael@0: michael@0: JS_ASSERT(pn->pn_funbox == funbox); michael@0: JS_ASSERT(pn->pn_body->isKind(PNK_ARGSBODY)); michael@0: pn->pn_body->append(body); michael@0: pn->pn_body->pn_pos = body->pn_pos; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template <> michael@0: bool michael@0: Parser::finishFunctionDefinition(Node pn, FunctionBox *funbox, michael@0: Node prelude, Node body) michael@0: { michael@0: // The LazyScript for a lazily parsed function needs to be constructed michael@0: // while its ParseContext and associated lexdeps and inner functions are michael@0: // still available. michael@0: michael@0: if (funbox->inWith) michael@0: return abortIfSyntaxParser(); michael@0: michael@0: size_t numFreeVariables = pc->lexdeps->count(); michael@0: size_t numInnerFunctions = pc->innerFunctions.length(); michael@0: michael@0: RootedFunction fun(context, funbox->function()); michael@0: LazyScript *lazy = LazyScript::CreateRaw(context, fun, numFreeVariables, numInnerFunctions, michael@0: versionNumber(), funbox->bufStart, funbox->bufEnd, michael@0: funbox->startLine, funbox->startColumn); michael@0: if (!lazy) michael@0: return false; michael@0: michael@0: HeapPtrAtom *freeVariables = lazy->freeVariables(); michael@0: size_t i = 0; michael@0: for (AtomDefnRange r = pc->lexdeps->all(); !r.empty(); r.popFront()) michael@0: freeVariables[i++].init(r.front().key()); michael@0: JS_ASSERT(i == numFreeVariables); michael@0: michael@0: HeapPtrFunction *innerFunctions = lazy->innerFunctions(); michael@0: for (size_t i = 0; i < numInnerFunctions; i++) michael@0: innerFunctions[i].init(pc->innerFunctions[i]); michael@0: michael@0: if (pc->sc->strict) michael@0: lazy->setStrict(); michael@0: lazy->setGeneratorKind(funbox->generatorKind()); michael@0: if (funbox->usesArguments && funbox->usesApply) michael@0: lazy->setUsesArgumentsAndApply(); michael@0: PropagateTransitiveParseFlags(funbox, lazy); michael@0: michael@0: fun->initLazyScript(lazy); michael@0: return true; michael@0: } michael@0: michael@0: template <> michael@0: bool michael@0: Parser::functionArgsAndBody(ParseNode *pn, HandleFunction fun, michael@0: FunctionType type, FunctionSyntaxKind kind, michael@0: GeneratorKind generatorKind, michael@0: Directives inheritedDirectives, michael@0: Directives *newDirectives) michael@0: { michael@0: ParseContext *outerpc = pc; michael@0: michael@0: // Create box for fun->object early to protect against last-ditch GC. michael@0: FunctionBox *funbox = newFunctionBox(pn, fun, pc, inheritedDirectives, generatorKind); michael@0: if (!funbox) michael@0: return false; michael@0: michael@0: // Try a syntax parse for this inner function. michael@0: do { michael@0: Parser *parser = handler.syntaxParser; michael@0: if (!parser) michael@0: break; michael@0: michael@0: { michael@0: // Move the syntax parser to the current position in the stream. michael@0: TokenStream::Position position(keepAtoms); michael@0: tokenStream.tell(&position); michael@0: if (!parser->tokenStream.seek(position, tokenStream)) michael@0: return false; michael@0: michael@0: ParseContext funpc(parser, outerpc, SyntaxParseHandler::null(), funbox, michael@0: newDirectives, outerpc->staticLevel + 1, michael@0: outerpc->blockidGen, michael@0: /* blockScopeDepth = */ 0); michael@0: if (!funpc.init(tokenStream)) michael@0: return false; michael@0: michael@0: if (!parser->functionArgsAndBodyGeneric(SyntaxParseHandler::NodeGeneric, michael@0: fun, type, kind, newDirectives)) michael@0: { michael@0: if (parser->hadAbortedSyntaxParse()) { michael@0: // Try again with a full parse. michael@0: parser->clearAbortedSyntaxParse(); michael@0: break; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: outerpc->blockidGen = funpc.blockidGen; michael@0: michael@0: // Advance this parser over tokens processed by the syntax parser. michael@0: parser->tokenStream.tell(&position); michael@0: if (!tokenStream.seek(position, parser->tokenStream)) michael@0: return false; michael@0: michael@0: // Update the end position of the parse node. michael@0: pn->pn_pos.end = tokenStream.currentToken().pos.end; michael@0: } michael@0: michael@0: if (!addFreeVariablesFromLazyFunction(fun, pc)) michael@0: return false; michael@0: michael@0: pn->pn_blockid = outerpc->blockid(); michael@0: PropagateTransitiveParseFlags(funbox, outerpc->sc); michael@0: return true; michael@0: } while (false); michael@0: michael@0: // Continue doing a full parse for this inner function. michael@0: ParseContext funpc(this, pc, pn, funbox, newDirectives, michael@0: outerpc->staticLevel + 1, outerpc->blockidGen, michael@0: /* blockScopeDepth = */ 0); michael@0: if (!funpc.init(tokenStream)) michael@0: return false; michael@0: michael@0: if (!functionArgsAndBodyGeneric(pn, fun, type, kind, newDirectives)) michael@0: return false; michael@0: michael@0: if (!leaveFunction(pn, outerpc, kind)) michael@0: return false; michael@0: michael@0: pn->pn_blockid = outerpc->blockid(); michael@0: michael@0: /* michael@0: * Fruit of the poisonous tree: if a closure contains a dynamic name access michael@0: * (eval, with, etc), we consider the parent to do the same. The reason is michael@0: * that the deoptimizing effects of dynamic name access apply equally to michael@0: * parents: any local can be read at runtime. michael@0: */ michael@0: PropagateTransitiveParseFlags(funbox, outerpc->sc); michael@0: return true; michael@0: } michael@0: michael@0: template <> michael@0: bool michael@0: Parser::functionArgsAndBody(Node pn, HandleFunction fun, michael@0: FunctionType type, FunctionSyntaxKind kind, michael@0: GeneratorKind generatorKind, michael@0: Directives inheritedDirectives, michael@0: Directives *newDirectives) michael@0: { michael@0: ParseContext *outerpc = pc; michael@0: michael@0: // Create box for fun->object early to protect against last-ditch GC. michael@0: FunctionBox *funbox = newFunctionBox(pn, fun, pc, inheritedDirectives, generatorKind); michael@0: if (!funbox) michael@0: return false; michael@0: michael@0: // Initialize early for possible flags mutation via destructuringExpr. michael@0: ParseContext funpc(this, pc, handler.null(), funbox, newDirectives, michael@0: outerpc->staticLevel + 1, outerpc->blockidGen, michael@0: /* blockScopeDepth = */ 0); michael@0: if (!funpc.init(tokenStream)) michael@0: return false; michael@0: michael@0: if (!functionArgsAndBodyGeneric(pn, fun, type, kind, newDirectives)) michael@0: return false; michael@0: michael@0: if (!leaveFunction(pn, outerpc, kind)) michael@0: return false; michael@0: michael@0: // This is a lazy function inner to another lazy function. Remember the michael@0: // inner function so that if the outer function is eventually parsed we do michael@0: // not need any further parsing or processing of the inner function. michael@0: JS_ASSERT(fun->lazyScript()); michael@0: return outerpc->innerFunctions.append(fun); michael@0: } michael@0: michael@0: template <> michael@0: ParseNode * michael@0: Parser::standaloneLazyFunction(HandleFunction fun, unsigned staticLevel, michael@0: bool strict, GeneratorKind generatorKind) michael@0: { michael@0: Node pn = handler.newFunctionDefinition(); michael@0: if (!pn) michael@0: return null(); michael@0: michael@0: Directives directives(/* strict = */ strict); michael@0: FunctionBox *funbox = newFunctionBox(pn, fun, /* outerpc = */ nullptr, directives, michael@0: generatorKind); michael@0: if (!funbox) michael@0: return null(); michael@0: funbox->length = fun->nargs() - fun->hasRest(); michael@0: michael@0: Directives newDirectives = directives; michael@0: ParseContext funpc(this, /* parent = */ nullptr, pn, funbox, michael@0: &newDirectives, staticLevel, /* bodyid = */ 0, michael@0: /* blockScopeDepth = */ 0); michael@0: if (!funpc.init(tokenStream)) michael@0: return null(); michael@0: michael@0: if (!functionArgsAndBodyGeneric(pn, fun, Normal, Statement, &newDirectives)) { michael@0: JS_ASSERT(directives == newDirectives); michael@0: return null(); michael@0: } michael@0: michael@0: if (fun->isNamedLambda()) { michael@0: if (AtomDefnPtr p = pc->lexdeps->lookup(fun->name())) { michael@0: Definition *dn = p.value().get(); michael@0: if (!ConvertDefinitionToNamedLambdaUse(tokenStream, pc, funbox, dn)) michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: InternalHandle bindings = michael@0: InternalHandle::fromMarkedLocation(&funbox->bindings); michael@0: if (!pc->generateFunctionBindings(context, tokenStream, alloc, bindings)) michael@0: return null(); michael@0: michael@0: if (!FoldConstants(context, &pn, this)) michael@0: return null(); michael@0: michael@0: return pn; michael@0: } michael@0: michael@0: template michael@0: bool michael@0: Parser::functionArgsAndBodyGeneric(Node pn, HandleFunction fun, FunctionType type, michael@0: FunctionSyntaxKind kind, michael@0: Directives *newDirectives) michael@0: { michael@0: // Given a properly initialized parse context, try to parse an actual michael@0: // function without concern for conversion to strict mode, use of lazy michael@0: // parsing and such. michael@0: michael@0: Node prelude = null(); michael@0: bool hasRest; michael@0: if (!functionArguments(kind, &prelude, pn, &hasRest)) michael@0: return false; michael@0: michael@0: FunctionBox *funbox = pc->sc->asFunctionBox(); michael@0: michael@0: fun->setArgCount(pc->numArgs()); michael@0: if (hasRest) michael@0: fun->setHasRest(); michael@0: michael@0: if (type == Getter && fun->nargs() > 0) { michael@0: report(ParseError, false, null(), JSMSG_ACCESSOR_WRONG_ARGS, "getter", "no", "s"); michael@0: return false; michael@0: } michael@0: if (type == Setter && fun->nargs() != 1) { michael@0: report(ParseError, false, null(), JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", ""); michael@0: return false; michael@0: } michael@0: michael@0: if (kind == Arrow && !tokenStream.matchToken(TOK_ARROW)) { michael@0: report(ParseError, false, null(), JSMSG_BAD_ARROW_ARGS); michael@0: return false; michael@0: } michael@0: michael@0: // Parse the function body. michael@0: FunctionBodyType bodyType = StatementListBody; michael@0: if (tokenStream.getToken(TokenStream::Operand) != TOK_LC) { michael@0: if (funbox->isStarGenerator()) { michael@0: report(ParseError, false, null(), JSMSG_CURLY_BEFORE_BODY); michael@0: return false; michael@0: } michael@0: tokenStream.ungetToken(); michael@0: bodyType = ExpressionBody; michael@0: fun->setIsExprClosure(); michael@0: } michael@0: michael@0: Node body = functionBody(kind, bodyType); michael@0: if (!body) michael@0: return false; michael@0: michael@0: if (fun->name() && !checkStrictBinding(fun->name(), pn)) michael@0: return false; michael@0: michael@0: #if JS_HAS_EXPR_CLOSURES michael@0: if (bodyType == StatementListBody) { michael@0: #endif michael@0: if (!tokenStream.matchToken(TOK_RC)) { michael@0: report(ParseError, false, null(), JSMSG_CURLY_AFTER_BODY); michael@0: return false; michael@0: } michael@0: funbox->bufEnd = pos().begin + 1; michael@0: #if JS_HAS_EXPR_CLOSURES michael@0: } else { michael@0: if (tokenStream.hadError()) michael@0: return false; michael@0: funbox->bufEnd = pos().end; michael@0: if (kind == Statement && !MatchOrInsertSemicolon(tokenStream)) michael@0: return false; michael@0: } michael@0: #endif michael@0: michael@0: return finishFunctionDefinition(pn, funbox, prelude, body); michael@0: } michael@0: michael@0: template michael@0: bool michael@0: Parser::checkYieldNameValidity() michael@0: { michael@0: // In star generators and in JS >= 1.7, yield is a keyword. Otherwise in michael@0: // strict mode, yield is a future reserved word. michael@0: if (pc->isStarGenerator() || versionNumber() >= JSVERSION_1_7 || pc->sc->strict) { michael@0: report(ParseError, false, null(), JSMSG_RESERVED_ID, "yield"); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::functionStmt() michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION)); michael@0: michael@0: TokenStream::Position start(keepAtoms); michael@0: tokenStream.tell(&start); michael@0: michael@0: RootedPropertyName name(context); michael@0: GeneratorKind generatorKind = NotGenerator; michael@0: TokenKind tt = tokenStream.getToken(); michael@0: michael@0: if (tt == TOK_MUL) { michael@0: tokenStream.tell(&start); michael@0: tt = tokenStream.getToken(); michael@0: generatorKind = StarGenerator; michael@0: } michael@0: michael@0: if (tt == TOK_NAME) { michael@0: name = tokenStream.currentName(); michael@0: } else if (tt == TOK_YIELD) { michael@0: if (!checkYieldNameValidity()) michael@0: return null(); michael@0: name = tokenStream.currentName(); michael@0: } else { michael@0: /* Unnamed function expressions are forbidden in statement context. */ michael@0: report(ParseError, false, null(), JSMSG_UNNAMED_FUNCTION_STMT); michael@0: return null(); michael@0: } michael@0: michael@0: /* We forbid function statements in strict mode code. */ michael@0: if (!pc->atBodyLevel() && pc->sc->needStrictChecks() && michael@0: !report(ParseStrictError, pc->sc->strict, null(), JSMSG_STRICT_FUNCTION_STATEMENT)) michael@0: return null(); michael@0: michael@0: return functionDef(name, start, Normal, Statement, generatorKind); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::functionExpr() michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION)); michael@0: michael@0: TokenStream::Position start(keepAtoms); michael@0: tokenStream.tell(&start); michael@0: michael@0: GeneratorKind generatorKind = NotGenerator; michael@0: TokenKind tt = tokenStream.getToken(); michael@0: michael@0: if (tt == TOK_MUL) { michael@0: tokenStream.tell(&start); michael@0: tt = tokenStream.getToken(); michael@0: generatorKind = StarGenerator; michael@0: } michael@0: michael@0: RootedPropertyName name(context); michael@0: if (tt == TOK_NAME) { michael@0: name = tokenStream.currentName(); michael@0: } else if (tt == TOK_YIELD) { michael@0: if (!checkYieldNameValidity()) michael@0: return null(); michael@0: name = tokenStream.currentName(); michael@0: } else { michael@0: tokenStream.ungetToken(); michael@0: } michael@0: michael@0: return functionDef(name, start, Normal, Expression, generatorKind); michael@0: } michael@0: michael@0: /* michael@0: * Return true if this node, known to be an unparenthesized string literal, michael@0: * could be the string of a directive in a Directive Prologue. Directive michael@0: * strings never contain escape sequences or line continuations. michael@0: * isEscapeFreeStringLiteral, below, checks whether the node itself could be michael@0: * a directive. michael@0: */ michael@0: static inline bool michael@0: IsEscapeFreeStringLiteral(const TokenPos &pos, JSAtom *str) michael@0: { michael@0: /* michael@0: * If the string's length in the source code is its length as a value, michael@0: * accounting for the quotes, then it must not contain any escape michael@0: * sequences or line continuations. michael@0: */ michael@0: return pos.begin + str->length() + 2 == pos.end; michael@0: } michael@0: michael@0: template <> michael@0: bool michael@0: Parser::asmJS(Node list) michael@0: { michael@0: // While asm.js could technically be validated and compiled during syntax michael@0: // parsing, we have no guarantee that some later JS wouldn't abort the michael@0: // syntax parse and cause us to re-parse (and re-compile) the asm.js module. michael@0: // For simplicity, unconditionally abort the syntax parse when "use asm" is michael@0: // encountered so that asm.js is always validated/compiled exactly once michael@0: // during a full parse. michael@0: JS_ALWAYS_FALSE(abortIfSyntaxParser()); michael@0: return false; michael@0: } michael@0: michael@0: template <> michael@0: bool michael@0: Parser::asmJS(Node list) michael@0: { michael@0: // If we are already inside "use asm" that means we are either actively michael@0: // compiling or we are reparsing after asm.js validation failure. In either michael@0: // case, nothing to do here. michael@0: if (pc->useAsmOrInsideUseAsm()) michael@0: return true; michael@0: michael@0: // If there is no ScriptSource, then we are doing a non-compiling parse and michael@0: // so we shouldn't (and can't, without a ScriptSource) compile. michael@0: if (ss == nullptr) michael@0: return true; michael@0: michael@0: pc->sc->asFunctionBox()->useAsm = true; michael@0: michael@0: #ifdef JS_ION michael@0: // Attempt to validate and compile this asm.js module. On success, the michael@0: // tokenStream has been advanced to the closing }. On failure, the michael@0: // tokenStream is in an indeterminate state and we must reparse the michael@0: // function from the beginning. Reparsing is triggered by marking that a michael@0: // new directive has been encountered and returning 'false'. michael@0: bool validated; michael@0: if (!CompileAsmJS(context, *this, list, &validated)) michael@0: return false; michael@0: if (!validated) { michael@0: pc->newDirectives->setAsmJS(); michael@0: return false; michael@0: } michael@0: #endif michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Recognize Directive Prologue members and directives. Assuming |pn| is a michael@0: * candidate for membership in a directive prologue, recognize directives and michael@0: * set |pc|'s flags accordingly. If |pn| is indeed part of a prologue, set its michael@0: * |pn_prologue| flag. michael@0: * michael@0: * Note that the following is a strict mode function: michael@0: * michael@0: * function foo() { michael@0: * "blah" // inserted semi colon michael@0: * "blurgh" michael@0: * "use\x20loose" michael@0: * "use strict" michael@0: * } michael@0: * michael@0: * That is, even though "use\x20loose" can never be a directive, now or in the michael@0: * future (because of the hex escape), the Directive Prologue extends through it michael@0: * to the "use strict" statement, which is indeed a directive. michael@0: */ michael@0: template michael@0: bool michael@0: Parser::maybeParseDirective(Node list, Node pn, bool *cont) michael@0: { michael@0: TokenPos directivePos; michael@0: JSAtom *directive = handler.isStringExprStatement(pn, &directivePos); michael@0: michael@0: *cont = !!directive; michael@0: if (!*cont) michael@0: return true; michael@0: michael@0: if (IsEscapeFreeStringLiteral(directivePos, directive)) { michael@0: // Mark this statement as being a possibly legitimate part of a michael@0: // directive prologue, so the bytecode emitter won't warn about it being michael@0: // useless code. (We mustn't just omit the statement entirely yet, as it michael@0: // could be producing the value of an eval or JSScript execution.) michael@0: // michael@0: // Note that even if the string isn't one we recognize as a directive, michael@0: // the emitter still shouldn't flag it as useless, as it could become a michael@0: // directive in the future. We don't want to interfere with people michael@0: // taking advantage of directive-prologue-enabled features that appear michael@0: // in other browsers first. michael@0: handler.setPrologue(pn); michael@0: michael@0: if (directive == context->names().useStrict) { michael@0: // We're going to be in strict mode. Note that this scope explicitly michael@0: // had "use strict"; michael@0: pc->sc->setExplicitUseStrict(); michael@0: if (!pc->sc->strict) { michael@0: if (pc->sc->isFunctionBox()) { michael@0: // Request that this function be reparsed as strict. michael@0: pc->newDirectives->setStrict(); michael@0: return false; michael@0: } else { michael@0: // We don't reparse global scopes, so we keep track of the michael@0: // one possible strict violation that could occur in the michael@0: // directive prologue -- octal escapes -- and complain now. michael@0: if (tokenStream.sawOctalEscape()) { michael@0: report(ParseError, false, null(), JSMSG_DEPRECATED_OCTAL); michael@0: return false; michael@0: } michael@0: pc->sc->strict = true; michael@0: } michael@0: } michael@0: } else if (directive == context->names().useAsm) { michael@0: if (pc->sc->isFunctionBox()) michael@0: return asmJS(list); michael@0: return report(ParseWarning, false, pn, JSMSG_USE_ASM_DIRECTIVE_FAIL); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Parse the statements in a block, creating a StatementList node that lists michael@0: * the statements. If called from block-parsing code, the caller must match michael@0: * '{' before and '}' after. michael@0: */ michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::statements() michael@0: { michael@0: JS_CHECK_RECURSION(context, return null()); michael@0: michael@0: Node pn = handler.newStatementList(pc->blockid(), pos()); michael@0: if (!pn) michael@0: return null(); michael@0: michael@0: Node saveBlock = pc->blockNode; michael@0: pc->blockNode = pn; michael@0: michael@0: bool canHaveDirectives = pc->atBodyLevel(); michael@0: for (;;) { michael@0: TokenKind tt = tokenStream.peekToken(TokenStream::Operand); michael@0: if (tt <= TOK_EOF || tt == TOK_RC) { michael@0: if (tt == TOK_ERROR) { michael@0: if (tokenStream.isEOF()) michael@0: isUnexpectedEOF_ = true; michael@0: return null(); michael@0: } michael@0: break; michael@0: } michael@0: Node next = statement(canHaveDirectives); michael@0: if (!next) { michael@0: if (tokenStream.isEOF()) michael@0: isUnexpectedEOF_ = true; michael@0: return null(); michael@0: } michael@0: michael@0: if (canHaveDirectives) { michael@0: if (!maybeParseDirective(pn, next, &canHaveDirectives)) michael@0: return null(); michael@0: } michael@0: michael@0: handler.addStatementToList(pn, next, pc); michael@0: } michael@0: michael@0: /* michael@0: * Handle the case where there was a let declaration under this block. If michael@0: * it replaced pc->blockNode with a new block node then we must refresh pn michael@0: * and then restore pc->blockNode. michael@0: */ michael@0: if (pc->blockNode != pn) michael@0: pn = pc->blockNode; michael@0: pc->blockNode = saveBlock; michael@0: return pn; michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::condition() michael@0: { michael@0: MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_COND); michael@0: Node pn = exprInParens(); michael@0: if (!pn) michael@0: return null(); michael@0: MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_COND); michael@0: michael@0: /* Check for (a = b) and warn about possible (a == b) mistype. */ michael@0: if (handler.isOperationWithoutParens(pn, PNK_ASSIGN) && michael@0: !report(ParseExtraWarning, false, null(), JSMSG_EQUAL_AS_ASSIGN)) michael@0: { michael@0: return null(); michael@0: } michael@0: return pn; michael@0: } michael@0: michael@0: template michael@0: bool michael@0: Parser::matchLabel(MutableHandle label) michael@0: { michael@0: TokenKind tt = tokenStream.peekTokenSameLine(TokenStream::Operand); michael@0: if (tt == TOK_ERROR) michael@0: return false; michael@0: if (tt == TOK_NAME) { michael@0: tokenStream.consumeKnownToken(TOK_NAME); michael@0: label.set(tokenStream.currentName()); michael@0: } else if (tt == TOK_YIELD) { michael@0: tokenStream.consumeKnownToken(TOK_YIELD); michael@0: if (!checkYieldNameValidity()) michael@0: return false; michael@0: label.set(tokenStream.currentName()); michael@0: } else { michael@0: label.set(nullptr); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: bool michael@0: Parser::reportRedeclaration(Node pn, bool isConst, JSAtom *atom) michael@0: { michael@0: JSAutoByteString name; michael@0: if (AtomToPrintableString(context, atom, &name)) michael@0: report(ParseError, false, pn, JSMSG_REDECLARED_VAR, isConst ? "const" : "variable", name.ptr()); michael@0: return false; michael@0: } michael@0: michael@0: /* michael@0: * Define a let-variable in a block, let-expression, or comprehension scope. pc michael@0: * must already be in such a scope. michael@0: * michael@0: * Throw a SyntaxError if 'atom' is an invalid name. Otherwise create a michael@0: * property for the new variable on the block object, pc->staticScope; michael@0: * populate data->pn->pn_{op,cookie,defn,dflags}; and stash a pointer to michael@0: * data->pn in a slot of the block object. michael@0: */ michael@0: template <> michael@0: /* static */ bool michael@0: Parser::bindLet(BindData *data, michael@0: HandlePropertyName name, Parser *parser) michael@0: { michael@0: ParseContext *pc = parser->pc; michael@0: ParseNode *pn = data->pn; michael@0: if (!parser->checkStrictBinding(name, pn)) michael@0: return false; michael@0: michael@0: ExclusiveContext *cx = parser->context; michael@0: michael@0: Rooted blockObj(cx, data->let.blockObj); michael@0: unsigned index = blockObj->numVariables(); michael@0: if (index >= StaticBlockObject::LOCAL_INDEX_LIMIT) { michael@0: parser->report(ParseError, false, pn, data->let.overflow); michael@0: return false; michael@0: } michael@0: michael@0: /* michael@0: * Assign block-local index to pn->pn_cookie right away, encoding it as an michael@0: * upvar cookie whose skip tells the current static level. The emitter will michael@0: * adjust the node's slot based on its stack depth model -- and, for global michael@0: * and eval code, js::frontend::CompileScript will adjust the slot michael@0: * again to include script->nfixed. michael@0: */ michael@0: if (!pn->pn_cookie.set(parser->tokenStream, pc->staticLevel, index)) michael@0: return false; michael@0: michael@0: /* michael@0: * For bindings that are hoisted to the beginning of the block/function, michael@0: * define() right now. Otherwise, delay define until PushLetScope. michael@0: */ michael@0: if (data->let.varContext == HoistVars) { michael@0: JS_ASSERT(!pc->atBodyLevel()); michael@0: Definition *dn = pc->decls().lookupFirst(name); michael@0: if (dn && dn->pn_blockid == pc->blockid()) michael@0: return parser->reportRedeclaration(pn, dn->isConst(), name); michael@0: if (!pc->define(parser->tokenStream, name, pn, Definition::LET)) michael@0: return false; michael@0: } michael@0: michael@0: bool redeclared; michael@0: RootedId id(cx, NameToId(name)); michael@0: RootedShape shape(cx, StaticBlockObject::addVar(cx, blockObj, id, index, &redeclared)); michael@0: if (!shape) { michael@0: if (redeclared) michael@0: parser->reportRedeclaration(pn, false, name); michael@0: return false; michael@0: } michael@0: michael@0: /* Store pn in the static block object. */ michael@0: blockObj->setDefinitionParseNode(index, reinterpret_cast(pn)); michael@0: return true; michael@0: } michael@0: michael@0: template <> michael@0: /* static */ bool michael@0: Parser::bindLet(BindData *data, michael@0: HandlePropertyName name, Parser *parser) michael@0: { michael@0: if (!parser->checkStrictBinding(name, data->pn)) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: static inline bool michael@0: ForEachLetDef(TokenStream &ts, ParseContext *pc, michael@0: HandleStaticBlockObject blockObj, Op op) michael@0: { michael@0: for (Shape::Range r(ts.context(), blockObj->lastProperty()); !r.empty(); r.popFront()) { michael@0: Shape &shape = r.front(); michael@0: michael@0: /* Beware the destructuring dummy slots. */ michael@0: if (JSID_IS_INT(shape.propid())) michael@0: continue; michael@0: michael@0: if (!op(ts, pc, blockObj, shape, JSID_TO_ATOM(shape.propid()))) michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: struct PopLetDecl { michael@0: bool operator()(TokenStream &, ParseContext *pc, HandleStaticBlockObject, michael@0: const Shape &, JSAtom *atom) michael@0: { michael@0: pc->popLetDecl(atom); michael@0: return true; michael@0: } michael@0: }; michael@0: michael@0: // We compute the maximum block scope depth, in slots, of a compilation unit at michael@0: // parse-time. Each nested statement has a field indicating the maximum block michael@0: // scope depth that is nested inside it. When we leave a nested statement, we michael@0: // add the number of slots in the statement to the nested depth, and use that to michael@0: // update the maximum block scope depth of the outer statement or parse michael@0: // context. In the end, pc->blockScopeDepth will indicate the number of slots michael@0: // to reserve in the fixed part of a stack frame. michael@0: // michael@0: template michael@0: static void michael@0: AccumulateBlockScopeDepth(ParseContext *pc) michael@0: { michael@0: uint32_t innerDepth = pc->topStmt->innerBlockScopeDepth; michael@0: StmtInfoPC *outer = pc->topStmt->down; michael@0: michael@0: if (pc->topStmt->isBlockScope) michael@0: innerDepth += pc->topStmt->staticScope->template as().numVariables(); michael@0: michael@0: if (outer) { michael@0: if (outer->innerBlockScopeDepth < innerDepth) michael@0: outer->innerBlockScopeDepth = innerDepth; michael@0: } else { michael@0: if (pc->blockScopeDepth < innerDepth) michael@0: pc->blockScopeDepth = innerDepth; michael@0: } michael@0: } michael@0: michael@0: template michael@0: static void michael@0: PopStatementPC(TokenStream &ts, ParseContext *pc) michael@0: { michael@0: RootedNestedScopeObject scopeObj(ts.context(), pc->topStmt->staticScope); michael@0: JS_ASSERT(!!scopeObj == pc->topStmt->isNestedScope); michael@0: michael@0: AccumulateBlockScopeDepth(pc); michael@0: FinishPopStatement(pc); michael@0: michael@0: if (scopeObj) { michael@0: if (scopeObj->is()) { michael@0: RootedStaticBlockObject blockObj(ts.context(), &scopeObj->as()); michael@0: JS_ASSERT(!blockObj->inDictionaryMode()); michael@0: ForEachLetDef(ts, pc, blockObj, PopLetDecl()); michael@0: } michael@0: scopeObj->resetEnclosingNestedScopeFromParser(); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * The function LexicalLookup searches a static binding for the given name in michael@0: * the stack of statements enclosing the statement currently being parsed. Each michael@0: * statement that introduces a new scope has a corresponding scope object, on michael@0: * which the bindings for that scope are stored. LexicalLookup either returns michael@0: * the innermost statement which has a scope object containing a binding with michael@0: * the given name, or nullptr. michael@0: */ michael@0: template michael@0: typename ContextT::StmtInfo * michael@0: LexicalLookup(ContextT *ct, HandleAtom atom, int *slotp, typename ContextT::StmtInfo *stmt) michael@0: { michael@0: RootedId id(ct->sc->context, AtomToId(atom)); michael@0: michael@0: if (!stmt) michael@0: stmt = ct->topScopeStmt; michael@0: for (; stmt; stmt = stmt->downScope) { michael@0: /* michael@0: * With-statements introduce dynamic bindings. Since dynamic bindings michael@0: * can potentially override any static bindings introduced by statements michael@0: * further up the stack, we have to abort the search. michael@0: */ michael@0: if (stmt->type == STMT_WITH) michael@0: break; michael@0: michael@0: // Skip statements that do not introduce a new scope michael@0: if (!stmt->isBlockScope) michael@0: continue; michael@0: michael@0: StaticBlockObject &blockObj = stmt->staticBlock(); michael@0: Shape *shape = blockObj.nativeLookup(ct->sc->context, id); michael@0: if (shape) { michael@0: if (slotp) michael@0: *slotp = blockObj.shapeToIndex(*shape); michael@0: return stmt; michael@0: } michael@0: } michael@0: michael@0: if (slotp) michael@0: *slotp = -1; michael@0: return stmt; michael@0: } michael@0: michael@0: template michael@0: static inline bool michael@0: OuterLet(ParseContext *pc, StmtInfoPC *stmt, HandleAtom atom) michael@0: { michael@0: while (stmt->downScope) { michael@0: stmt = LexicalLookup(pc, atom, nullptr, stmt->downScope); michael@0: if (!stmt) michael@0: return false; michael@0: if (stmt->type == STMT_BLOCK) michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: template michael@0: /* static */ bool michael@0: Parser::bindVarOrConst(BindData *data, michael@0: HandlePropertyName name, Parser *parser) michael@0: { michael@0: ExclusiveContext *cx = parser->context; michael@0: ParseContext *pc = parser->pc; michael@0: Node pn = data->pn; michael@0: bool isConstDecl = data->op == JSOP_DEFCONST; michael@0: michael@0: /* Default best op for pn is JSOP_NAME; we'll try to improve below. */ michael@0: parser->handler.setOp(pn, JSOP_NAME); michael@0: michael@0: if (!parser->checkStrictBinding(name, pn)) michael@0: return false; michael@0: michael@0: StmtInfoPC *stmt = LexicalLookup(pc, name, nullptr, (StmtInfoPC *)nullptr); michael@0: michael@0: if (stmt && stmt->type == STMT_WITH) { michael@0: parser->handler.setFlag(pn, PND_DEOPTIMIZED); michael@0: if (pc->sc->isFunctionBox()) { michael@0: FunctionBox *funbox = pc->sc->asFunctionBox(); michael@0: funbox->setMightAliasLocals(); michael@0: } michael@0: michael@0: /* michael@0: * This definition isn't being added to the parse context's michael@0: * declarations, so make sure to indicate the need to deoptimize michael@0: * the script's arguments object. Mark the function as if it michael@0: * contained a debugger statement, which will deoptimize arguments michael@0: * as much as possible. michael@0: */ michael@0: if (name == cx->names().arguments) michael@0: pc->sc->setHasDebuggerStatement(); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: DefinitionList::Range defs = pc->decls().lookupMulti(name); michael@0: JS_ASSERT_IF(stmt, !defs.empty()); michael@0: michael@0: if (defs.empty()) { michael@0: return pc->define(parser->tokenStream, name, pn, michael@0: isConstDecl ? Definition::CONST : Definition::VAR); michael@0: } michael@0: michael@0: /* michael@0: * There was a previous declaration with the same name. The standard michael@0: * disallows several forms of redeclaration. Critically, michael@0: * let (x) { var x; } // error michael@0: * is not allowed which allows us to turn any non-error redeclaration michael@0: * into a use of the initial declaration. michael@0: */ michael@0: DefinitionNode dn = defs.front(); michael@0: Definition::Kind dn_kind = parser->handler.getDefinitionKind(dn); michael@0: if (dn_kind == Definition::ARG) { michael@0: JSAutoByteString bytes; michael@0: if (!AtomToPrintableString(cx, name, &bytes)) michael@0: return false; michael@0: michael@0: if (isConstDecl) { michael@0: parser->report(ParseError, false, pn, JSMSG_REDECLARED_PARAM, bytes.ptr()); michael@0: return false; michael@0: } michael@0: if (!parser->report(ParseExtraWarning, false, pn, JSMSG_VAR_HIDES_ARG, bytes.ptr())) michael@0: return false; michael@0: } else { michael@0: bool error = (isConstDecl || michael@0: dn_kind == Definition::CONST || michael@0: (dn_kind == Definition::LET && michael@0: (stmt->type != STMT_CATCH || OuterLet(pc, stmt, name)))); michael@0: michael@0: if (parser->options().extraWarningsOption michael@0: ? data->op != JSOP_DEFVAR || dn_kind != Definition::VAR michael@0: : error) michael@0: { michael@0: JSAutoByteString bytes; michael@0: ParseReportKind reporter = error ? ParseError : ParseExtraWarning; michael@0: if (!AtomToPrintableString(cx, name, &bytes) || michael@0: !parser->report(reporter, false, pn, JSMSG_REDECLARED_VAR, michael@0: Definition::kindString(dn_kind), bytes.ptr())) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: parser->handler.linkUseToDef(pn, dn); michael@0: return true; michael@0: } michael@0: michael@0: template <> michael@0: bool michael@0: Parser::makeSetCall(ParseNode *pn, unsigned msg) michael@0: { michael@0: JS_ASSERT(pn->isKind(PNK_CALL)); michael@0: JS_ASSERT(pn->isArity(PN_LIST)); michael@0: JS_ASSERT(pn->isOp(JSOP_CALL) || pn->isOp(JSOP_SPREADCALL) || michael@0: pn->isOp(JSOP_EVAL) || pn->isOp(JSOP_SPREADEVAL) || michael@0: pn->isOp(JSOP_FUNCALL) || pn->isOp(JSOP_FUNAPPLY)); michael@0: michael@0: if (!report(ParseStrictError, pc->sc->strict, pn, msg)) michael@0: return false; michael@0: handler.markAsSetCall(pn); michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: bool michael@0: Parser::noteNameUse(HandlePropertyName name, Node pn) michael@0: { michael@0: StmtInfoPC *stmt = LexicalLookup(pc, name, nullptr, (StmtInfoPC *)nullptr); michael@0: michael@0: DefinitionList::Range defs = pc->decls().lookupMulti(name); michael@0: michael@0: DefinitionNode dn; michael@0: if (!defs.empty()) { michael@0: dn = defs.front(); michael@0: } else { michael@0: /* michael@0: * No definition before this use in any lexical scope. michael@0: * Create a placeholder definition node to either: michael@0: * - Be adopted when we parse the real defining michael@0: * declaration, or michael@0: * - Be left as a free variable definition if we never michael@0: * see the real definition. michael@0: */ michael@0: dn = getOrCreateLexicalDependency(pc, name); michael@0: if (!dn) michael@0: return false; michael@0: } michael@0: michael@0: handler.linkUseToDef(pn, dn); michael@0: michael@0: if (stmt && stmt->type == STMT_WITH) michael@0: handler.setFlag(pn, PND_DEOPTIMIZED); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template <> michael@0: bool michael@0: Parser::bindDestructuringVar(BindData *data, ParseNode *pn) michael@0: { michael@0: JS_ASSERT(pn->isKind(PNK_NAME)); michael@0: michael@0: RootedPropertyName name(context, pn->pn_atom->asPropertyName()); michael@0: michael@0: data->pn = pn; michael@0: if (!data->binder(data, name, this)) michael@0: return false; michael@0: michael@0: /* michael@0: * Select the appropriate name-setting opcode, respecting eager selection michael@0: * done by the data->binder function. michael@0: */ michael@0: if (pn->pn_dflags & PND_BOUND) michael@0: pn->setOp(JSOP_SETLOCAL); michael@0: else if (data->op == JSOP_DEFCONST) michael@0: pn->setOp(JSOP_SETCONST); michael@0: else michael@0: pn->setOp(JSOP_SETNAME); michael@0: michael@0: if (data->op == JSOP_DEFCONST) michael@0: pn->pn_dflags |= PND_CONST; michael@0: michael@0: pn->markAsAssigned(); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Destructuring patterns can appear in two kinds of contexts: michael@0: * michael@0: * - assignment-like: assignment expressions and |for| loop heads. In michael@0: * these cases, the patterns' property value positions can be michael@0: * arbitrary lvalue expressions; the destructuring is just a fancy michael@0: * assignment. michael@0: * michael@0: * - declaration-like: |var| and |let| declarations, functions' formal michael@0: * parameter lists, |catch| clauses, and comprehension tails. In michael@0: * these cases, the patterns' property value positions must be michael@0: * simple names; the destructuring defines them as new variables. michael@0: * michael@0: * In both cases, other code parses the pattern as an arbitrary michael@0: * primaryExpr, and then, here in CheckDestructuring, verify that the michael@0: * tree is a valid destructuring expression. michael@0: * michael@0: * In assignment-like contexts, we parse the pattern with michael@0: * pc->inDeclDestructuring clear, so the lvalue expressions in the michael@0: * pattern are parsed normally. primaryExpr links variable references michael@0: * into the appropriate use chains; creates placeholder definitions; michael@0: * and so on. CheckDestructuring is called with |data| nullptr (since michael@0: * we won't be binding any new names), and we specialize lvalues as michael@0: * appropriate. michael@0: * michael@0: * In declaration-like contexts, the normal variable reference michael@0: * processing would just be an obstruction, because we're going to michael@0: * define the names that appear in the property value positions as new michael@0: * variables anyway. In this case, we parse the pattern with michael@0: * pc->inDeclDestructuring set, which directs primaryExpr to leave michael@0: * whatever name nodes it creates unconnected. Then, here in michael@0: * CheckDestructuring, we require the pattern's property value michael@0: * positions to be simple names, and define them as appropriate to the michael@0: * context. For these calls, |data| points to the right sort of michael@0: * BindData. michael@0: * michael@0: * The 'toplevel' is a private detail of the recursive strategy used by michael@0: * CheckDestructuring and callers should use the default value. michael@0: */ michael@0: template <> michael@0: bool michael@0: Parser::checkDestructuring(BindData *data, michael@0: ParseNode *left, bool toplevel) michael@0: { michael@0: bool ok; michael@0: michael@0: if (left->isKind(PNK_ARRAYCOMP)) { michael@0: report(ParseError, false, left, JSMSG_ARRAY_COMP_LEFTSIDE); michael@0: return false; michael@0: } michael@0: michael@0: Rooted blockObj(context); michael@0: blockObj = data && data->binder == bindLet ? data->let.blockObj.get() : nullptr; michael@0: michael@0: if (left->isKind(PNK_ARRAY)) { michael@0: for (ParseNode *pn = left->pn_head; pn; pn = pn->pn_next) { michael@0: if (!pn->isKind(PNK_ELISION)) { michael@0: if (pn->isKind(PNK_ARRAY) || pn->isKind(PNK_OBJECT)) { michael@0: ok = checkDestructuring(data, pn, false); michael@0: } else { michael@0: if (data) { michael@0: if (!pn->isKind(PNK_NAME)) { michael@0: report(ParseError, false, pn, JSMSG_NO_VARIABLE_NAME); michael@0: return false; michael@0: } michael@0: ok = bindDestructuringVar(data, pn); michael@0: } else { michael@0: ok = checkAndMarkAsAssignmentLhs(pn, KeyedDestructuringAssignment); michael@0: } michael@0: } michael@0: if (!ok) michael@0: return false; michael@0: } michael@0: } michael@0: } else { michael@0: JS_ASSERT(left->isKind(PNK_OBJECT)); michael@0: for (ParseNode *member = left->pn_head; member; member = member->pn_next) { michael@0: MOZ_ASSERT(member->isKind(PNK_COLON)); michael@0: ParseNode *expr = member->pn_right; michael@0: michael@0: if (expr->isKind(PNK_ARRAY) || expr->isKind(PNK_OBJECT)) { michael@0: ok = checkDestructuring(data, expr, false); michael@0: } else if (data) { michael@0: if (!expr->isKind(PNK_NAME)) { michael@0: report(ParseError, false, expr, JSMSG_NO_VARIABLE_NAME); michael@0: return false; michael@0: } michael@0: ok = bindDestructuringVar(data, expr); michael@0: } else { michael@0: /* michael@0: * If this is a destructuring shorthand ({x} = ...), then michael@0: * identifierName wasn't used to parse |x|. As a result, |x| michael@0: * hasn't been officially linked to its def or registered in michael@0: * lexdeps. Do that now. michael@0: */ michael@0: if (member->pn_right == member->pn_left) { michael@0: RootedPropertyName name(context, expr->pn_atom->asPropertyName()); michael@0: if (!noteNameUse(name, expr)) michael@0: return false; michael@0: } michael@0: ok = checkAndMarkAsAssignmentLhs(expr, KeyedDestructuringAssignment); michael@0: } michael@0: if (!ok) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template <> michael@0: bool michael@0: Parser::checkDestructuring(BindData *data, michael@0: Node left, bool toplevel) michael@0: { michael@0: return abortIfSyntaxParser(); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::destructuringExpr(BindData *data, TokenKind tt) michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(tt)); michael@0: michael@0: pc->inDeclDestructuring = true; michael@0: Node pn = primaryExpr(tt); michael@0: pc->inDeclDestructuring = false; michael@0: if (!pn) michael@0: return null(); michael@0: if (!checkDestructuring(data, pn)) michael@0: return null(); michael@0: return pn; michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::pushLexicalScope(HandleStaticBlockObject blockObj, StmtInfoPC *stmt) michael@0: { michael@0: JS_ASSERT(blockObj); michael@0: michael@0: ObjectBox *blockbox = newObjectBox(blockObj); michael@0: if (!blockbox) michael@0: return null(); michael@0: michael@0: PushStatementPC(pc, stmt, STMT_BLOCK); michael@0: blockObj->initEnclosingNestedScopeFromParser(pc->staticScope); michael@0: FinishPushNestedScope(pc, stmt, *blockObj.get()); michael@0: stmt->isBlockScope = true; michael@0: michael@0: Node pn = handler.newLexicalScope(blockbox); michael@0: if (!pn) michael@0: return null(); michael@0: michael@0: if (!GenerateBlockId(tokenStream, pc, stmt->blockid)) michael@0: return null(); michael@0: handler.setBlockId(pn, stmt->blockid); michael@0: return pn; michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::pushLexicalScope(StmtInfoPC *stmt) michael@0: { michael@0: RootedStaticBlockObject blockObj(context, StaticBlockObject::create(context)); michael@0: if (!blockObj) michael@0: return null(); michael@0: michael@0: return pushLexicalScope(blockObj, stmt); michael@0: } michael@0: michael@0: struct AddLetDecl michael@0: { michael@0: uint32_t blockid; michael@0: michael@0: AddLetDecl(uint32_t blockid) : blockid(blockid) {} michael@0: michael@0: bool operator()(TokenStream &ts, ParseContext *pc, michael@0: HandleStaticBlockObject blockObj, const Shape &shape, JSAtom *) michael@0: { michael@0: ParseNode *def = (ParseNode *) blockObj->getSlot(shape.slot()).toPrivate(); michael@0: def->pn_blockid = blockid; michael@0: RootedPropertyName name(ts.context(), def->name()); michael@0: return pc->define(ts, name, def, Definition::LET); michael@0: } michael@0: }; michael@0: michael@0: template <> michael@0: ParseNode * michael@0: Parser::pushLetScope(HandleStaticBlockObject blockObj, StmtInfoPC *stmt) michael@0: { michael@0: JS_ASSERT(blockObj); michael@0: ParseNode *pn = pushLexicalScope(blockObj, stmt); michael@0: if (!pn) michael@0: return null(); michael@0: michael@0: pn->pn_dflags |= PND_LET; michael@0: michael@0: /* Populate the new scope with decls found in the head with updated blockid. */ michael@0: if (!ForEachLetDef(tokenStream, pc, blockObj, AddLetDecl(stmt->blockid))) michael@0: return null(); michael@0: michael@0: return pn; michael@0: } michael@0: michael@0: template <> michael@0: SyntaxParseHandler::Node michael@0: Parser::pushLetScope(HandleStaticBlockObject blockObj, StmtInfoPC *stmt) michael@0: { michael@0: JS_ALWAYS_FALSE(abortIfSyntaxParser()); michael@0: return SyntaxParseHandler::NodeFailure; michael@0: } michael@0: michael@0: /* michael@0: * Parse a let block statement or let expression (determined by 'letContext'). michael@0: * In both cases, bindings are not hoisted to the top of the enclosing block michael@0: * and thus must be carefully injected between variables() and the let body. michael@0: */ michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::letBlock(LetContext letContext) michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_LET)); michael@0: michael@0: RootedStaticBlockObject blockObj(context, StaticBlockObject::create(context)); michael@0: if (!blockObj) michael@0: return null(); michael@0: michael@0: uint32_t begin = pos().begin; michael@0: michael@0: MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_LET); michael@0: michael@0: Node vars = variables(PNK_LET, nullptr, blockObj, DontHoistVars); michael@0: if (!vars) michael@0: return null(); michael@0: michael@0: MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_LET); michael@0: michael@0: StmtInfoPC stmtInfo(context); michael@0: Node block = pushLetScope(blockObj, &stmtInfo); michael@0: if (!block) michael@0: return null(); michael@0: michael@0: Node pnlet = handler.newBinary(PNK_LET, vars, block); michael@0: if (!pnlet) michael@0: return null(); michael@0: handler.setBeginPosition(pnlet, begin); michael@0: michael@0: bool needExprStmt = false; michael@0: if (letContext == LetStatement && !tokenStream.matchToken(TOK_LC, TokenStream::Operand)) { michael@0: /* michael@0: * Strict mode eliminates a grammar ambiguity with unparenthesized michael@0: * LetExpressions in an ExpressionStatement. If followed immediately michael@0: * by an arguments list, it's ambiguous whether the let expression michael@0: * is the callee or the call is inside the let expression body. michael@0: * michael@0: * See bug 569464. michael@0: */ michael@0: if (!report(ParseStrictError, pc->sc->strict, pnlet, michael@0: JSMSG_STRICT_CODE_LET_EXPR_STMT)) michael@0: { michael@0: return null(); michael@0: } michael@0: michael@0: /* michael@0: * If this is really an expression in let statement guise, then we michael@0: * need to wrap the PNK_LET node in a PNK_SEMI node so that we pop michael@0: * the return value of the expression. michael@0: */ michael@0: needExprStmt = true; michael@0: letContext = LetExpresion; michael@0: } michael@0: michael@0: Node expr; michael@0: if (letContext == LetStatement) { michael@0: expr = statements(); michael@0: if (!expr) michael@0: return null(); michael@0: MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_LET); michael@0: } else { michael@0: JS_ASSERT(letContext == LetExpresion); michael@0: expr = assignExpr(); michael@0: if (!expr) michael@0: return null(); michael@0: } michael@0: handler.setLexicalScopeBody(block, expr); michael@0: PopStatementPC(tokenStream, pc); michael@0: michael@0: handler.setEndPosition(pnlet, pos().end); michael@0: michael@0: if (needExprStmt) { michael@0: if (!MatchOrInsertSemicolon(tokenStream)) michael@0: return null(); michael@0: return handler.newExprStatement(pnlet, pos().end); michael@0: } michael@0: return pnlet; michael@0: } michael@0: michael@0: template michael@0: static bool michael@0: PushBlocklikeStatement(TokenStream &ts, StmtInfoPC *stmt, StmtType type, michael@0: ParseContext *pc) michael@0: { michael@0: PushStatementPC(pc, stmt, type); michael@0: return GenerateBlockId(ts, pc, stmt->blockid); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::blockStatement() michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_LC)); michael@0: michael@0: StmtInfoPC stmtInfo(context); michael@0: if (!PushBlocklikeStatement(tokenStream, &stmtInfo, STMT_BLOCK, pc)) michael@0: return null(); michael@0: michael@0: Node list = statements(); michael@0: if (!list) michael@0: return null(); michael@0: michael@0: MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_IN_COMPOUND); michael@0: PopStatementPC(tokenStream, pc); michael@0: return list; michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::newBindingNode(PropertyName *name, bool functionScope, VarContext varContext) michael@0: { michael@0: /* michael@0: * If this name is being injected into an existing block/function, see if michael@0: * it has already been declared or if it resolves an outstanding lexdep. michael@0: * Otherwise, this is a let block/expr that introduces a new scope and thus michael@0: * shadows existing decls and doesn't resolve existing lexdeps. Duplicate michael@0: * names are caught by bindLet. michael@0: */ michael@0: if (varContext == HoistVars) { michael@0: if (AtomDefnPtr p = pc->lexdeps->lookup(name)) { michael@0: DefinitionNode lexdep = p.value().get(); michael@0: JS_ASSERT(handler.getDefinitionKind(lexdep) == Definition::PLACEHOLDER); michael@0: michael@0: Node pn = handler.getDefinitionNode(lexdep); michael@0: if (handler.dependencyCovered(pn, pc->blockid(), functionScope)) { michael@0: handler.setBlockId(pn, pc->blockid()); michael@0: pc->lexdeps->remove(p); michael@0: handler.setPosition(pn, pos()); michael@0: return pn; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* Make a new node for this declarator name (or destructuring pattern). */ michael@0: return newName(name); michael@0: } michael@0: michael@0: /* michael@0: * The 'blockObj' parameter is non-null when parsing the 'vars' in a let michael@0: * expression, block statement, non-top-level let declaration in statement michael@0: * context, and the let-initializer of a for-statement. michael@0: */ michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::variables(ParseNodeKind kind, bool *psimple, michael@0: StaticBlockObject *blockObj, VarContext varContext) michael@0: { michael@0: /* michael@0: * The four options here are: michael@0: * - PNK_VAR: We're parsing var declarations. michael@0: * - PNK_CONST: We're parsing const declarations. michael@0: * - PNK_LET: We are parsing a let declaration. michael@0: * - PNK_CALL: We are parsing the head of a let block. michael@0: */ michael@0: JS_ASSERT(kind == PNK_VAR || kind == PNK_CONST || kind == PNK_LET || kind == PNK_CALL); michael@0: michael@0: /* michael@0: * The simple flag is set if the declaration has the form 'var x', with michael@0: * only one variable declared and no initializer expression. michael@0: */ michael@0: JS_ASSERT_IF(psimple, *psimple); michael@0: michael@0: JSOp op = blockObj ? JSOP_NOP : kind == PNK_VAR ? JSOP_DEFVAR : JSOP_DEFCONST; michael@0: michael@0: Node pn = handler.newList(kind, null(), op); michael@0: if (!pn) michael@0: return null(); michael@0: michael@0: /* michael@0: * SpiderMonkey const is really "write once per initialization evaluation" michael@0: * var, whereas let is block scoped. ES-Harmony wants block-scoped const so michael@0: * this code will change soon. michael@0: */ michael@0: BindData data(context); michael@0: if (blockObj) michael@0: data.initLet(varContext, *blockObj, JSMSG_TOO_MANY_LOCALS); michael@0: else michael@0: data.initVarOrConst(op); michael@0: michael@0: bool first = true; michael@0: Node pn2; michael@0: do { michael@0: if (psimple && !first) michael@0: *psimple = false; michael@0: first = false; michael@0: michael@0: TokenKind tt = tokenStream.getToken(); michael@0: if (tt == TOK_LB || tt == TOK_LC) { michael@0: if (psimple) michael@0: *psimple = false; michael@0: michael@0: pc->inDeclDestructuring = true; michael@0: pn2 = primaryExpr(tt); michael@0: pc->inDeclDestructuring = false; michael@0: if (!pn2) michael@0: return null(); michael@0: michael@0: if (!checkDestructuring(&data, pn2)) michael@0: return null(); michael@0: bool ignored; michael@0: if (pc->parsingForInit && matchInOrOf(&ignored)) { michael@0: tokenStream.ungetToken(); michael@0: handler.addList(pn, pn2); michael@0: continue; michael@0: } michael@0: michael@0: MUST_MATCH_TOKEN(TOK_ASSIGN, JSMSG_BAD_DESTRUCT_DECL); michael@0: michael@0: Node init = assignExpr(); michael@0: if (!init) michael@0: return null(); michael@0: michael@0: pn2 = handler.newBinaryOrAppend(PNK_ASSIGN, pn2, init, pc); michael@0: if (!pn2) michael@0: return null(); michael@0: handler.addList(pn, pn2); michael@0: continue; michael@0: } michael@0: michael@0: if (tt != TOK_NAME) { michael@0: if (tt == TOK_YIELD) { michael@0: if (!checkYieldNameValidity()) michael@0: return null(); michael@0: } else { michael@0: if (tt != TOK_ERROR) michael@0: report(ParseError, false, null(), JSMSG_NO_VARIABLE_NAME); michael@0: return null(); michael@0: } michael@0: } michael@0: michael@0: RootedPropertyName name(context, tokenStream.currentName()); michael@0: pn2 = newBindingNode(name, kind == PNK_VAR || kind == PNK_CONST, varContext); michael@0: if (!pn2) michael@0: return null(); michael@0: if (data.op == JSOP_DEFCONST) michael@0: handler.setFlag(pn2, PND_CONST); michael@0: data.pn = pn2; michael@0: if (!data.binder(&data, name, this)) michael@0: return null(); michael@0: handler.addList(pn, pn2); michael@0: michael@0: if (tokenStream.matchToken(TOK_ASSIGN)) { michael@0: if (psimple) michael@0: *psimple = false; michael@0: michael@0: Node init = assignExpr(); michael@0: if (!init) michael@0: return null(); michael@0: michael@0: if (!handler.finishInitializerAssignment(pn2, init, data.op)) michael@0: return null(); michael@0: } michael@0: } while (tokenStream.matchToken(TOK_COMMA)); michael@0: michael@0: return pn; michael@0: } michael@0: michael@0: template <> michael@0: ParseNode * michael@0: Parser::letDeclaration() michael@0: { michael@0: handler.disableSyntaxParser(); michael@0: michael@0: ParseNode *pn; michael@0: michael@0: do { michael@0: /* michael@0: * This is a let declaration. We must be directly under a block per the michael@0: * proposed ES4 specs, but not an implicit block created due to michael@0: * 'for (let ...)'. If we pass this error test, make the enclosing michael@0: * StmtInfoPC be our scope. Further let declarations in this block will michael@0: * find this scope statement and use the same block object. michael@0: * michael@0: * If we are the first let declaration in this block (i.e., when the michael@0: * enclosing maybe-scope StmtInfoPC isn't yet a scope statement) then michael@0: * we also need to set pc->blockNode to be our PNK_LEXICALSCOPE. michael@0: */ michael@0: StmtInfoPC *stmt = pc->topStmt; michael@0: if (stmt && (!stmt->maybeScope() || stmt->isForLetBlock)) { michael@0: report(ParseError, false, null(), JSMSG_LET_DECL_NOT_IN_BLOCK); michael@0: return null(); michael@0: } michael@0: michael@0: if (stmt && stmt->isBlockScope) { michael@0: JS_ASSERT(pc->staticScope == stmt->staticScope); michael@0: } else { michael@0: if (pc->atBodyLevel()) { michael@0: /* michael@0: * ES4 specifies that let at top level and at body-block scope michael@0: * does not shadow var, so convert back to var. michael@0: */ michael@0: pn = variables(PNK_VAR); michael@0: if (!pn) michael@0: return null(); michael@0: pn->pn_xflags |= PNX_POPVAR; michael@0: break; michael@0: } michael@0: michael@0: /* michael@0: * Some obvious assertions here, but they may help clarify the michael@0: * situation. This stmt is not yet a scope, so it must not be a michael@0: * catch block (catch is a lexical scope by definition). michael@0: */ michael@0: JS_ASSERT(!stmt->isBlockScope); michael@0: JS_ASSERT(stmt != pc->topScopeStmt); michael@0: JS_ASSERT(stmt->type == STMT_BLOCK || michael@0: stmt->type == STMT_SWITCH || michael@0: stmt->type == STMT_TRY || michael@0: stmt->type == STMT_FINALLY); michael@0: JS_ASSERT(!stmt->downScope); michael@0: michael@0: /* Convert the block statement into a scope statement. */ michael@0: StaticBlockObject *blockObj = StaticBlockObject::create(context); michael@0: if (!blockObj) michael@0: return null(); michael@0: michael@0: ObjectBox *blockbox = newObjectBox(blockObj); michael@0: if (!blockbox) michael@0: return null(); michael@0: michael@0: /* michael@0: * Insert stmt on the pc->topScopeStmt/stmtInfo.downScope linked michael@0: * list stack, if it isn't already there. If it is there, but it michael@0: * lacks the SIF_SCOPE flag, it must be a try, catch, or finally michael@0: * block. michael@0: */ michael@0: stmt->isBlockScope = stmt->isNestedScope = true; michael@0: stmt->downScope = pc->topScopeStmt; michael@0: pc->topScopeStmt = stmt; michael@0: michael@0: blockObj->initEnclosingNestedScopeFromParser(pc->staticScope); michael@0: pc->staticScope = blockObj; michael@0: stmt->staticScope = blockObj; michael@0: michael@0: #ifdef DEBUG michael@0: ParseNode *tmp = pc->blockNode; michael@0: JS_ASSERT(!tmp || !tmp->isKind(PNK_LEXICALSCOPE)); michael@0: #endif michael@0: michael@0: /* Create a new lexical scope node for these statements. */ michael@0: ParseNode *pn1 = LexicalScopeNode::create(PNK_LEXICALSCOPE, &handler); michael@0: if (!pn1) michael@0: return null(); michael@0: michael@0: pn1->pn_pos = pc->blockNode->pn_pos; michael@0: pn1->pn_objbox = blockbox; michael@0: pn1->pn_expr = pc->blockNode; michael@0: pn1->pn_blockid = pc->blockNode->pn_blockid; michael@0: pc->blockNode = pn1; michael@0: } michael@0: michael@0: pn = variables(PNK_LET, nullptr, &pc->staticScope->as(), HoistVars); michael@0: if (!pn) michael@0: return null(); michael@0: pn->pn_xflags = PNX_POPVAR; michael@0: } while (0); michael@0: michael@0: return MatchOrInsertSemicolon(tokenStream) ? pn : nullptr; michael@0: } michael@0: michael@0: template <> michael@0: SyntaxParseHandler::Node michael@0: Parser::letDeclaration() michael@0: { michael@0: JS_ALWAYS_FALSE(abortIfSyntaxParser()); michael@0: return SyntaxParseHandler::NodeFailure; michael@0: } michael@0: michael@0: template <> michael@0: ParseNode * michael@0: Parser::letStatement() michael@0: { michael@0: handler.disableSyntaxParser(); michael@0: michael@0: /* Check for a let statement or let expression. */ michael@0: ParseNode *pn; michael@0: if (tokenStream.peekToken() == TOK_LP) { michael@0: pn = letBlock(LetStatement); michael@0: JS_ASSERT_IF(pn, pn->isKind(PNK_LET) || pn->isKind(PNK_SEMI)); michael@0: } else { michael@0: pn = letDeclaration(); michael@0: } michael@0: return pn; michael@0: } michael@0: michael@0: template <> michael@0: SyntaxParseHandler::Node michael@0: Parser::letStatement() michael@0: { michael@0: JS_ALWAYS_FALSE(abortIfSyntaxParser()); michael@0: return SyntaxParseHandler::NodeFailure; michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::importDeclaration() michael@0: { michael@0: JS_ASSERT(tokenStream.currentToken().type == TOK_IMPORT); michael@0: michael@0: if (pc->sc->isFunctionBox() || !pc->atBodyLevel()) { michael@0: report(ParseError, false, null(), JSMSG_IMPORT_DECL_AT_TOP_LEVEL); michael@0: return null(); michael@0: } michael@0: michael@0: uint32_t begin = pos().begin; michael@0: TokenKind tt = tokenStream.getToken(); michael@0: michael@0: Node importSpecSet = handler.newList(PNK_IMPORT_SPEC_LIST); michael@0: if (!importSpecSet) michael@0: return null(); michael@0: michael@0: if (tt == TOK_NAME || tt == TOK_LC) { michael@0: if (tt == TOK_NAME) { michael@0: // Handle the form |import a from 'b'|, by adding a single import michael@0: // specifier to the list, with 'default' as the import name and michael@0: // 'a' as the binding name. This is equivalent to michael@0: // |import { default as a } from 'b'|. michael@0: Node importName = newName(context->names().default_); michael@0: if (!importName) michael@0: return null(); michael@0: michael@0: Node bindingName = newName(tokenStream.currentName()); michael@0: if (!bindingName) michael@0: return null(); michael@0: michael@0: Node importSpec = handler.newBinary(PNK_IMPORT_SPEC, importName, bindingName); michael@0: if (!importSpec) michael@0: return null(); michael@0: michael@0: handler.addList(importSpecSet, importSpec); michael@0: } else { michael@0: do { michael@0: // Handle the forms |import {} from 'a'| and michael@0: // |import { ..., } from 'a'| (where ... is non empty), by michael@0: // escaping the loop early if the next token is }. michael@0: tt = tokenStream.peekToken(TokenStream::KeywordIsName); michael@0: if (tt == TOK_ERROR) michael@0: return null(); michael@0: if (tt == TOK_RC) michael@0: break; michael@0: michael@0: // If the next token is a keyword, the previous call to michael@0: // peekToken matched it as a TOK_NAME, and put it in the michael@0: // lookahead buffer, so this call will match keywords as well. michael@0: MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_IMPORT_NAME); michael@0: Node importName = newName(tokenStream.currentName()); michael@0: if (!importName) michael@0: return null(); michael@0: michael@0: if (tokenStream.getToken() == TOK_NAME && michael@0: tokenStream.currentName() == context->names().as) michael@0: { michael@0: if (tokenStream.getToken() != TOK_NAME) { michael@0: report(ParseError, false, null(), JSMSG_NO_BINDING_NAME); michael@0: return null(); michael@0: } michael@0: } else { michael@0: // Keywords cannot be bound to themselves, so an import name michael@0: // that is a keyword is a syntax error if it is not followed michael@0: // by the keyword 'as'. michael@0: if (IsKeyword(importName->name())) { michael@0: JSAutoByteString bytes; michael@0: if (!AtomToPrintableString(context, importName->name(), &bytes)) michael@0: return null(); michael@0: report(ParseError, false, null(), JSMSG_AS_AFTER_RESERVED_WORD, bytes.ptr()); michael@0: return null(); michael@0: } michael@0: tokenStream.ungetToken(); michael@0: } michael@0: Node bindingName = newName(tokenStream.currentName()); michael@0: if (!bindingName) michael@0: return null(); michael@0: michael@0: Node importSpec = handler.newBinary(PNK_IMPORT_SPEC, importName, bindingName); michael@0: if (!importSpec) michael@0: return null(); michael@0: michael@0: handler.addList(importSpecSet, importSpec); michael@0: } while (tokenStream.matchToken(TOK_COMMA)); michael@0: michael@0: MUST_MATCH_TOKEN(TOK_RC, JSMSG_RC_AFTER_IMPORT_SPEC_LIST); michael@0: } michael@0: michael@0: if (tokenStream.getToken() != TOK_NAME || michael@0: tokenStream.currentName() != context->names().from) michael@0: { michael@0: report(ParseError, false, null(), JSMSG_FROM_AFTER_IMPORT_SPEC_SET); michael@0: return null(); michael@0: } michael@0: michael@0: MUST_MATCH_TOKEN(TOK_STRING, JSMSG_MODULE_SPEC_AFTER_FROM); michael@0: } else { michael@0: if (tt != TOK_STRING) { michael@0: report(ParseError, false, null(), JSMSG_DECLARATION_AFTER_IMPORT); michael@0: return null(); michael@0: } michael@0: michael@0: // Handle the form |import 'a'| by leaving the list empty. This is michael@0: // equivalent to |import {} from 'a'|. michael@0: importSpecSet->pn_pos.end = importSpecSet->pn_pos.begin; michael@0: } michael@0: michael@0: Node moduleSpec = stringLiteral(); michael@0: if (!moduleSpec) michael@0: return null(); michael@0: michael@0: if (!MatchOrInsertSemicolon(tokenStream)) michael@0: return null(); michael@0: michael@0: return handler.newImportDeclaration(importSpecSet, moduleSpec, michael@0: TokenPos(begin, pos().end)); michael@0: } michael@0: michael@0: template<> michael@0: SyntaxParseHandler::Node michael@0: Parser::importDeclaration() michael@0: { michael@0: JS_ALWAYS_FALSE(abortIfSyntaxParser()); michael@0: return SyntaxParseHandler::NodeFailure; michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::exportDeclaration() michael@0: { michael@0: JS_ASSERT(tokenStream.currentToken().type == TOK_EXPORT); michael@0: michael@0: if (pc->sc->isFunctionBox() || !pc->atBodyLevel()) { michael@0: report(ParseError, false, null(), JSMSG_EXPORT_DECL_AT_TOP_LEVEL); michael@0: return null(); michael@0: } michael@0: michael@0: uint32_t begin = pos().begin; michael@0: michael@0: Node kid; michael@0: switch (TokenKind tt = tokenStream.getToken()) { michael@0: case TOK_LC: michael@0: case TOK_MUL: michael@0: kid = handler.newList(PNK_EXPORT_SPEC_LIST); michael@0: if (!kid) michael@0: return null(); michael@0: michael@0: if (tt == TOK_LC) { michael@0: do { michael@0: // Handle the forms |export {}| and |export { ..., }| (where ... michael@0: // is non empty), by escaping the loop early if the next token michael@0: // is }. michael@0: tt = tokenStream.peekToken(); michael@0: if (tt == TOK_ERROR) michael@0: return null(); michael@0: if (tt == TOK_RC) michael@0: break; michael@0: michael@0: MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_BINDING_NAME); michael@0: Node bindingName = newName(tokenStream.currentName()); michael@0: if (!bindingName) michael@0: return null(); michael@0: michael@0: if (tokenStream.getToken() == TOK_NAME && michael@0: tokenStream.currentName() == context->names().as) michael@0: { michael@0: if (tokenStream.getToken(TokenStream::KeywordIsName) != TOK_NAME) { michael@0: report(ParseError, false, null(), JSMSG_NO_EXPORT_NAME); michael@0: return null(); michael@0: } michael@0: } else { michael@0: tokenStream.ungetToken(); michael@0: } michael@0: Node exportName = newName(tokenStream.currentName()); michael@0: if (!exportName) michael@0: return null(); michael@0: michael@0: Node exportSpec = handler.newBinary(PNK_EXPORT_SPEC, bindingName, exportName); michael@0: if (!exportSpec) michael@0: return null(); michael@0: michael@0: handler.addList(kid, exportSpec); michael@0: } while (tokenStream.matchToken(TOK_COMMA)); michael@0: michael@0: MUST_MATCH_TOKEN(TOK_RC, JSMSG_RC_AFTER_EXPORT_SPEC_LIST); michael@0: } else { michael@0: // Handle the form |export *| by adding a special export batch michael@0: // specifier to the list. michael@0: Node exportSpec = handler.newNullary(PNK_EXPORT_BATCH_SPEC, JSOP_NOP, pos()); michael@0: if (!kid) michael@0: return null(); michael@0: michael@0: handler.addList(kid, exportSpec); michael@0: } michael@0: if (tokenStream.getToken() == TOK_NAME && michael@0: tokenStream.currentName() == context->names().from) michael@0: { michael@0: MUST_MATCH_TOKEN(TOK_STRING, JSMSG_MODULE_SPEC_AFTER_FROM); michael@0: michael@0: Node moduleSpec = stringLiteral(); michael@0: if (!moduleSpec) michael@0: return null(); michael@0: michael@0: if (!MatchOrInsertSemicolon(tokenStream)) michael@0: return null(); michael@0: michael@0: return handler.newExportFromDeclaration(begin, kid, moduleSpec); michael@0: } else { michael@0: tokenStream.ungetToken(); michael@0: } michael@0: michael@0: kid = MatchOrInsertSemicolon(tokenStream) ? kid : nullptr; michael@0: if (!kid) michael@0: return null(); michael@0: break; michael@0: michael@0: case TOK_FUNCTION: michael@0: kid = functionStmt(); michael@0: if (!kid) michael@0: return null(); michael@0: break; michael@0: michael@0: case TOK_VAR: michael@0: case TOK_CONST: michael@0: kid = variables(tt == TOK_VAR ? PNK_VAR : PNK_CONST); michael@0: if (!kid) michael@0: return null(); michael@0: kid->pn_xflags = PNX_POPVAR; michael@0: michael@0: kid = MatchOrInsertSemicolon(tokenStream) ? kid : nullptr; michael@0: if (!kid) michael@0: return null(); michael@0: break; michael@0: michael@0: case TOK_NAME: michael@0: // Handle the form |export a} in the same way as |export let a|, by michael@0: // acting as if we've just seen the let keyword. Simply unget the token michael@0: // and fall through. michael@0: tokenStream.ungetToken(); michael@0: case TOK_LET: michael@0: kid = letDeclaration(); michael@0: if (!kid) michael@0: return null(); michael@0: break; michael@0: michael@0: default: michael@0: report(ParseError, false, null(), JSMSG_DECLARATION_AFTER_EXPORT); michael@0: return null(); michael@0: } michael@0: michael@0: return handler.newExportDeclaration(kid, TokenPos(begin, pos().end)); michael@0: } michael@0: michael@0: template<> michael@0: SyntaxParseHandler::Node michael@0: Parser::exportDeclaration() michael@0: { michael@0: JS_ALWAYS_FALSE(abortIfSyntaxParser()); michael@0: return SyntaxParseHandler::NodeFailure; michael@0: } michael@0: michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::expressionStatement() michael@0: { michael@0: tokenStream.ungetToken(); michael@0: Node pnexpr = expr(); michael@0: if (!pnexpr) michael@0: return null(); michael@0: if (!MatchOrInsertSemicolon(tokenStream)) michael@0: return null(); michael@0: return handler.newExprStatement(pnexpr, pos().end); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::ifStatement() michael@0: { michael@0: uint32_t begin = pos().begin; michael@0: michael@0: /* An IF node has three kids: condition, then, and optional else. */ michael@0: Node cond = condition(); michael@0: if (!cond) michael@0: return null(); michael@0: michael@0: if (tokenStream.peekToken(TokenStream::Operand) == TOK_SEMI && michael@0: !report(ParseExtraWarning, false, null(), JSMSG_EMPTY_CONSEQUENT)) michael@0: { michael@0: return null(); michael@0: } michael@0: michael@0: StmtInfoPC stmtInfo(context); michael@0: PushStatementPC(pc, &stmtInfo, STMT_IF); michael@0: Node thenBranch = statement(); michael@0: if (!thenBranch) michael@0: return null(); michael@0: michael@0: Node elseBranch; michael@0: if (tokenStream.matchToken(TOK_ELSE, TokenStream::Operand)) { michael@0: stmtInfo.type = STMT_ELSE; michael@0: elseBranch = statement(); michael@0: if (!elseBranch) michael@0: return null(); michael@0: } else { michael@0: elseBranch = null(); michael@0: } michael@0: michael@0: PopStatementPC(tokenStream, pc); michael@0: return handler.newIfStatement(begin, cond, thenBranch, elseBranch); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::doWhileStatement() michael@0: { michael@0: uint32_t begin = pos().begin; michael@0: StmtInfoPC stmtInfo(context); michael@0: PushStatementPC(pc, &stmtInfo, STMT_DO_LOOP); michael@0: Node body = statement(); michael@0: if (!body) michael@0: return null(); michael@0: MUST_MATCH_TOKEN(TOK_WHILE, JSMSG_WHILE_AFTER_DO); michael@0: Node cond = condition(); michael@0: if (!cond) michael@0: return null(); michael@0: PopStatementPC(tokenStream, pc); michael@0: michael@0: if (versionNumber() == JSVERSION_ECMA_3) { michael@0: // Pedantically require a semicolon or line break, following ES3. michael@0: // Bug 880329 proposes removing this case. michael@0: if (!MatchOrInsertSemicolon(tokenStream)) michael@0: return null(); michael@0: } else { michael@0: // The semicolon after do-while is even more optional than most michael@0: // semicolons in JS. Web compat required this by 2004: michael@0: // http://bugzilla.mozilla.org/show_bug.cgi?id=238945 michael@0: // ES3 and ES5 disagreed, but ES6 conforms to Web reality: michael@0: // https://bugs.ecmascript.org/show_bug.cgi?id=157 michael@0: (void) tokenStream.matchToken(TOK_SEMI); michael@0: } michael@0: michael@0: return handler.newDoWhileStatement(body, cond, TokenPos(begin, pos().end)); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::whileStatement() michael@0: { michael@0: uint32_t begin = pos().begin; michael@0: StmtInfoPC stmtInfo(context); michael@0: PushStatementPC(pc, &stmtInfo, STMT_WHILE_LOOP); michael@0: Node cond = condition(); michael@0: if (!cond) michael@0: return null(); michael@0: Node body = statement(); michael@0: if (!body) michael@0: return null(); michael@0: PopStatementPC(tokenStream, pc); michael@0: return handler.newWhileStatement(begin, cond, body); michael@0: } michael@0: michael@0: template michael@0: bool michael@0: Parser::matchInOrOf(bool *isForOfp) michael@0: { michael@0: if (tokenStream.matchToken(TOK_IN)) { michael@0: *isForOfp = false; michael@0: return true; michael@0: } michael@0: if (tokenStream.matchContextualKeyword(context->names().of)) { michael@0: *isForOfp = true; michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: template <> michael@0: bool michael@0: Parser::isValidForStatementLHS(ParseNode *pn1, JSVersion version, michael@0: bool isForDecl, bool isForEach, michael@0: ParseNodeKind headKind) michael@0: { michael@0: if (isForDecl) { michael@0: if (pn1->pn_count > 1) michael@0: return false; michael@0: if (pn1->isOp(JSOP_DEFCONST)) michael@0: return false; michael@0: michael@0: // In JS 1.7 only, for (var [K, V] in EXPR) has a special meaning. michael@0: // Hence all other destructuring decls are banned there. michael@0: if (version == JSVERSION_1_7 && !isForEach && headKind == PNK_FORIN) { michael@0: ParseNode *lhs = pn1->pn_head; michael@0: if (lhs->isKind(PNK_ASSIGN)) michael@0: lhs = lhs->pn_left; michael@0: michael@0: if (lhs->isKind(PNK_OBJECT)) michael@0: return false; michael@0: if (lhs->isKind(PNK_ARRAY) && lhs->pn_count != 2) michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: switch (pn1->getKind()) { michael@0: case PNK_NAME: michael@0: case PNK_DOT: michael@0: case PNK_CALL: michael@0: case PNK_ELEM: michael@0: return true; michael@0: michael@0: case PNK_ARRAY: michael@0: case PNK_OBJECT: michael@0: // In JS 1.7 only, for ([K, V] in EXPR) has a special meaning. michael@0: // Hence all other destructuring left-hand sides are banned there. michael@0: if (version == JSVERSION_1_7 && !isForEach && headKind == PNK_FORIN) michael@0: return pn1->isKind(PNK_ARRAY) && pn1->pn_count == 2; michael@0: return true; michael@0: michael@0: default: michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: template <> michael@0: ParseNode * michael@0: Parser::forStatement() michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR)); michael@0: uint32_t begin = pos().begin; michael@0: michael@0: StmtInfoPC forStmt(context); michael@0: PushStatementPC(pc, &forStmt, STMT_FOR_LOOP); michael@0: michael@0: bool isForEach = false; michael@0: unsigned iflags = 0; michael@0: michael@0: if (allowsForEachIn() && tokenStream.matchContextualKeyword(context->names().each)) { michael@0: iflags = JSITER_FOREACH; michael@0: isForEach = true; michael@0: } michael@0: michael@0: MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); michael@0: michael@0: /* michael@0: * True if we have 'for (var/let/const ...)', except in the oddball case michael@0: * where 'let' begins a let-expression in 'for (let (...) ...)'. michael@0: */ michael@0: bool isForDecl = false; michael@0: michael@0: /* Non-null when isForDecl is true for a 'for (let ...)' statement. */ michael@0: RootedStaticBlockObject blockObj(context); michael@0: michael@0: /* Set to 'x' in 'for (x ;... ;...)' or 'for (x in ...)'. */ michael@0: ParseNode *pn1; michael@0: michael@0: { michael@0: TokenKind tt = tokenStream.peekToken(TokenStream::Operand); michael@0: if (tt == TOK_SEMI) { michael@0: pn1 = nullptr; michael@0: } else { michael@0: /* michael@0: * Set pn1 to a var list or an initializing expression. michael@0: * michael@0: * Set the parsingForInit flag during parsing of the first clause michael@0: * of the for statement. This flag will be used by the RelExpr michael@0: * production; if it is set, then the 'in' keyword will not be michael@0: * recognized as an operator, leaving it available to be parsed as michael@0: * part of a for/in loop. michael@0: * michael@0: * A side effect of this restriction is that (unparenthesized) michael@0: * expressions involving an 'in' operator are illegal in the init michael@0: * clause of an ordinary for loop. michael@0: */ michael@0: pc->parsingForInit = true; michael@0: if (tt == TOK_VAR || tt == TOK_CONST) { michael@0: isForDecl = true; michael@0: tokenStream.consumeKnownToken(tt); michael@0: pn1 = variables(tt == TOK_VAR ? PNK_VAR : PNK_CONST); michael@0: } michael@0: else if (tt == TOK_LET) { michael@0: handler.disableSyntaxParser(); michael@0: (void) tokenStream.getToken(); michael@0: if (tokenStream.peekToken() == TOK_LP) { michael@0: pn1 = letBlock(LetExpresion); michael@0: } else { michael@0: isForDecl = true; michael@0: blockObj = StaticBlockObject::create(context); michael@0: if (!blockObj) michael@0: return null(); michael@0: pn1 = variables(PNK_LET, nullptr, blockObj, DontHoistVars); michael@0: } michael@0: } michael@0: else { michael@0: pn1 = expr(); michael@0: } michael@0: pc->parsingForInit = false; michael@0: if (!pn1) michael@0: return null(); michael@0: } michael@0: } michael@0: michael@0: JS_ASSERT_IF(isForDecl, pn1->isArity(PN_LIST)); michael@0: JS_ASSERT(!!blockObj == (isForDecl && pn1->isOp(JSOP_NOP))); michael@0: michael@0: // The form 'for (let ; ; ) ' generates an michael@0: // implicit block even if stmt is not a BlockStatement. michael@0: // If the loop has that exact form, then: michael@0: // - forLetImpliedBlock is the node for the implicit block scope. michael@0: // - forLetDecl is the node for the decl 'let '. michael@0: // Otherwise both are null. michael@0: ParseNode *forLetImpliedBlock = nullptr; michael@0: ParseNode *forLetDecl = nullptr; michael@0: michael@0: // If non-null, the node for the decl 'var v = expr1' in the weirdo form michael@0: // 'for (var v = expr1 in expr2) stmt'. michael@0: ParseNode *hoistedVar = nullptr; michael@0: michael@0: /* michael@0: * We can be sure that it's a for/in loop if there's still an 'in' michael@0: * keyword here, even if JavaScript recognizes 'in' as an operator, michael@0: * as we've excluded 'in' from being parsed in RelExpr by setting michael@0: * pc->parsingForInit. michael@0: */ michael@0: StmtInfoPC letStmt(context); /* used if blockObj != nullptr. */ michael@0: ParseNode *pn2, *pn3; /* forHead->pn_kid2 and pn_kid3. */ michael@0: ParseNodeKind headKind = PNK_FORHEAD; michael@0: if (pn1) { michael@0: bool isForOf; michael@0: if (matchInOrOf(&isForOf)) michael@0: headKind = isForOf ? PNK_FOROF : PNK_FORIN; michael@0: } michael@0: michael@0: if (headKind == PNK_FOROF || headKind == PNK_FORIN) { michael@0: /* michael@0: * Parse the rest of the for/in or for/of head. michael@0: * michael@0: * Here pn1 is everything to the left of 'in' or 'of'. At the end of michael@0: * this block, pn1 is a decl or nullptr, pn2 is the assignment target michael@0: * that receives the enumeration value each iteration, and pn3 is the michael@0: * rhs of 'in'. michael@0: */ michael@0: if (headKind == PNK_FOROF) { michael@0: forStmt.type = STMT_FOR_OF_LOOP; michael@0: forStmt.type = (headKind == PNK_FOROF) ? STMT_FOR_OF_LOOP : STMT_FOR_IN_LOOP; michael@0: if (isForEach) { michael@0: report(ParseError, false, null(), JSMSG_BAD_FOR_EACH_LOOP); michael@0: return null(); michael@0: } michael@0: } else { michael@0: forStmt.type = STMT_FOR_IN_LOOP; michael@0: iflags |= JSITER_ENUMERATE; michael@0: } michael@0: michael@0: /* Check that the left side of the 'in' or 'of' is valid. */ michael@0: if (!isValidForStatementLHS(pn1, versionNumber(), isForDecl, isForEach, headKind)) { michael@0: report(ParseError, false, pn1, JSMSG_BAD_FOR_LEFTSIDE); michael@0: return null(); michael@0: } michael@0: michael@0: /* michael@0: * After the following if-else, pn2 will point to the name or michael@0: * destructuring pattern on in's left. pn1 will point to the decl, if michael@0: * any, else nullptr. Note that the "declaration with initializer" case michael@0: * rewrites the loop-head, moving the decl and setting pn1 to nullptr. michael@0: */ michael@0: if (isForDecl) { michael@0: pn2 = pn1->pn_head; michael@0: if ((pn2->isKind(PNK_NAME) && pn2->maybeExpr()) || pn2->isKind(PNK_ASSIGN)) { michael@0: /* michael@0: * Declaration with initializer. michael@0: * michael@0: * Rewrite 'for ( x = i in o)' where is 'var' or michael@0: * 'const' to hoist the initializer or the entire decl out of michael@0: * the loop head. michael@0: */ michael@0: if (headKind == PNK_FOROF) { michael@0: report(ParseError, false, pn2, JSMSG_INVALID_FOR_OF_INIT); michael@0: return null(); michael@0: } michael@0: if (blockObj) { michael@0: report(ParseError, false, pn2, JSMSG_INVALID_FOR_IN_INIT); michael@0: return null(); michael@0: } michael@0: michael@0: hoistedVar = pn1; michael@0: michael@0: /* michael@0: * All of 'var x = i' is hoisted above 'for (x in o)'. michael@0: * michael@0: * Request JSOP_POP here since the var is for a simple michael@0: * name (it is not a destructuring binding's left-hand michael@0: * side) and it has an initializer. michael@0: */ michael@0: pn1->pn_xflags |= PNX_POPVAR; michael@0: pn1 = nullptr; michael@0: michael@0: if (pn2->isKind(PNK_ASSIGN)) { michael@0: pn2 = pn2->pn_left; michael@0: JS_ASSERT(pn2->isKind(PNK_ARRAY) || pn2->isKind(PNK_OBJECT) || michael@0: pn2->isKind(PNK_NAME)); michael@0: } michael@0: } michael@0: } else { michael@0: /* Not a declaration. */ michael@0: JS_ASSERT(!blockObj); michael@0: pn2 = pn1; michael@0: pn1 = nullptr; michael@0: michael@0: if (!checkAndMarkAsAssignmentLhs(pn2, PlainAssignment)) michael@0: return null(); michael@0: } michael@0: michael@0: pn3 = (headKind == PNK_FOROF) ? assignExpr() : expr(); michael@0: if (!pn3) michael@0: return null(); michael@0: michael@0: if (blockObj) { michael@0: /* michael@0: * Now that the pn3 has been parsed, push the let scope. To hold michael@0: * the blockObj for the emitter, wrap the PNK_LEXICALSCOPE node michael@0: * created by PushLetScope around the for's initializer. This also michael@0: * serves to indicate the let-decl to the emitter. michael@0: */ michael@0: ParseNode *block = pushLetScope(blockObj, &letStmt); michael@0: if (!block) michael@0: return null(); michael@0: letStmt.isForLetBlock = true; michael@0: block->pn_expr = pn1; michael@0: block->pn_pos = pn1->pn_pos; michael@0: pn1 = block; michael@0: } michael@0: michael@0: if (isForDecl) { michael@0: /* michael@0: * pn2 is part of a declaration. Make a copy that can be passed to michael@0: * EmitAssignment. Take care to do this after PushLetScope. michael@0: */ michael@0: pn2 = cloneLeftHandSide(pn2); michael@0: if (!pn2) michael@0: return null(); michael@0: } michael@0: michael@0: switch (pn2->getKind()) { michael@0: case PNK_NAME: michael@0: /* Beware 'for (arguments in ...)' with or without a 'var'. */ michael@0: pn2->markAsAssigned(); michael@0: break; michael@0: michael@0: case PNK_ASSIGN: michael@0: MOZ_ASSUME_UNREACHABLE("forStatement TOK_ASSIGN"); michael@0: michael@0: case PNK_ARRAY: michael@0: case PNK_OBJECT: michael@0: if (versionNumber() == JSVERSION_1_7) { michael@0: /* michael@0: * Destructuring for-in requires [key, value] enumeration michael@0: * in JS1.7. michael@0: */ michael@0: if (!isForEach && headKind == PNK_FORIN) michael@0: iflags |= JSITER_FOREACH | JSITER_KEYVALUE; michael@0: } michael@0: break; michael@0: michael@0: default:; michael@0: } michael@0: } else { michael@0: if (isForEach) { michael@0: reportWithOffset(ParseError, false, begin, JSMSG_BAD_FOR_EACH_LOOP); michael@0: return null(); michael@0: } michael@0: michael@0: headKind = PNK_FORHEAD; michael@0: michael@0: if (blockObj) { michael@0: /* michael@0: * Desugar 'for (let A; B; C) D' into 'let (A) { for (; B; C) D }' michael@0: * to induce the correct scoping for A. michael@0: */ michael@0: forLetImpliedBlock = pushLetScope(blockObj, &letStmt); michael@0: if (!forLetImpliedBlock) michael@0: return null(); michael@0: letStmt.isForLetBlock = true; michael@0: michael@0: forLetDecl = pn1; michael@0: pn1 = nullptr; michael@0: } michael@0: michael@0: /* Parse the loop condition or null into pn2. */ michael@0: MUST_MATCH_TOKEN(TOK_SEMI, JSMSG_SEMI_AFTER_FOR_INIT); michael@0: if (tokenStream.peekToken(TokenStream::Operand) == TOK_SEMI) { michael@0: pn2 = nullptr; michael@0: } else { michael@0: pn2 = expr(); michael@0: if (!pn2) michael@0: return null(); michael@0: } michael@0: michael@0: /* Parse the update expression or null into pn3. */ michael@0: MUST_MATCH_TOKEN(TOK_SEMI, JSMSG_SEMI_AFTER_FOR_COND); michael@0: if (tokenStream.peekToken(TokenStream::Operand) == TOK_RP) { michael@0: pn3 = nullptr; michael@0: } else { michael@0: pn3 = expr(); michael@0: if (!pn3) michael@0: return null(); michael@0: } michael@0: } michael@0: michael@0: MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL); michael@0: michael@0: TokenPos headPos(begin, pos().end); michael@0: ParseNode *forHead = handler.newForHead(headKind, pn1, pn2, pn3, headPos); michael@0: if (!forHead) michael@0: return null(); michael@0: michael@0: /* Parse the loop body. */ michael@0: ParseNode *body = statement(); michael@0: if (!body) michael@0: return null(); michael@0: michael@0: if (blockObj) michael@0: PopStatementPC(tokenStream, pc); michael@0: PopStatementPC(tokenStream, pc); michael@0: michael@0: ParseNode *forLoop = handler.newForStatement(begin, forHead, body, iflags); michael@0: if (!forLoop) michael@0: return null(); michael@0: michael@0: if (hoistedVar) { michael@0: ParseNode *pnseq = handler.newList(PNK_SEQ, hoistedVar); michael@0: if (!pnseq) michael@0: return null(); michael@0: pnseq->pn_pos = forLoop->pn_pos; michael@0: pnseq->append(forLoop); michael@0: return pnseq; michael@0: } michael@0: if (forLetImpliedBlock) { michael@0: forLetImpliedBlock->pn_expr = forLoop; michael@0: forLetImpliedBlock->pn_pos = forLoop->pn_pos; michael@0: ParseNode *let = handler.newBinary(PNK_LET, forLetDecl, forLetImpliedBlock); michael@0: if (!let) michael@0: return null(); michael@0: let->pn_pos = forLoop->pn_pos; michael@0: return let; michael@0: } michael@0: return forLoop; michael@0: } michael@0: michael@0: template <> michael@0: SyntaxParseHandler::Node michael@0: Parser::forStatement() michael@0: { michael@0: /* michael@0: * 'for' statement parsing is fantastically complicated and requires being michael@0: * able to inspect the parse tree for previous parts of the 'for'. Syntax michael@0: * parsing of 'for' statements is thus done separately, and only handles michael@0: * the types of 'for' statements likely to be seen in web content. michael@0: */ michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR)); michael@0: michael@0: StmtInfoPC forStmt(context); michael@0: PushStatementPC(pc, &forStmt, STMT_FOR_LOOP); michael@0: michael@0: /* Don't parse 'for each' loops. */ michael@0: if (allowsForEachIn()) { michael@0: TokenKind tt = tokenStream.peekToken(); michael@0: // Not all "yield" tokens are names, but the ones that aren't names are michael@0: // invalid in this context anyway. michael@0: if (tt == TOK_NAME || tt == TOK_YIELD) { michael@0: JS_ALWAYS_FALSE(abortIfSyntaxParser()); michael@0: return null(); michael@0: } michael@0: } michael@0: michael@0: MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); michael@0: michael@0: /* True if we have 'for (var ...)'. */ michael@0: bool isForDecl = false; michael@0: bool simpleForDecl = true; michael@0: michael@0: /* Set to 'x' in 'for (x ;... ;...)' or 'for (x in ...)'. */ michael@0: Node lhsNode; michael@0: michael@0: { michael@0: TokenKind tt = tokenStream.peekToken(TokenStream::Operand); michael@0: if (tt == TOK_SEMI) { michael@0: lhsNode = null(); michael@0: } else { michael@0: /* Set lhsNode to a var list or an initializing expression. */ michael@0: pc->parsingForInit = true; michael@0: if (tt == TOK_VAR) { michael@0: isForDecl = true; michael@0: tokenStream.consumeKnownToken(tt); michael@0: lhsNode = variables(tt == TOK_VAR ? PNK_VAR : PNK_CONST, &simpleForDecl); michael@0: } michael@0: else if (tt == TOK_CONST || tt == TOK_LET) { michael@0: JS_ALWAYS_FALSE(abortIfSyntaxParser()); michael@0: return null(); michael@0: } michael@0: else { michael@0: lhsNode = expr(); michael@0: } michael@0: if (!lhsNode) michael@0: return null(); michael@0: pc->parsingForInit = false; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * We can be sure that it's a for/in loop if there's still an 'in' michael@0: * keyword here, even if JavaScript recognizes 'in' as an operator, michael@0: * as we've excluded 'in' from being parsed in RelExpr by setting michael@0: * pc->parsingForInit. michael@0: */ michael@0: bool isForOf; michael@0: if (lhsNode && matchInOrOf(&isForOf)) { michael@0: /* Parse the rest of the for/in or for/of head. */ michael@0: forStmt.type = isForOf ? STMT_FOR_OF_LOOP : STMT_FOR_IN_LOOP; michael@0: michael@0: /* Check that the left side of the 'in' or 'of' is valid. */ michael@0: if (!isForDecl && michael@0: lhsNode != SyntaxParseHandler::NodeName && michael@0: lhsNode != SyntaxParseHandler::NodeGetProp && michael@0: lhsNode != SyntaxParseHandler::NodeLValue) michael@0: { michael@0: JS_ALWAYS_FALSE(abortIfSyntaxParser()); michael@0: return null(); michael@0: } michael@0: michael@0: if (!simpleForDecl) { michael@0: JS_ALWAYS_FALSE(abortIfSyntaxParser()); michael@0: return null(); michael@0: } michael@0: michael@0: if (!isForDecl && !checkAndMarkAsAssignmentLhs(lhsNode, PlainAssignment)) michael@0: return null(); michael@0: michael@0: if (!expr()) michael@0: return null(); michael@0: } else { michael@0: /* Parse the loop condition or null. */ michael@0: MUST_MATCH_TOKEN(TOK_SEMI, JSMSG_SEMI_AFTER_FOR_INIT); michael@0: if (tokenStream.peekToken(TokenStream::Operand) != TOK_SEMI) { michael@0: if (!expr()) michael@0: return null(); michael@0: } michael@0: michael@0: /* Parse the update expression or null. */ michael@0: MUST_MATCH_TOKEN(TOK_SEMI, JSMSG_SEMI_AFTER_FOR_COND); michael@0: if (tokenStream.peekToken(TokenStream::Operand) != TOK_RP) { michael@0: if (!expr()) michael@0: return null(); michael@0: } michael@0: } michael@0: michael@0: MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL); michael@0: michael@0: /* Parse the loop body. */ michael@0: if (!statement()) michael@0: return null(); michael@0: michael@0: PopStatementPC(tokenStream, pc); michael@0: return SyntaxParseHandler::NodeGeneric; michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::switchStatement() michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_SWITCH)); michael@0: uint32_t begin = pos().begin; michael@0: michael@0: MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_SWITCH); michael@0: michael@0: Node discriminant = exprInParens(); michael@0: if (!discriminant) michael@0: return null(); michael@0: michael@0: MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_SWITCH); michael@0: MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_SWITCH); michael@0: michael@0: StmtInfoPC stmtInfo(context); michael@0: PushStatementPC(pc, &stmtInfo, STMT_SWITCH); michael@0: michael@0: if (!GenerateBlockId(tokenStream, pc, pc->topStmt->blockid)) michael@0: return null(); michael@0: michael@0: Node caseList = handler.newStatementList(pc->blockid(), pos()); michael@0: if (!caseList) michael@0: return null(); michael@0: michael@0: Node saveBlock = pc->blockNode; michael@0: pc->blockNode = caseList; michael@0: michael@0: bool seenDefault = false; michael@0: TokenKind tt; michael@0: while ((tt = tokenStream.getToken()) != TOK_RC) { michael@0: uint32_t caseBegin = pos().begin; michael@0: michael@0: Node caseExpr; michael@0: switch (tt) { michael@0: case TOK_DEFAULT: michael@0: if (seenDefault) { michael@0: report(ParseError, false, null(), JSMSG_TOO_MANY_DEFAULTS); michael@0: return null(); michael@0: } michael@0: seenDefault = true; michael@0: caseExpr = null(); // The default case has pn_left == nullptr. michael@0: break; michael@0: michael@0: case TOK_CASE: michael@0: caseExpr = expr(); michael@0: if (!caseExpr) michael@0: return null(); michael@0: break; michael@0: michael@0: case TOK_ERROR: michael@0: return null(); michael@0: michael@0: default: michael@0: report(ParseError, false, null(), JSMSG_BAD_SWITCH); michael@0: return null(); michael@0: } michael@0: michael@0: MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_AFTER_CASE); michael@0: michael@0: Node body = handler.newStatementList(pc->blockid(), pos()); michael@0: if (!body) michael@0: return null(); michael@0: michael@0: while ((tt = tokenStream.peekToken(TokenStream::Operand)) != TOK_RC && michael@0: tt != TOK_CASE && tt != TOK_DEFAULT) { michael@0: if (tt == TOK_ERROR) michael@0: return null(); michael@0: Node stmt = statement(); michael@0: if (!stmt) michael@0: return null(); michael@0: handler.addList(body, stmt); michael@0: } michael@0: michael@0: Node casepn = handler.newCaseOrDefault(caseBegin, caseExpr, body); michael@0: if (!casepn) michael@0: return null(); michael@0: handler.addList(caseList, casepn); michael@0: } michael@0: michael@0: /* michael@0: * Handle the case where there was a let declaration in any case in michael@0: * the switch body, but not within an inner block. If it replaced michael@0: * pc->blockNode with a new block node then we must refresh caseList and michael@0: * then restore pc->blockNode. michael@0: */ michael@0: if (pc->blockNode != caseList) michael@0: caseList = pc->blockNode; michael@0: pc->blockNode = saveBlock; michael@0: michael@0: PopStatementPC(tokenStream, pc); michael@0: michael@0: handler.setEndPosition(caseList, pos().end); michael@0: michael@0: return handler.newSwitchStatement(begin, discriminant, caseList); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::continueStatement() michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_CONTINUE)); michael@0: uint32_t begin = pos().begin; michael@0: michael@0: RootedPropertyName label(context); michael@0: if (!matchLabel(&label)) michael@0: return null(); michael@0: michael@0: StmtInfoPC *stmt = pc->topStmt; michael@0: if (label) { michael@0: for (StmtInfoPC *stmt2 = nullptr; ; stmt = stmt->down) { michael@0: if (!stmt) { michael@0: report(ParseError, false, null(), JSMSG_LABEL_NOT_FOUND); michael@0: return null(); michael@0: } michael@0: if (stmt->type == STMT_LABEL) { michael@0: if (stmt->label == label) { michael@0: if (!stmt2 || !stmt2->isLoop()) { michael@0: report(ParseError, false, null(), JSMSG_BAD_CONTINUE); michael@0: return null(); michael@0: } michael@0: break; michael@0: } michael@0: } else { michael@0: stmt2 = stmt; michael@0: } michael@0: } michael@0: } else { michael@0: for (; ; stmt = stmt->down) { michael@0: if (!stmt) { michael@0: report(ParseError, false, null(), JSMSG_BAD_CONTINUE); michael@0: return null(); michael@0: } michael@0: if (stmt->isLoop()) michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (!MatchOrInsertSemicolon(tokenStream)) michael@0: return null(); michael@0: michael@0: return handler.newContinueStatement(label, TokenPos(begin, pos().end)); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::breakStatement() michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_BREAK)); michael@0: uint32_t begin = pos().begin; michael@0: michael@0: RootedPropertyName label(context); michael@0: if (!matchLabel(&label)) michael@0: return null(); michael@0: StmtInfoPC *stmt = pc->topStmt; michael@0: if (label) { michael@0: for (; ; stmt = stmt->down) { michael@0: if (!stmt) { michael@0: report(ParseError, false, null(), JSMSG_LABEL_NOT_FOUND); michael@0: return null(); michael@0: } michael@0: if (stmt->type == STMT_LABEL && stmt->label == label) michael@0: break; michael@0: } michael@0: } else { michael@0: for (; ; stmt = stmt->down) { michael@0: if (!stmt) { michael@0: report(ParseError, false, null(), JSMSG_TOUGH_BREAK); michael@0: return null(); michael@0: } michael@0: if (stmt->isLoop() || stmt->type == STMT_SWITCH) michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (!MatchOrInsertSemicolon(tokenStream)) michael@0: return null(); michael@0: michael@0: return handler.newBreakStatement(label, TokenPos(begin, pos().end)); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::returnStatement() michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_RETURN)); michael@0: uint32_t begin = pos().begin; michael@0: michael@0: if (!pc->sc->isFunctionBox()) { michael@0: report(ParseError, false, null(), JSMSG_BAD_RETURN_OR_YIELD, js_return_str); michael@0: return null(); michael@0: } michael@0: michael@0: // Parse an optional operand. michael@0: // michael@0: // This is ugly, but we don't want to require a semicolon. michael@0: Node exprNode; michael@0: switch (tokenStream.peekTokenSameLine(TokenStream::Operand)) { michael@0: case TOK_ERROR: michael@0: return null(); michael@0: case TOK_EOF: michael@0: case TOK_EOL: michael@0: case TOK_SEMI: michael@0: case TOK_RC: michael@0: exprNode = null(); michael@0: pc->funHasReturnVoid = true; michael@0: break; michael@0: default: { michael@0: exprNode = expr(); michael@0: if (!exprNode) michael@0: return null(); michael@0: pc->funHasReturnExpr = true; michael@0: } michael@0: } michael@0: michael@0: if (!MatchOrInsertSemicolon(tokenStream)) michael@0: return null(); michael@0: michael@0: Node pn = handler.newReturnStatement(exprNode, TokenPos(begin, pos().end)); michael@0: if (!pn) michael@0: return null(); michael@0: michael@0: if (options().extraWarningsOption && pc->funHasReturnExpr && pc->funHasReturnVoid && michael@0: !reportBadReturn(pn, ParseExtraWarning, michael@0: JSMSG_NO_RETURN_VALUE, JSMSG_ANON_NO_RETURN_VALUE)) michael@0: { michael@0: return null(); michael@0: } michael@0: michael@0: if (pc->isLegacyGenerator() && exprNode) { michael@0: /* Disallow "return v;" in legacy generators. */ michael@0: reportBadReturn(pn, ParseError, JSMSG_BAD_GENERATOR_RETURN, michael@0: JSMSG_BAD_ANON_GENERATOR_RETURN); michael@0: return null(); michael@0: } michael@0: michael@0: return pn; michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::yieldExpression() michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_YIELD)); michael@0: uint32_t begin = pos().begin; michael@0: michael@0: switch (pc->generatorKind()) { michael@0: case StarGenerator: michael@0: { michael@0: JS_ASSERT(pc->sc->isFunctionBox()); michael@0: michael@0: pc->lastYieldOffset = begin; michael@0: michael@0: ParseNodeKind kind = tokenStream.matchToken(TOK_MUL) ? PNK_YIELD_STAR : PNK_YIELD; michael@0: michael@0: // ES6 generators require a value. michael@0: Node exprNode = assignExpr(); michael@0: if (!exprNode) michael@0: return null(); michael@0: michael@0: return handler.newUnary(kind, JSOP_NOP, begin, exprNode); michael@0: } michael@0: michael@0: case NotGenerator: michael@0: // We are in code that has not seen a yield, but we are in JS 1.7 or michael@0: // later. Try to transition to being a legacy generator. michael@0: JS_ASSERT(tokenStream.versionNumber() >= JSVERSION_1_7); michael@0: JS_ASSERT(pc->lastYieldOffset == ParseContext::NoYieldOffset); michael@0: michael@0: if (!abortIfSyntaxParser()) michael@0: return null(); michael@0: michael@0: if (!pc->sc->isFunctionBox()) { michael@0: report(ParseError, false, null(), JSMSG_BAD_RETURN_OR_YIELD, js_yield_str); michael@0: return null(); michael@0: } michael@0: michael@0: pc->sc->asFunctionBox()->setGeneratorKind(LegacyGenerator); michael@0: michael@0: if (pc->funHasReturnExpr) { michael@0: /* As in Python (see PEP-255), disallow return v; in generators. */ michael@0: reportBadReturn(null(), ParseError, JSMSG_BAD_GENERATOR_RETURN, michael@0: JSMSG_BAD_ANON_GENERATOR_RETURN); michael@0: return null(); michael@0: } michael@0: // Fall through. michael@0: michael@0: case LegacyGenerator: michael@0: { michael@0: // We are in a legacy generator: a function that has already seen a michael@0: // yield, or in a legacy generator comprehension. michael@0: JS_ASSERT(pc->sc->isFunctionBox()); michael@0: michael@0: pc->lastYieldOffset = begin; michael@0: michael@0: // Legacy generators do not require a value. michael@0: Node exprNode; michael@0: switch (tokenStream.peekTokenSameLine(TokenStream::Operand)) { michael@0: case TOK_ERROR: michael@0: return null(); michael@0: case TOK_EOF: michael@0: case TOK_EOL: michael@0: case TOK_SEMI: michael@0: case TOK_RC: michael@0: case TOK_RB: michael@0: case TOK_RP: michael@0: case TOK_COLON: michael@0: case TOK_COMMA: michael@0: // No value. michael@0: exprNode = null(); michael@0: // ES6 does not permit yield without an operand. We should michael@0: // encourage users of yield expressions of this kind to pass an michael@0: // operand, to bring users closer to standard syntax. michael@0: if (!reportWithOffset(ParseWarning, false, pos().begin, JSMSG_YIELD_WITHOUT_OPERAND)) michael@0: return null(); michael@0: break; michael@0: default: michael@0: exprNode = assignExpr(); michael@0: if (!exprNode) michael@0: return null(); michael@0: } michael@0: michael@0: return handler.newUnary(PNK_YIELD, JSOP_NOP, begin, exprNode); michael@0: } michael@0: } michael@0: michael@0: MOZ_ASSUME_UNREACHABLE("yieldExpr"); michael@0: } michael@0: michael@0: template <> michael@0: ParseNode * michael@0: Parser::withStatement() michael@0: { michael@0: // test262/ch12/12.10/12.10-0-1.js fails if we try to parse with-statements michael@0: // in syntax-parse mode. See bug 892583. michael@0: if (handler.syntaxParser) { michael@0: handler.disableSyntaxParser(); michael@0: abortedSyntaxParse = true; michael@0: return null(); michael@0: } michael@0: michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_WITH)); michael@0: uint32_t begin = pos().begin; michael@0: michael@0: // In most cases, we want the constructs forbidden in strict mode code to be michael@0: // a subset of those that JSOPTION_EXTRA_WARNINGS warns about, and we should michael@0: // use reportStrictModeError. However, 'with' is the sole instance of a michael@0: // construct that is forbidden in strict mode code, but doesn't even merit a michael@0: // warning under JSOPTION_EXTRA_WARNINGS. See michael@0: // https://bugzilla.mozilla.org/show_bug.cgi?id=514576#c1. michael@0: if (pc->sc->strict && !report(ParseStrictError, true, null(), JSMSG_STRICT_CODE_WITH)) michael@0: return null(); michael@0: michael@0: MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_WITH); michael@0: Node objectExpr = exprInParens(); michael@0: if (!objectExpr) michael@0: return null(); michael@0: MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_WITH); michael@0: michael@0: bool oldParsingWith = pc->parsingWith; michael@0: pc->parsingWith = true; michael@0: michael@0: StmtInfoPC stmtInfo(context); michael@0: PushStatementPC(pc, &stmtInfo, STMT_WITH); michael@0: Rooted staticWith(context, StaticWithObject::create(context)); michael@0: if (!staticWith) michael@0: return null(); michael@0: staticWith->initEnclosingNestedScopeFromParser(pc->staticScope); michael@0: FinishPushNestedScope(pc, &stmtInfo, *staticWith); michael@0: michael@0: Node innerBlock = statement(); michael@0: if (!innerBlock) michael@0: return null(); michael@0: michael@0: PopStatementPC(tokenStream, pc); michael@0: michael@0: pc->sc->setBindingsAccessedDynamically(); michael@0: pc->parsingWith = oldParsingWith; michael@0: michael@0: /* michael@0: * Make sure to deoptimize lexical dependencies inside the |with| michael@0: * to safely optimize binding globals (see bug 561923). michael@0: */ michael@0: for (AtomDefnRange r = pc->lexdeps->all(); !r.empty(); r.popFront()) { michael@0: DefinitionNode defn = r.front().value().get(); michael@0: DefinitionNode lexdep = handler.resolve(defn); michael@0: handler.deoptimizeUsesWithin(lexdep, TokenPos(begin, pos().begin)); michael@0: } michael@0: michael@0: ObjectBox *staticWithBox = newObjectBox(staticWith); michael@0: if (!staticWithBox) michael@0: return null(); michael@0: return handler.newWithStatement(begin, objectExpr, innerBlock, staticWithBox); michael@0: } michael@0: michael@0: template <> michael@0: SyntaxParseHandler::Node michael@0: Parser::withStatement() michael@0: { michael@0: JS_ALWAYS_FALSE(abortIfSyntaxParser()); michael@0: return null(); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::labeledStatement() michael@0: { michael@0: uint32_t begin = pos().begin; michael@0: RootedPropertyName label(context, tokenStream.currentName()); michael@0: for (StmtInfoPC *stmt = pc->topStmt; stmt; stmt = stmt->down) { michael@0: if (stmt->type == STMT_LABEL && stmt->label == label) { michael@0: report(ParseError, false, null(), JSMSG_DUPLICATE_LABEL); michael@0: return null(); michael@0: } michael@0: } michael@0: michael@0: tokenStream.consumeKnownToken(TOK_COLON); michael@0: michael@0: /* Push a label struct and parse the statement. */ michael@0: StmtInfoPC stmtInfo(context); michael@0: PushStatementPC(pc, &stmtInfo, STMT_LABEL); michael@0: stmtInfo.label = label; michael@0: Node pn = statement(); michael@0: if (!pn) michael@0: return null(); michael@0: michael@0: /* Pop the label, set pn_expr, and return early. */ michael@0: PopStatementPC(tokenStream, pc); michael@0: michael@0: return handler.newLabeledStatement(label, pn, begin); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::throwStatement() michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_THROW)); michael@0: uint32_t begin = pos().begin; michael@0: michael@0: /* ECMA-262 Edition 3 says 'throw [no LineTerminator here] Expr'. */ michael@0: TokenKind tt = tokenStream.peekTokenSameLine(TokenStream::Operand); michael@0: if (tt == TOK_ERROR) michael@0: return null(); michael@0: if (tt == TOK_EOF || tt == TOK_EOL || tt == TOK_SEMI || tt == TOK_RC) { michael@0: report(ParseError, false, null(), JSMSG_SYNTAX_ERROR); michael@0: return null(); michael@0: } michael@0: michael@0: Node throwExpr = expr(); michael@0: if (!throwExpr) michael@0: return null(); michael@0: michael@0: if (!MatchOrInsertSemicolon(tokenStream)) michael@0: return null(); michael@0: michael@0: return handler.newThrowStatement(throwExpr, TokenPos(begin, pos().end)); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::tryStatement() michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_TRY)); michael@0: uint32_t begin = pos().begin; michael@0: michael@0: /* michael@0: * try nodes are ternary. michael@0: * kid1 is the try statement michael@0: * kid2 is the catch node list or null michael@0: * kid3 is the finally statement michael@0: * michael@0: * catch nodes are ternary. michael@0: * kid1 is the lvalue (TOK_NAME, TOK_LB, or TOK_LC) michael@0: * kid2 is the catch guard or null if no guard michael@0: * kid3 is the catch block michael@0: * michael@0: * catch lvalue nodes are either: michael@0: * TOK_NAME for a single identifier michael@0: * TOK_RB or TOK_RC for a destructuring left-hand side michael@0: * michael@0: * finally nodes are TOK_LC statement lists. michael@0: */ michael@0: michael@0: MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_TRY); michael@0: StmtInfoPC stmtInfo(context); michael@0: if (!PushBlocklikeStatement(tokenStream, &stmtInfo, STMT_TRY, pc)) michael@0: return null(); michael@0: Node innerBlock = statements(); michael@0: if (!innerBlock) michael@0: return null(); michael@0: MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_TRY); michael@0: PopStatementPC(tokenStream, pc); michael@0: michael@0: bool hasUnconditionalCatch = false; michael@0: Node catchList = null(); michael@0: TokenKind tt = tokenStream.getToken(); michael@0: if (tt == TOK_CATCH) { michael@0: catchList = handler.newList(PNK_CATCH); michael@0: if (!catchList) michael@0: return null(); michael@0: michael@0: do { michael@0: Node pnblock; michael@0: BindData data(context); michael@0: michael@0: /* Check for another catch after unconditional catch. */ michael@0: if (hasUnconditionalCatch) { michael@0: report(ParseError, false, null(), JSMSG_CATCH_AFTER_GENERAL); michael@0: return null(); michael@0: } michael@0: michael@0: /* michael@0: * Create a lexical scope node around the whole catch clause, michael@0: * including the head. michael@0: */ michael@0: pnblock = pushLexicalScope(&stmtInfo); michael@0: if (!pnblock) michael@0: return null(); michael@0: stmtInfo.type = STMT_CATCH; michael@0: michael@0: /* michael@0: * Legal catch forms are: michael@0: * catch (lhs) michael@0: * catch (lhs if ) michael@0: * where lhs is a name or a destructuring left-hand side. michael@0: * (the latter is legal only #ifdef JS_HAS_CATCH_GUARD) michael@0: */ michael@0: MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_CATCH); michael@0: michael@0: /* michael@0: * Contrary to ECMA Ed. 3, the catch variable is lexically michael@0: * scoped, not a property of a new Object instance. This is michael@0: * an intentional change that anticipates ECMA Ed. 4. michael@0: */ michael@0: data.initLet(HoistVars, pc->staticScope->template as(), michael@0: JSMSG_TOO_MANY_CATCH_VARS); michael@0: JS_ASSERT(data.let.blockObj); michael@0: michael@0: tt = tokenStream.getToken(); michael@0: Node catchName; michael@0: switch (tt) { michael@0: case TOK_LB: michael@0: case TOK_LC: michael@0: catchName = destructuringExpr(&data, tt); michael@0: if (!catchName) michael@0: return null(); michael@0: break; michael@0: michael@0: case TOK_YIELD: michael@0: if (!checkYieldNameValidity()) michael@0: return null(); michael@0: // Fall through. michael@0: case TOK_NAME: michael@0: { michael@0: RootedPropertyName label(context, tokenStream.currentName()); michael@0: catchName = newBindingNode(label, false); michael@0: if (!catchName) michael@0: return null(); michael@0: data.pn = catchName; michael@0: if (!data.binder(&data, label, this)) michael@0: return null(); michael@0: break; michael@0: } michael@0: michael@0: default: michael@0: report(ParseError, false, null(), JSMSG_CATCH_IDENTIFIER); michael@0: return null(); michael@0: } michael@0: michael@0: Node catchGuard = null(); michael@0: #if JS_HAS_CATCH_GUARD michael@0: /* michael@0: * We use 'catch (x if x === 5)' (not 'catch (x : x === 5)') michael@0: * to avoid conflicting with the JS2/ECMAv4 type annotation michael@0: * catchguard syntax. michael@0: */ michael@0: if (tokenStream.matchToken(TOK_IF)) { michael@0: catchGuard = expr(); michael@0: if (!catchGuard) michael@0: return null(); michael@0: } michael@0: #endif michael@0: MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_CATCH); michael@0: michael@0: MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CATCH); michael@0: Node catchBody = statements(); michael@0: if (!catchBody) michael@0: return null(); michael@0: MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_CATCH); michael@0: PopStatementPC(tokenStream, pc); michael@0: michael@0: if (!catchGuard) michael@0: hasUnconditionalCatch = true; michael@0: michael@0: if (!handler.addCatchBlock(catchList, pnblock, catchName, catchGuard, catchBody)) michael@0: return null(); michael@0: handler.setEndPosition(catchList, pos().end); michael@0: handler.setEndPosition(pnblock, pos().end); michael@0: michael@0: tt = tokenStream.getToken(TokenStream::Operand); michael@0: } while (tt == TOK_CATCH); michael@0: } michael@0: michael@0: Node finallyBlock = null(); michael@0: michael@0: if (tt == TOK_FINALLY) { michael@0: MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_FINALLY); michael@0: if (!PushBlocklikeStatement(tokenStream, &stmtInfo, STMT_FINALLY, pc)) michael@0: return null(); michael@0: finallyBlock = statements(); michael@0: if (!finallyBlock) michael@0: return null(); michael@0: MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_FINALLY); michael@0: PopStatementPC(tokenStream, pc); michael@0: } else { michael@0: tokenStream.ungetToken(); michael@0: } michael@0: if (!catchList && !finallyBlock) { michael@0: report(ParseError, false, null(), JSMSG_CATCH_OR_FINALLY); michael@0: return null(); michael@0: } michael@0: michael@0: return handler.newTryStatement(begin, innerBlock, catchList, finallyBlock); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::debuggerStatement() michael@0: { michael@0: TokenPos p; michael@0: p.begin = pos().begin; michael@0: if (!MatchOrInsertSemicolon(tokenStream)) michael@0: return null(); michael@0: p.end = pos().end; michael@0: michael@0: pc->sc->setBindingsAccessedDynamically(); michael@0: pc->sc->setHasDebuggerStatement(); michael@0: michael@0: return handler.newDebuggerStatement(p); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::statement(bool canHaveDirectives) michael@0: { michael@0: JS_CHECK_RECURSION(context, return null()); michael@0: michael@0: switch (TokenKind tt = tokenStream.getToken(TokenStream::Operand)) { michael@0: case TOK_LC: michael@0: return blockStatement(); michael@0: michael@0: case TOK_CONST: michael@0: if (!abortIfSyntaxParser()) michael@0: return null(); michael@0: // FALL THROUGH michael@0: case TOK_VAR: { michael@0: Node pn = variables(tt == TOK_CONST ? PNK_CONST : PNK_VAR); michael@0: if (!pn) michael@0: return null(); michael@0: michael@0: // Tell js_EmitTree to generate a final POP. michael@0: handler.setListFlag(pn, PNX_POPVAR); michael@0: michael@0: if (!MatchOrInsertSemicolon(tokenStream)) michael@0: return null(); michael@0: return pn; michael@0: } michael@0: michael@0: case TOK_LET: michael@0: return letStatement(); michael@0: case TOK_IMPORT: michael@0: return importDeclaration(); michael@0: case TOK_EXPORT: michael@0: return exportDeclaration(); michael@0: case TOK_SEMI: michael@0: return handler.newEmptyStatement(pos()); michael@0: case TOK_IF: michael@0: return ifStatement(); michael@0: case TOK_DO: michael@0: return doWhileStatement(); michael@0: case TOK_WHILE: michael@0: return whileStatement(); michael@0: case TOK_FOR: michael@0: return forStatement(); michael@0: case TOK_SWITCH: michael@0: return switchStatement(); michael@0: case TOK_CONTINUE: michael@0: return continueStatement(); michael@0: case TOK_BREAK: michael@0: return breakStatement(); michael@0: case TOK_RETURN: michael@0: return returnStatement(); michael@0: case TOK_WITH: michael@0: return withStatement(); michael@0: case TOK_THROW: michael@0: return throwStatement(); michael@0: case TOK_TRY: michael@0: return tryStatement(); michael@0: case TOK_FUNCTION: michael@0: return functionStmt(); michael@0: case TOK_DEBUGGER: michael@0: return debuggerStatement(); michael@0: michael@0: /* TOK_CATCH and TOK_FINALLY are both handled in the TOK_TRY case */ michael@0: case TOK_CATCH: michael@0: report(ParseError, false, null(), JSMSG_CATCH_WITHOUT_TRY); michael@0: return null(); michael@0: michael@0: case TOK_FINALLY: michael@0: report(ParseError, false, null(), JSMSG_FINALLY_WITHOUT_TRY); michael@0: return null(); michael@0: michael@0: case TOK_ERROR: michael@0: return null(); michael@0: michael@0: case TOK_STRING: michael@0: if (!canHaveDirectives && tokenStream.currentToken().atom() == context->names().useAsm) { michael@0: if (!abortIfSyntaxParser()) michael@0: return null(); michael@0: if (!report(ParseWarning, false, null(), JSMSG_USE_ASM_DIRECTIVE_FAIL)) michael@0: return null(); michael@0: } michael@0: return expressionStatement(); michael@0: michael@0: case TOK_YIELD: michael@0: if (tokenStream.peekToken() == TOK_COLON) { michael@0: if (!checkYieldNameValidity()) michael@0: return null(); michael@0: return labeledStatement(); michael@0: } michael@0: return expressionStatement(); michael@0: michael@0: case TOK_NAME: michael@0: if (tokenStream.peekToken() == TOK_COLON) michael@0: return labeledStatement(); michael@0: return expressionStatement(); michael@0: michael@0: default: michael@0: return expressionStatement(); michael@0: } michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::expr() michael@0: { michael@0: Node pn = assignExpr(); michael@0: if (pn && tokenStream.matchToken(TOK_COMMA)) { michael@0: Node seq = handler.newList(PNK_COMMA, pn); michael@0: if (!seq) michael@0: return null(); michael@0: do { michael@0: if (handler.isUnparenthesizedYield(pn)) { michael@0: report(ParseError, false, pn, JSMSG_BAD_GENERATOR_SYNTAX, js_yield_str); michael@0: return null(); michael@0: } michael@0: michael@0: pn = assignExpr(); michael@0: if (!pn) michael@0: return null(); michael@0: handler.addList(seq, pn); michael@0: } while (tokenStream.matchToken(TOK_COMMA)); michael@0: return seq; michael@0: } michael@0: return pn; michael@0: } michael@0: michael@0: static const JSOp ParseNodeKindToJSOp[] = { michael@0: JSOP_OR, michael@0: JSOP_AND, michael@0: JSOP_BITOR, michael@0: JSOP_BITXOR, michael@0: JSOP_BITAND, michael@0: JSOP_STRICTEQ, michael@0: JSOP_EQ, michael@0: JSOP_STRICTNE, michael@0: JSOP_NE, michael@0: JSOP_LT, michael@0: JSOP_LE, michael@0: JSOP_GT, michael@0: JSOP_GE, michael@0: JSOP_INSTANCEOF, michael@0: JSOP_IN, michael@0: JSOP_LSH, michael@0: JSOP_RSH, michael@0: JSOP_URSH, michael@0: JSOP_ADD, michael@0: JSOP_SUB, michael@0: JSOP_MUL, michael@0: JSOP_DIV, michael@0: JSOP_MOD michael@0: }; michael@0: michael@0: static inline JSOp michael@0: BinaryOpParseNodeKindToJSOp(ParseNodeKind pnk) michael@0: { michael@0: JS_ASSERT(pnk >= PNK_BINOP_FIRST); michael@0: JS_ASSERT(pnk <= PNK_BINOP_LAST); michael@0: return ParseNodeKindToJSOp[pnk - PNK_BINOP_FIRST]; michael@0: } michael@0: michael@0: static bool michael@0: IsBinaryOpToken(TokenKind tok, bool parsingForInit) michael@0: { michael@0: return tok == TOK_IN ? !parsingForInit : TokenKindIsBinaryOp(tok); michael@0: } michael@0: michael@0: static ParseNodeKind michael@0: BinaryOpTokenKindToParseNodeKind(TokenKind tok) michael@0: { michael@0: JS_ASSERT(TokenKindIsBinaryOp(tok)); michael@0: return ParseNodeKind(PNK_BINOP_FIRST + (tok - TOK_BINOP_FIRST)); michael@0: } michael@0: michael@0: static const int PrecedenceTable[] = { michael@0: 1, /* PNK_OR */ michael@0: 2, /* PNK_AND */ michael@0: 3, /* PNK_BITOR */ michael@0: 4, /* PNK_BITXOR */ michael@0: 5, /* PNK_BITAND */ michael@0: 6, /* PNK_STRICTEQ */ michael@0: 6, /* PNK_EQ */ michael@0: 6, /* PNK_STRICTNE */ michael@0: 6, /* PNK_NE */ michael@0: 7, /* PNK_LT */ michael@0: 7, /* PNK_LE */ michael@0: 7, /* PNK_GT */ michael@0: 7, /* PNK_GE */ michael@0: 7, /* PNK_INSTANCEOF */ michael@0: 7, /* PNK_IN */ michael@0: 8, /* PNK_LSH */ michael@0: 8, /* PNK_RSH */ michael@0: 8, /* PNK_URSH */ michael@0: 9, /* PNK_ADD */ michael@0: 9, /* PNK_SUB */ michael@0: 10, /* PNK_STAR */ michael@0: 10, /* PNK_DIV */ michael@0: 10 /* PNK_MOD */ michael@0: }; michael@0: michael@0: static const int PRECEDENCE_CLASSES = 10; michael@0: michael@0: static int michael@0: Precedence(ParseNodeKind pnk) { michael@0: // Everything binds tighter than PNK_LIMIT, because we want to reduce all michael@0: // nodes to a single node when we reach a token that is not another binary michael@0: // operator. michael@0: if (pnk == PNK_LIMIT) michael@0: return 0; michael@0: michael@0: JS_ASSERT(pnk >= PNK_BINOP_FIRST); michael@0: JS_ASSERT(pnk <= PNK_BINOP_LAST); michael@0: return PrecedenceTable[pnk - PNK_BINOP_FIRST]; michael@0: } michael@0: michael@0: template michael@0: MOZ_ALWAYS_INLINE typename ParseHandler::Node michael@0: Parser::orExpr1() michael@0: { michael@0: // Shift-reduce parser for the left-associative binary operator part of michael@0: // the JS syntax. michael@0: michael@0: // Conceptually there's just one stack, a stack of pairs (lhs, op). michael@0: // It's implemented using two separate arrays, though. michael@0: Node nodeStack[PRECEDENCE_CLASSES]; michael@0: ParseNodeKind kindStack[PRECEDENCE_CLASSES]; michael@0: int depth = 0; michael@0: michael@0: bool oldParsingForInit = pc->parsingForInit; michael@0: pc->parsingForInit = false; michael@0: michael@0: Node pn; michael@0: for (;;) { michael@0: pn = unaryExpr(); michael@0: if (!pn) michael@0: return pn; michael@0: michael@0: // If a binary operator follows, consume it and compute the michael@0: // corresponding operator. michael@0: TokenKind tok = tokenStream.getToken(); michael@0: if (tok == TOK_ERROR) michael@0: return null(); michael@0: ParseNodeKind pnk; michael@0: if (IsBinaryOpToken(tok, oldParsingForInit)) { michael@0: pnk = BinaryOpTokenKindToParseNodeKind(tok); michael@0: } else { michael@0: tok = TOK_EOF; michael@0: pnk = PNK_LIMIT; michael@0: } michael@0: michael@0: // If pnk has precedence less than or equal to another operator on the michael@0: // stack, reduce. This combines nodes on the stack until we form the michael@0: // actual lhs of pnk. michael@0: // michael@0: // The >= in this condition works because all the operators in question michael@0: // are left-associative; if any were not, the case where two operators michael@0: // have equal precedence would need to be handled specially, and the michael@0: // stack would need to be a Vector. michael@0: while (depth > 0 && Precedence(kindStack[depth - 1]) >= Precedence(pnk)) { michael@0: depth--; michael@0: ParseNodeKind combiningPnk = kindStack[depth]; michael@0: JSOp combiningOp = BinaryOpParseNodeKindToJSOp(combiningPnk); michael@0: pn = handler.newBinaryOrAppend(combiningPnk, nodeStack[depth], pn, pc, combiningOp); michael@0: if (!pn) michael@0: return pn; michael@0: } michael@0: michael@0: if (pnk == PNK_LIMIT) michael@0: break; michael@0: michael@0: nodeStack[depth] = pn; michael@0: kindStack[depth] = pnk; michael@0: depth++; michael@0: JS_ASSERT(depth <= PRECEDENCE_CLASSES); michael@0: } michael@0: michael@0: JS_ASSERT(depth == 0); michael@0: pc->parsingForInit = oldParsingForInit; michael@0: return pn; michael@0: } michael@0: michael@0: template michael@0: MOZ_ALWAYS_INLINE typename ParseHandler::Node michael@0: Parser::condExpr1() michael@0: { michael@0: Node condition = orExpr1(); michael@0: if (!condition || !tokenStream.isCurrentTokenType(TOK_HOOK)) michael@0: return condition; michael@0: michael@0: /* michael@0: * Always accept the 'in' operator in the middle clause of a ternary, michael@0: * where it's unambiguous, even if we might be parsing the init of a michael@0: * for statement. michael@0: */ michael@0: bool oldParsingForInit = pc->parsingForInit; michael@0: pc->parsingForInit = false; michael@0: Node thenExpr = assignExpr(); michael@0: pc->parsingForInit = oldParsingForInit; michael@0: if (!thenExpr) michael@0: return null(); michael@0: michael@0: MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_IN_COND); michael@0: michael@0: Node elseExpr = assignExpr(); michael@0: if (!elseExpr) michael@0: return null(); michael@0: michael@0: tokenStream.getToken(); /* read one token past the end */ michael@0: return handler.newConditional(condition, thenExpr, elseExpr); michael@0: } michael@0: michael@0: template <> michael@0: bool michael@0: Parser::checkAndMarkAsAssignmentLhs(ParseNode *pn, AssignmentFlavor flavor) michael@0: { michael@0: switch (pn->getKind()) { michael@0: case PNK_NAME: michael@0: if (!checkStrictAssignment(pn, flavor)) michael@0: return false; michael@0: if (flavor == KeyedDestructuringAssignment) { michael@0: /* michael@0: * We may be called on a name node that has already been michael@0: * specialized, in the very weird "for (var [x] = i in o) ..." michael@0: * case. See bug 558633. michael@0: */ michael@0: if (!(js_CodeSpec[pn->getOp()].format & JOF_SET)) michael@0: pn->setOp(JSOP_SETNAME); michael@0: } else { michael@0: pn->setOp(pn->isOp(JSOP_GETLOCAL) ? JSOP_SETLOCAL : JSOP_SETNAME); michael@0: } michael@0: pn->markAsAssigned(); michael@0: break; michael@0: michael@0: case PNK_DOT: michael@0: case PNK_ELEM: michael@0: break; michael@0: michael@0: case PNK_ARRAY: michael@0: case PNK_OBJECT: michael@0: if (flavor == CompoundAssignment) { michael@0: report(ParseError, false, null(), JSMSG_BAD_DESTRUCT_ASS); michael@0: return false; michael@0: } michael@0: if (!checkDestructuring(nullptr, pn)) michael@0: return false; michael@0: break; michael@0: michael@0: case PNK_CALL: michael@0: if (!makeSetCall(pn, JSMSG_BAD_LEFTSIDE_OF_ASS)) michael@0: return false; michael@0: break; michael@0: michael@0: default: michael@0: report(ParseError, false, pn, JSMSG_BAD_LEFTSIDE_OF_ASS); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: template <> michael@0: bool michael@0: Parser::checkAndMarkAsAssignmentLhs(Node pn, AssignmentFlavor flavor) michael@0: { michael@0: /* Full syntax checking of valid assignment LHS terms requires a parse tree. */ michael@0: if (pn != SyntaxParseHandler::NodeName && michael@0: pn != SyntaxParseHandler::NodeGetProp && michael@0: pn != SyntaxParseHandler::NodeLValue) michael@0: { michael@0: return abortIfSyntaxParser(); michael@0: } michael@0: return checkStrictAssignment(pn, flavor); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::assignExpr() michael@0: { michael@0: JS_CHECK_RECURSION(context, return null()); michael@0: michael@0: // It's very common at this point to have a "detectably simple" expression, michael@0: // i.e. a name/number/string token followed by one of the following tokens michael@0: // that obviously isn't part of an expression: , ; : ) ] } michael@0: // michael@0: // (In Parsemark this happens 81.4% of the time; in code with large michael@0: // numeric arrays, such as some Kraken benchmarks, it happens more often.) michael@0: // michael@0: // In such cases, we can avoid the full expression parsing route through michael@0: // assignExpr(), condExpr1(), orExpr1(), unaryExpr(), memberExpr(), and michael@0: // primaryExpr(). michael@0: michael@0: TokenKind tt = tokenStream.getToken(TokenStream::Operand); michael@0: michael@0: if (tt == TOK_NAME && tokenStream.nextTokenEndsExpr()) michael@0: return identifierName(); michael@0: michael@0: if (tt == TOK_NUMBER && tokenStream.nextTokenEndsExpr()) michael@0: return newNumber(tokenStream.currentToken()); michael@0: michael@0: if (tt == TOK_STRING && tokenStream.nextTokenEndsExpr()) michael@0: return stringLiteral(); michael@0: michael@0: if (tt == TOK_YIELD && (versionNumber() >= JSVERSION_1_7 || pc->isGenerator())) michael@0: return yieldExpression(); michael@0: michael@0: tokenStream.ungetToken(); michael@0: michael@0: // Save the tokenizer state in case we find an arrow function and have to michael@0: // rewind. michael@0: TokenStream::Position start(keepAtoms); michael@0: tokenStream.tell(&start); michael@0: michael@0: Node lhs = condExpr1(); michael@0: if (!lhs) michael@0: return null(); michael@0: michael@0: ParseNodeKind kind; michael@0: JSOp op; michael@0: switch (tokenStream.currentToken().type) { michael@0: case TOK_ASSIGN: kind = PNK_ASSIGN; op = JSOP_NOP; break; michael@0: case TOK_ADDASSIGN: kind = PNK_ADDASSIGN; op = JSOP_ADD; break; michael@0: case TOK_SUBASSIGN: kind = PNK_SUBASSIGN; op = JSOP_SUB; break; michael@0: case TOK_BITORASSIGN: kind = PNK_BITORASSIGN; op = JSOP_BITOR; break; michael@0: case TOK_BITXORASSIGN: kind = PNK_BITXORASSIGN; op = JSOP_BITXOR; break; michael@0: case TOK_BITANDASSIGN: kind = PNK_BITANDASSIGN; op = JSOP_BITAND; break; michael@0: case TOK_LSHASSIGN: kind = PNK_LSHASSIGN; op = JSOP_LSH; break; michael@0: case TOK_RSHASSIGN: kind = PNK_RSHASSIGN; op = JSOP_RSH; break; michael@0: case TOK_URSHASSIGN: kind = PNK_URSHASSIGN; op = JSOP_URSH; break; michael@0: case TOK_MULASSIGN: kind = PNK_MULASSIGN; op = JSOP_MUL; break; michael@0: case TOK_DIVASSIGN: kind = PNK_DIVASSIGN; op = JSOP_DIV; break; michael@0: case TOK_MODASSIGN: kind = PNK_MODASSIGN; op = JSOP_MOD; break; michael@0: michael@0: case TOK_ARROW: { michael@0: tokenStream.seek(start); michael@0: if (!abortIfSyntaxParser()) michael@0: return null(); michael@0: michael@0: if (tokenStream.getToken() == TOK_ERROR) michael@0: return null(); michael@0: tokenStream.ungetToken(); michael@0: michael@0: return functionDef(NullPtr(), start, Normal, Arrow, NotGenerator); michael@0: } michael@0: michael@0: default: michael@0: JS_ASSERT(!tokenStream.isCurrentTokenAssignment()); michael@0: tokenStream.ungetToken(); michael@0: return lhs; michael@0: } michael@0: michael@0: AssignmentFlavor flavor = kind == PNK_ASSIGN ? PlainAssignment : CompoundAssignment; michael@0: if (!checkAndMarkAsAssignmentLhs(lhs, flavor)) michael@0: return null(); michael@0: michael@0: Node rhs = assignExpr(); michael@0: if (!rhs) michael@0: return null(); michael@0: michael@0: return handler.newBinaryOrAppend(kind, lhs, rhs, pc, op); michael@0: } michael@0: michael@0: static const char incop_name_str[][10] = {"increment", "decrement"}; michael@0: michael@0: template <> michael@0: bool michael@0: Parser::checkAndMarkAsIncOperand(ParseNode *kid, TokenKind tt, bool preorder) michael@0: { michael@0: // Check. michael@0: if (!kid->isKind(PNK_NAME) && michael@0: !kid->isKind(PNK_DOT) && michael@0: !kid->isKind(PNK_ELEM) && michael@0: !(kid->isKind(PNK_CALL) && michael@0: (kid->isOp(JSOP_CALL) || kid->isOp(JSOP_SPREADCALL) || michael@0: kid->isOp(JSOP_EVAL) || kid->isOp(JSOP_SPREADEVAL) || michael@0: kid->isOp(JSOP_FUNCALL) || michael@0: kid->isOp(JSOP_FUNAPPLY)))) michael@0: { michael@0: report(ParseError, false, null(), JSMSG_BAD_OPERAND, incop_name_str[tt == TOK_DEC]); michael@0: return false; michael@0: } michael@0: michael@0: if (!checkStrictAssignment(kid, IncDecAssignment)) michael@0: return false; michael@0: michael@0: // Mark. michael@0: if (kid->isKind(PNK_NAME)) { michael@0: kid->markAsAssigned(); michael@0: } else if (kid->isKind(PNK_CALL)) { michael@0: if (!makeSetCall(kid, JSMSG_BAD_INCOP_OPERAND)) michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: template <> michael@0: bool michael@0: Parser::checkAndMarkAsIncOperand(Node kid, TokenKind tt, bool preorder) michael@0: { michael@0: // To the extent of what we support in syntax-parse mode, the rules for michael@0: // inc/dec operands are the same as for assignment. There are differences, michael@0: // such as destructuring; but if we hit any of those cases, we'll abort and michael@0: // reparse in full mode. michael@0: return checkAndMarkAsAssignmentLhs(kid, IncDecAssignment); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::unaryOpExpr(ParseNodeKind kind, JSOp op, uint32_t begin) michael@0: { michael@0: Node kid = unaryExpr(); michael@0: if (!kid) michael@0: return null(); michael@0: return handler.newUnary(kind, op, begin, kid); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::unaryExpr() michael@0: { michael@0: Node pn, pn2; michael@0: michael@0: JS_CHECK_RECURSION(context, return null()); michael@0: michael@0: TokenKind tt = tokenStream.getToken(TokenStream::Operand); michael@0: uint32_t begin = pos().begin; michael@0: switch (tt) { michael@0: case TOK_TYPEOF: michael@0: return unaryOpExpr(PNK_TYPEOF, JSOP_TYPEOF, begin); michael@0: case TOK_VOID: michael@0: return unaryOpExpr(PNK_VOID, JSOP_VOID, begin); michael@0: case TOK_NOT: michael@0: return unaryOpExpr(PNK_NOT, JSOP_NOT, begin); michael@0: case TOK_BITNOT: michael@0: return unaryOpExpr(PNK_BITNOT, JSOP_BITNOT, begin); michael@0: case TOK_ADD: michael@0: return unaryOpExpr(PNK_POS, JSOP_POS, begin); michael@0: case TOK_SUB: michael@0: return unaryOpExpr(PNK_NEG, JSOP_NEG, begin); michael@0: michael@0: case TOK_INC: michael@0: case TOK_DEC: michael@0: { michael@0: TokenKind tt2 = tokenStream.getToken(TokenStream::Operand); michael@0: pn2 = memberExpr(tt2, true); michael@0: if (!pn2) michael@0: return null(); michael@0: if (!checkAndMarkAsIncOperand(pn2, tt, true)) michael@0: return null(); michael@0: return handler.newUnary((tt == TOK_INC) ? PNK_PREINCREMENT : PNK_PREDECREMENT, michael@0: JSOP_NOP, michael@0: begin, michael@0: pn2); michael@0: } michael@0: michael@0: case TOK_DELETE: { michael@0: Node expr = unaryExpr(); michael@0: if (!expr) michael@0: return null(); michael@0: michael@0: // Per spec, deleting any unary expression is valid -- it simply michael@0: // returns true -- except for one case that is illegal in strict mode. michael@0: if (handler.isName(expr)) { michael@0: if (!report(ParseStrictError, pc->sc->strict, expr, JSMSG_DEPRECATED_DELETE_OPERAND)) michael@0: return null(); michael@0: pc->sc->setBindingsAccessedDynamically(); michael@0: } michael@0: michael@0: return handler.newDelete(begin, expr); michael@0: } michael@0: michael@0: case TOK_ERROR: michael@0: return null(); michael@0: michael@0: default: michael@0: pn = memberExpr(tt, true); michael@0: if (!pn) michael@0: return null(); michael@0: michael@0: /* Don't look across a newline boundary for a postfix incop. */ michael@0: tt = tokenStream.peekTokenSameLine(TokenStream::Operand); michael@0: if (tt == TOK_INC || tt == TOK_DEC) { michael@0: tokenStream.consumeKnownToken(tt); michael@0: if (!checkAndMarkAsIncOperand(pn, tt, false)) michael@0: return null(); michael@0: return handler.newUnary((tt == TOK_INC) ? PNK_POSTINCREMENT : PNK_POSTDECREMENT, michael@0: JSOP_NOP, michael@0: begin, michael@0: pn); michael@0: } michael@0: return pn; michael@0: } michael@0: MOZ_ASSUME_UNREACHABLE("unaryExpr"); michael@0: } michael@0: michael@0: /* michael@0: * A dedicated helper for transplanting the legacy comprehension expression E in michael@0: * michael@0: * [E for (V in I)] // legacy array comprehension michael@0: * (E for (V in I)) // legacy generator expression michael@0: * michael@0: * from its initial location in the AST, on the left of the 'for', to its final michael@0: * position on the right. To avoid a separate pass we do this by adjusting the michael@0: * blockids and name binding links that were established when E was parsed. michael@0: * michael@0: * A legacy generator expression desugars like so: michael@0: * michael@0: * (E for (V in I)) => (function () { for (var V in I) yield E; })() michael@0: * michael@0: * so the transplanter must adjust static level as well as blockid. E's source michael@0: * coordinates in root->pn_pos are critical to deciding which binding links to michael@0: * preserve and which to cut. michael@0: * michael@0: * NB: This is not a general tree transplanter -- it knows in particular that michael@0: * the one or more bindings induced by V have not yet been created. michael@0: */ michael@0: class LegacyCompExprTransplanter michael@0: { michael@0: ParseNode *root; michael@0: Parser *parser; michael@0: ParseContext *outerpc; michael@0: GeneratorKind comprehensionKind; michael@0: unsigned adjust; michael@0: HashSet visitedImplicitArguments; michael@0: michael@0: public: michael@0: LegacyCompExprTransplanter(ParseNode *pn, Parser *parser, michael@0: ParseContext *outerpc, michael@0: GeneratorKind kind, unsigned adj) michael@0: : root(pn), parser(parser), outerpc(outerpc), comprehensionKind(kind), adjust(adj), michael@0: visitedImplicitArguments(parser->context) michael@0: {} michael@0: michael@0: bool init() { michael@0: return visitedImplicitArguments.init(); michael@0: } michael@0: michael@0: bool transplant(ParseNode *pn); michael@0: }; michael@0: michael@0: /* michael@0: * Any definitions nested within the legacy comprehension expression of a michael@0: * generator expression must move "down" one static level, which of course michael@0: * increases the upvar-frame-skip count. michael@0: */ michael@0: template michael@0: static bool michael@0: BumpStaticLevel(TokenStream &ts, ParseNode *pn, ParseContext *pc) michael@0: { michael@0: if (pn->pn_cookie.isFree()) michael@0: return true; michael@0: michael@0: unsigned level = unsigned(pn->pn_cookie.level()) + 1; michael@0: JS_ASSERT(level >= pc->staticLevel); michael@0: return pn->pn_cookie.set(ts, level, pn->pn_cookie.slot()); michael@0: } michael@0: michael@0: template michael@0: static bool michael@0: AdjustBlockId(TokenStream &ts, ParseNode *pn, unsigned adjust, ParseContext *pc) michael@0: { michael@0: JS_ASSERT(pn->isArity(PN_LIST) || pn->isArity(PN_CODE) || pn->isArity(PN_NAME)); michael@0: if (BlockIdLimit - pn->pn_blockid <= adjust + 1) { michael@0: ts.reportError(JSMSG_NEED_DIET, "program"); michael@0: return false; michael@0: } michael@0: pn->pn_blockid += adjust; michael@0: if (pn->pn_blockid >= pc->blockidGen) michael@0: pc->blockidGen = pn->pn_blockid + 1; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: LegacyCompExprTransplanter::transplant(ParseNode *pn) michael@0: { michael@0: ParseContext *pc = parser->pc; michael@0: michael@0: bool isGenexp = comprehensionKind != NotGenerator; michael@0: michael@0: if (!pn) michael@0: return true; michael@0: michael@0: switch (pn->getArity()) { michael@0: case PN_LIST: michael@0: for (ParseNode *pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { michael@0: if (!transplant(pn2)) michael@0: return false; michael@0: } michael@0: if (pn->pn_pos >= root->pn_pos) { michael@0: if (!AdjustBlockId(parser->tokenStream, pn, adjust, pc)) michael@0: return false; michael@0: } michael@0: break; michael@0: michael@0: case PN_TERNARY: michael@0: if (!transplant(pn->pn_kid1) || michael@0: !transplant(pn->pn_kid2) || michael@0: !transplant(pn->pn_kid3)) michael@0: return false; michael@0: break; michael@0: michael@0: case PN_BINARY: michael@0: case PN_BINARY_OBJ: michael@0: if (!transplant(pn->pn_left)) michael@0: return false; michael@0: michael@0: /* Binary TOK_COLON nodes can have left == right. See bug 492714. */ michael@0: if (pn->pn_right != pn->pn_left) { michael@0: if (!transplant(pn->pn_right)) michael@0: return false; michael@0: } michael@0: break; michael@0: michael@0: case PN_UNARY: michael@0: if (!transplant(pn->pn_kid)) michael@0: return false; michael@0: break; michael@0: michael@0: case PN_CODE: michael@0: case PN_NAME: michael@0: if (!transplant(pn->maybeExpr())) michael@0: return false; michael@0: michael@0: if (pn->isDefn()) { michael@0: if (isGenexp && !BumpStaticLevel(parser->tokenStream, pn, pc)) michael@0: return false; michael@0: } else if (pn->isUsed()) { michael@0: JS_ASSERT(pn->pn_cookie.isFree()); michael@0: michael@0: Definition *dn = pn->pn_lexdef; michael@0: JS_ASSERT(dn->isDefn()); michael@0: michael@0: /* michael@0: * Adjust the definition's block id only if it is a placeholder not michael@0: * to the left of the root node, and if pn is the last use visited michael@0: * in the legacy comprehension expression (to avoid adjusting the michael@0: * blockid multiple times). michael@0: * michael@0: * Non-placeholder definitions within the legacy comprehension michael@0: * expression will be visited further below. michael@0: */ michael@0: if (dn->isPlaceholder() && dn->pn_pos >= root->pn_pos && dn->dn_uses == pn) { michael@0: if (isGenexp && !BumpStaticLevel(parser->tokenStream, dn, pc)) michael@0: return false; michael@0: if (!AdjustBlockId(parser->tokenStream, dn, adjust, pc)) michael@0: return false; michael@0: } michael@0: michael@0: RootedAtom atom(parser->context, pn->pn_atom); michael@0: #ifdef DEBUG michael@0: StmtInfoPC *stmt = LexicalLookup(pc, atom, nullptr, (StmtInfoPC *)nullptr); michael@0: JS_ASSERT(!stmt || stmt != pc->topStmt); michael@0: #endif michael@0: if (isGenexp && !dn->isOp(JSOP_CALLEE)) { michael@0: JS_ASSERT(!pc->decls().lookupFirst(atom)); michael@0: michael@0: if (dn->pn_pos < root->pn_pos) { michael@0: /* michael@0: * The variable originally appeared to be a use of a michael@0: * definition or placeholder outside the generator, but now michael@0: * we know it is scoped within the legacy comprehension michael@0: * tail's clauses. Make it (along with any other uses within michael@0: * the generator) a use of a new placeholder in the michael@0: * generator's lexdeps. michael@0: */ michael@0: Definition *dn2 = parser->handler.newPlaceholder(atom, parser->pc->blockid(), michael@0: parser->pos()); michael@0: if (!dn2) michael@0: return false; michael@0: dn2->pn_pos = root->pn_pos; michael@0: michael@0: /* michael@0: * Change all uses of |dn| that lie within the generator's michael@0: * |yield| expression into uses of dn2. michael@0: */ michael@0: ParseNode **pnup = &dn->dn_uses; michael@0: ParseNode *pnu; michael@0: while ((pnu = *pnup) != nullptr && pnu->pn_pos >= root->pn_pos) { michael@0: pnu->pn_lexdef = dn2; michael@0: dn2->pn_dflags |= pnu->pn_dflags & PND_USE2DEF_FLAGS; michael@0: pnup = &pnu->pn_link; michael@0: } michael@0: dn2->dn_uses = dn->dn_uses; michael@0: dn->dn_uses = *pnup; michael@0: *pnup = nullptr; michael@0: DefinitionSingle def = DefinitionSingle::new_(dn2); michael@0: if (!pc->lexdeps->put(atom, def)) michael@0: return false; michael@0: if (dn->isClosed()) michael@0: dn2->pn_dflags |= PND_CLOSED; michael@0: } else if (dn->isPlaceholder()) { michael@0: /* michael@0: * The variable first occurs free in the 'yield' expression; michael@0: * move the existing placeholder node (and all its uses) michael@0: * from the parent's lexdeps into the generator's lexdeps. michael@0: */ michael@0: outerpc->lexdeps->remove(atom); michael@0: DefinitionSingle def = DefinitionSingle::new_(dn); michael@0: if (!pc->lexdeps->put(atom, def)) michael@0: return false; michael@0: } else if (dn->isImplicitArguments()) { michael@0: /* michael@0: * Implicit 'arguments' Definition nodes (see michael@0: * PND_IMPLICITARGUMENTS in Parser::functionBody) are only michael@0: * reachable via the lexdefs of their uses. Unfortunately, michael@0: * there may be multiple uses, so we need to maintain a set michael@0: * to only bump the definition once. michael@0: */ michael@0: if (isGenexp && !visitedImplicitArguments.has(dn)) { michael@0: if (!BumpStaticLevel(parser->tokenStream, dn, pc)) michael@0: return false; michael@0: if (!AdjustBlockId(parser->tokenStream, dn, adjust, pc)) michael@0: return false; michael@0: if (!visitedImplicitArguments.put(dn)) michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (pn->pn_pos >= root->pn_pos) { michael@0: if (!AdjustBlockId(parser->tokenStream, pn, adjust, pc)) michael@0: return false; michael@0: } michael@0: break; michael@0: michael@0: case PN_NULLARY: michael@0: /* Nothing. */ michael@0: break; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: // Parsing legacy (JS1.7-style) comprehensions is terrible: we parse the head michael@0: // expression as if it's part of a comma expression, then when we see the "for" michael@0: // we transplant the parsed expression into the inside of a constructed michael@0: // for-of/for-in/for-each tail. Transplanting an already-parsed expression is michael@0: // tricky, but the LegacyCompExprTransplanter handles most of that. michael@0: // michael@0: // The one remaining thing to patch up is the block scope depth. We need to michael@0: // compute the maximum block scope depth of a function, so we know how much michael@0: // space to reserve in the fixed part of a stack frame. Normally this is done michael@0: // whenever we leave a statement, via AccumulateBlockScopeDepth. However if the michael@0: // head has a let expression, we need to re-assign that depth to the tail of the michael@0: // comprehension. michael@0: // michael@0: // Thing is, we don't actually know what that depth is, because the only michael@0: // information we keep is the maximum nested depth within a statement, so we michael@0: // just conservatively propagate the maximum nested depth from the top statement michael@0: // to the comprehension tail. michael@0: // michael@0: template michael@0: static unsigned michael@0: LegacyComprehensionHeadBlockScopeDepth(ParseContext *pc) michael@0: { michael@0: return pc->topStmt ? pc->topStmt->innerBlockScopeDepth : pc->blockScopeDepth; michael@0: } michael@0: michael@0: /* michael@0: * Starting from a |for| keyword after the first array initialiser element or michael@0: * an expression in an open parenthesis, parse the tail of the comprehension michael@0: * or generator expression signified by this |for| keyword in context. michael@0: * michael@0: * Return null on failure, else return the top-most parse node for the array michael@0: * comprehension or generator expression, with a unary node as the body of the michael@0: * (possibly nested) for-loop, initialized by |kind, op, kid|. michael@0: */ michael@0: template <> michael@0: ParseNode * michael@0: Parser::legacyComprehensionTail(ParseNode *bodyStmt, unsigned blockid, michael@0: GeneratorKind comprehensionKind, michael@0: ParseContext *outerpc, michael@0: unsigned innerBlockScopeDepth) michael@0: { michael@0: /* michael@0: * If we saw any inner functions while processing the generator expression michael@0: * then they may have upvars referring to the let vars in this generator michael@0: * which were not correctly processed. Bail out and start over without michael@0: * allowing lazy parsing. michael@0: */ michael@0: if (handler.syntaxParser) { michael@0: handler.disableSyntaxParser(); michael@0: abortedSyntaxParse = true; michael@0: return nullptr; michael@0: } michael@0: michael@0: unsigned adjust; michael@0: ParseNode *pn, *pn2, *pn3, **pnp; michael@0: StmtInfoPC stmtInfo(context); michael@0: BindData data(context); michael@0: TokenKind tt; michael@0: michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR)); michael@0: michael@0: bool isGenexp = comprehensionKind != NotGenerator; michael@0: michael@0: if (isGenexp) { michael@0: JS_ASSERT(comprehensionKind == LegacyGenerator); michael@0: /* michael@0: * Generator expression desugars to an immediately applied lambda that michael@0: * yields the next value from a for-in loop (possibly nested, and with michael@0: * optional if guard). Make pn be the TOK_LC body node. michael@0: */ michael@0: pn = pushLexicalScope(&stmtInfo); michael@0: if (!pn) michael@0: return null(); michael@0: adjust = pn->pn_blockid - blockid; michael@0: } else { michael@0: /* michael@0: * Make a parse-node and literal object representing the block scope of michael@0: * this array comprehension. Our caller in primaryExpr, the TOK_LB case michael@0: * aka the array initialiser case, has passed the blockid to claim for michael@0: * the comprehension's block scope. We allocate that id or one above it michael@0: * here, by calling PushLexicalScope. michael@0: * michael@0: * In the case of a comprehension expression that has nested blocks michael@0: * (e.g., let expressions), we will allocate a higher blockid but then michael@0: * slide all blocks "to the right" to make room for the comprehension's michael@0: * block scope. michael@0: */ michael@0: adjust = pc->blockid(); michael@0: pn = pushLexicalScope(&stmtInfo); michael@0: if (!pn) michael@0: return null(); michael@0: michael@0: JS_ASSERT(blockid <= pn->pn_blockid); michael@0: JS_ASSERT(blockid < pc->blockidGen); michael@0: JS_ASSERT(pc->bodyid < blockid); michael@0: pn->pn_blockid = stmtInfo.blockid = blockid; michael@0: JS_ASSERT(adjust < blockid); michael@0: adjust = blockid - adjust; michael@0: } michael@0: michael@0: handler.setBeginPosition(pn, bodyStmt); michael@0: michael@0: pnp = &pn->pn_expr; michael@0: michael@0: LegacyCompExprTransplanter transplanter(bodyStmt, this, outerpc, comprehensionKind, adjust); michael@0: if (!transplanter.init()) michael@0: return null(); michael@0: michael@0: if (!transplanter.transplant(bodyStmt)) michael@0: return null(); michael@0: michael@0: JS_ASSERT(pc->staticScope && pc->staticScope == pn->pn_objbox->object); michael@0: data.initLet(HoistVars, pc->staticScope->as(), JSMSG_ARRAY_INIT_TOO_BIG); michael@0: michael@0: do { michael@0: /* michael@0: * FOR node is binary, left is loop control and right is body. Use michael@0: * index to count each block-local let-variable on the left-hand side michael@0: * of the in/of. michael@0: */ michael@0: pn2 = BinaryNode::create(PNK_FOR, &handler); michael@0: if (!pn2) michael@0: return null(); michael@0: michael@0: pn2->setOp(JSOP_ITER); michael@0: pn2->pn_iflags = JSITER_ENUMERATE; michael@0: if (allowsForEachIn() && tokenStream.matchContextualKeyword(context->names().each)) michael@0: pn2->pn_iflags |= JSITER_FOREACH; michael@0: MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); michael@0: michael@0: uint32_t startYieldOffset = pc->lastYieldOffset; michael@0: michael@0: RootedPropertyName name(context); michael@0: tt = tokenStream.getToken(); michael@0: switch (tt) { michael@0: case TOK_LB: michael@0: case TOK_LC: michael@0: pc->inDeclDestructuring = true; michael@0: pn3 = primaryExpr(tt); michael@0: pc->inDeclDestructuring = false; michael@0: if (!pn3) michael@0: return null(); michael@0: break; michael@0: michael@0: case TOK_NAME: michael@0: name = tokenStream.currentName(); michael@0: michael@0: /* michael@0: * Create a name node with pn_op JSOP_NAME. We can't set pn_op to michael@0: * JSOP_GETLOCAL here, because we don't yet know the block's depth michael@0: * in the operand stack frame. The code generator computes that, michael@0: * and it tries to bind all names to slots, so we must let it do michael@0: * the deed. michael@0: */ michael@0: pn3 = newBindingNode(name, false); michael@0: if (!pn3) michael@0: return null(); michael@0: break; michael@0: michael@0: default: michael@0: report(ParseError, false, null(), JSMSG_NO_VARIABLE_NAME); michael@0: michael@0: case TOK_ERROR: michael@0: return null(); michael@0: } michael@0: michael@0: bool isForOf; michael@0: if (!matchInOrOf(&isForOf)) { michael@0: report(ParseError, false, null(), JSMSG_IN_AFTER_FOR_NAME); michael@0: return null(); michael@0: } michael@0: ParseNodeKind headKind = PNK_FORIN; michael@0: if (isForOf) { michael@0: if (pn2->pn_iflags != JSITER_ENUMERATE) { michael@0: JS_ASSERT(pn2->pn_iflags == (JSITER_FOREACH | JSITER_ENUMERATE)); michael@0: report(ParseError, false, null(), JSMSG_BAD_FOR_EACH_LOOP); michael@0: return null(); michael@0: } michael@0: pn2->pn_iflags = 0; michael@0: headKind = PNK_FOROF; michael@0: } michael@0: michael@0: ParseNode *pn4 = expr(); michael@0: if (!pn4) michael@0: return null(); michael@0: MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL); michael@0: michael@0: if (isGenexp && pc->lastYieldOffset != startYieldOffset) { michael@0: reportWithOffset(ParseError, false, pc->lastYieldOffset, michael@0: JSMSG_BAD_GENEXP_BODY, js_yield_str); michael@0: return null(); michael@0: } michael@0: michael@0: switch (tt) { michael@0: case TOK_LB: michael@0: case TOK_LC: michael@0: if (!checkDestructuring(&data, pn3)) michael@0: return null(); michael@0: michael@0: if (versionNumber() == JSVERSION_1_7 && michael@0: !(pn2->pn_iflags & JSITER_FOREACH) && michael@0: !isForOf) michael@0: { michael@0: /* Destructuring requires [key, value] enumeration in JS1.7. */ michael@0: if (!pn3->isKind(PNK_ARRAY) || pn3->pn_count != 2) { michael@0: report(ParseError, false, null(), JSMSG_BAD_FOR_LEFTSIDE); michael@0: return null(); michael@0: } michael@0: michael@0: JS_ASSERT(pn2->isOp(JSOP_ITER)); michael@0: JS_ASSERT(pn2->pn_iflags & JSITER_ENUMERATE); michael@0: JS_ASSERT(headKind == PNK_FORIN); michael@0: pn2->pn_iflags |= JSITER_FOREACH | JSITER_KEYVALUE; michael@0: } michael@0: break; michael@0: michael@0: case TOK_NAME: michael@0: data.pn = pn3; michael@0: if (!data.binder(&data, name, this)) michael@0: return null(); michael@0: break; michael@0: michael@0: default:; michael@0: } michael@0: michael@0: /* michael@0: * Synthesize a declaration. Every definition must appear in the parse michael@0: * tree in order for ComprehensionTranslator to work. michael@0: */ michael@0: ParseNode *vars = ListNode::create(PNK_VAR, &handler); michael@0: if (!vars) michael@0: return null(); michael@0: vars->setOp(JSOP_NOP); michael@0: vars->pn_pos = pn3->pn_pos; michael@0: vars->makeEmpty(); michael@0: vars->append(pn3); michael@0: michael@0: /* Definitions can't be passed directly to EmitAssignment as lhs. */ michael@0: pn3 = cloneLeftHandSide(pn3); michael@0: if (!pn3) michael@0: return null(); michael@0: michael@0: pn2->pn_left = handler.newTernary(headKind, vars, pn3, pn4); michael@0: if (!pn2->pn_left) michael@0: return null(); michael@0: *pnp = pn2; michael@0: pnp = &pn2->pn_right; michael@0: } while (tokenStream.matchToken(TOK_FOR)); michael@0: michael@0: if (tokenStream.matchToken(TOK_IF)) { michael@0: pn2 = TernaryNode::create(PNK_IF, &handler); michael@0: if (!pn2) michael@0: return null(); michael@0: pn2->pn_kid1 = condition(); michael@0: if (!pn2->pn_kid1) michael@0: return null(); michael@0: *pnp = pn2; michael@0: pnp = &pn2->pn_kid2; michael@0: } michael@0: michael@0: *pnp = bodyStmt; michael@0: michael@0: pc->topStmt->innerBlockScopeDepth += innerBlockScopeDepth; michael@0: PopStatementPC(tokenStream, pc); michael@0: michael@0: handler.setEndPosition(pn, pos().end); michael@0: michael@0: return pn; michael@0: } michael@0: michael@0: template <> michael@0: SyntaxParseHandler::Node michael@0: Parser::legacyComprehensionTail(SyntaxParseHandler::Node bodyStmt, michael@0: unsigned blockid, michael@0: GeneratorKind comprehensionKind, michael@0: ParseContext *outerpc, michael@0: unsigned innerBlockScopeDepth) michael@0: { michael@0: abortIfSyntaxParser(); michael@0: return null(); michael@0: } michael@0: michael@0: template <> michael@0: ParseNode* michael@0: Parser::legacyArrayComprehension(ParseNode *array) michael@0: { michael@0: array->setKind(PNK_ARRAYCOMP); michael@0: michael@0: // Remove the single element from array's linked list, leaving us with an michael@0: // empty array literal and a comprehension expression. michael@0: JS_ASSERT(array->pn_count == 1); michael@0: ParseNode *bodyExpr = array->last(); michael@0: array->pn_count = 0; michael@0: array->pn_tail = &array->pn_head; michael@0: *array->pn_tail = nullptr; michael@0: michael@0: ParseNode *arrayPush = handler.newUnary(PNK_ARRAYPUSH, JSOP_ARRAYPUSH, michael@0: bodyExpr->pn_pos.begin, bodyExpr); michael@0: if (!arrayPush) michael@0: return null(); michael@0: michael@0: ParseNode *comp = legacyComprehensionTail(arrayPush, array->pn_blockid, NotGenerator, michael@0: nullptr, LegacyComprehensionHeadBlockScopeDepth(pc)); michael@0: if (!comp) michael@0: return null(); michael@0: michael@0: MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_AFTER_ARRAY_COMPREHENSION); michael@0: michael@0: TokenPos p = handler.getPosition(array); michael@0: p.end = pos().end; michael@0: return handler.newArrayComprehension(comp, array->pn_blockid, p); michael@0: } michael@0: michael@0: template <> michael@0: SyntaxParseHandler::Node michael@0: Parser::legacyArrayComprehension(Node array) michael@0: { michael@0: abortIfSyntaxParser(); michael@0: return null(); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::generatorComprehensionLambda(GeneratorKind comprehensionKind, michael@0: unsigned begin, Node innerStmt) michael@0: { michael@0: JS_ASSERT(comprehensionKind == LegacyGenerator || comprehensionKind == StarGenerator); michael@0: JS_ASSERT(!!innerStmt == (comprehensionKind == LegacyGenerator)); michael@0: michael@0: Node genfn = handler.newFunctionDefinition(); michael@0: if (!genfn) michael@0: return null(); michael@0: handler.setOp(genfn, JSOP_LAMBDA); michael@0: michael@0: ParseContext *outerpc = pc; michael@0: michael@0: // If we are off the main thread, the generator meta-objects have michael@0: // already been created by js::StartOffThreadParseScript, so cx will not michael@0: // be necessary. michael@0: RootedObject proto(context); michael@0: if (comprehensionKind == StarGenerator) { michael@0: JSContext *cx = context->maybeJSContext(); michael@0: proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, context->global()); michael@0: if (!proto) michael@0: return null(); michael@0: } michael@0: michael@0: RootedFunction fun(context, newFunction(outerpc, /* atom = */ NullPtr(), Expression, proto)); michael@0: if (!fun) michael@0: return null(); michael@0: michael@0: // Create box for fun->object early to root it. michael@0: Directives directives(/* strict = */ outerpc->sc->strict); michael@0: FunctionBox *genFunbox = newFunctionBox(genfn, fun, outerpc, directives, comprehensionKind); michael@0: if (!genFunbox) michael@0: return null(); michael@0: michael@0: ParseContext genpc(this, outerpc, genfn, genFunbox, michael@0: /* newDirectives = */ nullptr, michael@0: outerpc->staticLevel + 1, outerpc->blockidGen, michael@0: /* blockScopeDepth = */ 0); michael@0: if (!genpc.init(tokenStream)) michael@0: return null(); michael@0: michael@0: /* michael@0: * We assume conservatively that any deoptimization flags in pc->sc michael@0: * come from the kid. So we propagate these flags into genfn. For code michael@0: * simplicity we also do not detect if the flags were only set in the michael@0: * kid and could be removed from pc->sc. michael@0: */ michael@0: genFunbox->anyCxFlags = outerpc->sc->anyCxFlags; michael@0: if (outerpc->sc->isFunctionBox()) michael@0: genFunbox->funCxFlags = outerpc->sc->asFunctionBox()->funCxFlags; michael@0: michael@0: JS_ASSERT(genFunbox->generatorKind() == comprehensionKind); michael@0: genFunbox->inGenexpLambda = true; michael@0: handler.setBlockId(genfn, genpc.bodyid); michael@0: michael@0: Node body; michael@0: michael@0: if (comprehensionKind == StarGenerator) { michael@0: body = comprehension(StarGenerator); michael@0: if (!body) michael@0: return null(); michael@0: } else { michael@0: JS_ASSERT(comprehensionKind == LegacyGenerator); michael@0: body = legacyComprehensionTail(innerStmt, outerpc->blockid(), LegacyGenerator, michael@0: outerpc, LegacyComprehensionHeadBlockScopeDepth(outerpc)); michael@0: if (!body) michael@0: return null(); michael@0: } michael@0: michael@0: if (comprehensionKind == StarGenerator) michael@0: MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_IN_PAREN); michael@0: michael@0: handler.setBeginPosition(body, begin); michael@0: handler.setEndPosition(body, pos().end); michael@0: michael@0: handler.setBeginPosition(genfn, begin); michael@0: handler.setEndPosition(genfn, pos().end); michael@0: michael@0: // Note that if we ever start syntax-parsing generators, we will also michael@0: // need to propagate the closed-over variable set to the inner michael@0: // lazyscript, as in finishFunctionDefinition. michael@0: handler.setFunctionBody(genfn, body); michael@0: michael@0: PropagateTransitiveParseFlags(genFunbox, outerpc->sc); michael@0: michael@0: if (!leaveFunction(genfn, outerpc)) michael@0: return null(); michael@0: michael@0: return genfn; michael@0: } michael@0: michael@0: #if JS_HAS_GENERATOR_EXPRS michael@0: michael@0: /* michael@0: * Starting from a |for| keyword after an expression, parse the comprehension michael@0: * tail completing this generator expression. Wrap the expression at kid in a michael@0: * generator function that is immediately called to evaluate to the generator michael@0: * iterator that is the value of this legacy generator expression. michael@0: * michael@0: * |kid| must be the expression before the |for| keyword; we return an michael@0: * application of a generator function that includes the |for| loops and michael@0: * |if| guards, with |kid| as the operand of a |yield| expression as the michael@0: * innermost loop body. michael@0: * michael@0: * Note how unlike Python, we do not evaluate the expression to the right of michael@0: * the first |in| in the chain of |for| heads. Instead, a generator expression michael@0: * is merely sugar for a generator function expression and its application. michael@0: */ michael@0: template <> michael@0: ParseNode * michael@0: Parser::legacyGeneratorExpr(ParseNode *expr) michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR)); michael@0: michael@0: /* Create a |yield| node for |kid|. */ michael@0: ParseNode *yieldExpr = handler.newUnary(PNK_YIELD, JSOP_NOP, expr->pn_pos.begin, expr); michael@0: if (!yieldExpr) michael@0: return null(); michael@0: yieldExpr->setInParens(true); michael@0: michael@0: // A statement to wrap the yield expression. michael@0: ParseNode *yieldStmt = handler.newExprStatement(yieldExpr, expr->pn_pos.end); michael@0: if (!yieldStmt) michael@0: return null(); michael@0: michael@0: /* Make a new node for the desugared generator function. */ michael@0: ParseNode *genfn = generatorComprehensionLambda(LegacyGenerator, expr->pn_pos.begin, yieldStmt); michael@0: if (!genfn) michael@0: return null(); michael@0: michael@0: /* michael@0: * Our result is a call expression that invokes the anonymous generator michael@0: * function object. michael@0: */ michael@0: ParseNode *result = ListNode::create(PNK_GENEXP, &handler); michael@0: if (!result) michael@0: return null(); michael@0: result->setOp(JSOP_CALL); michael@0: result->pn_pos.begin = genfn->pn_pos.begin; michael@0: result->initList(genfn); michael@0: return result; michael@0: } michael@0: michael@0: template <> michael@0: SyntaxParseHandler::Node michael@0: Parser::legacyGeneratorExpr(Node kid) michael@0: { michael@0: JS_ALWAYS_FALSE(abortIfSyntaxParser()); michael@0: return SyntaxParseHandler::NodeFailure; michael@0: } michael@0: michael@0: static const char js_generator_str[] = "generator"; michael@0: michael@0: #endif /* JS_HAS_GENERATOR_EXPRS */ michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::comprehensionFor(GeneratorKind comprehensionKind) michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR)); michael@0: michael@0: uint32_t begin = pos().begin; michael@0: michael@0: MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); michael@0: michael@0: // FIXME: Destructuring binding (bug 980828). michael@0: michael@0: MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_VARIABLE_NAME); michael@0: RootedPropertyName name(context, tokenStream.currentName()); michael@0: if (name == context->names().let) { michael@0: report(ParseError, false, null(), JSMSG_LET_COMP_BINDING); michael@0: return null(); michael@0: } michael@0: if (!tokenStream.matchContextualKeyword(context->names().of)) { michael@0: report(ParseError, false, null(), JSMSG_OF_AFTER_FOR_NAME); michael@0: return null(); michael@0: } michael@0: michael@0: Node rhs = assignExpr(); michael@0: if (!rhs) michael@0: return null(); michael@0: michael@0: MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_OF_ITERABLE); michael@0: michael@0: TokenPos headPos(begin, pos().end); michael@0: michael@0: StmtInfoPC stmtInfo(context); michael@0: BindData data(context); michael@0: RootedStaticBlockObject blockObj(context, StaticBlockObject::create(context)); michael@0: if (!blockObj) michael@0: return null(); michael@0: data.initLet(DontHoistVars, *blockObj, JSMSG_TOO_MANY_LOCALS); michael@0: Node lhs = newName(name); michael@0: if (!lhs) michael@0: return null(); michael@0: Node decls = handler.newList(PNK_LET, lhs, JSOP_NOP); michael@0: if (!decls) michael@0: return null(); michael@0: data.pn = lhs; michael@0: if (!data.binder(&data, name, this)) michael@0: return null(); michael@0: Node letScope = pushLetScope(blockObj, &stmtInfo); michael@0: if (!letScope) michael@0: return null(); michael@0: handler.setLexicalScopeBody(letScope, decls); michael@0: michael@0: Node assignLhs = newName(name); michael@0: if (!assignLhs) michael@0: return null(); michael@0: if (!noteNameUse(name, assignLhs)) michael@0: return null(); michael@0: handler.setOp(assignLhs, JSOP_SETNAME); michael@0: michael@0: Node head = handler.newForHead(PNK_FOROF, letScope, assignLhs, rhs, headPos); michael@0: if (!head) michael@0: return null(); michael@0: michael@0: Node tail = comprehensionTail(comprehensionKind); michael@0: if (!tail) michael@0: return null(); michael@0: michael@0: PopStatementPC(tokenStream, pc); michael@0: michael@0: return handler.newForStatement(begin, head, tail, JSOP_ITER); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::comprehensionIf(GeneratorKind comprehensionKind) michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_IF)); michael@0: michael@0: uint32_t begin = pos().begin; michael@0: michael@0: MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_COND); michael@0: Node cond = assignExpr(); michael@0: if (!cond) michael@0: return null(); michael@0: MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_COND); michael@0: michael@0: /* Check for (a = b) and warn about possible (a == b) mistype. */ michael@0: if (handler.isOperationWithoutParens(cond, PNK_ASSIGN) && michael@0: !report(ParseExtraWarning, false, null(), JSMSG_EQUAL_AS_ASSIGN)) michael@0: { michael@0: return null(); michael@0: } michael@0: michael@0: Node then = comprehensionTail(comprehensionKind); michael@0: if (!then) michael@0: return null(); michael@0: michael@0: return handler.newIfStatement(begin, cond, then, null()); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::comprehensionTail(GeneratorKind comprehensionKind) michael@0: { michael@0: JS_CHECK_RECURSION(context, return null()); michael@0: michael@0: if (tokenStream.matchToken(TOK_FOR, TokenStream::Operand)) michael@0: return comprehensionFor(comprehensionKind); michael@0: michael@0: if (tokenStream.matchToken(TOK_IF, TokenStream::Operand)) michael@0: return comprehensionIf(comprehensionKind); michael@0: michael@0: uint32_t begin = pos().begin; michael@0: michael@0: Node bodyExpr = assignExpr(); michael@0: if (!bodyExpr) michael@0: return null(); michael@0: michael@0: if (comprehensionKind == NotGenerator) michael@0: return handler.newUnary(PNK_ARRAYPUSH, JSOP_ARRAYPUSH, begin, bodyExpr); michael@0: michael@0: JS_ASSERT(comprehensionKind == StarGenerator); michael@0: Node yieldExpr = handler.newUnary(PNK_YIELD, JSOP_NOP, begin, bodyExpr); michael@0: if (!yieldExpr) michael@0: return null(); michael@0: handler.setInParens(yieldExpr); michael@0: michael@0: return handler.newExprStatement(yieldExpr, pos().end); michael@0: } michael@0: michael@0: // Parse an ES6 generator or array comprehension, starting at the first 'for'. michael@0: // The caller is responsible for matching the ending TOK_RP or TOK_RB. michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::comprehension(GeneratorKind comprehensionKind) michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR)); michael@0: michael@0: uint32_t startYieldOffset = pc->lastYieldOffset; michael@0: michael@0: Node body = comprehensionFor(comprehensionKind); michael@0: if (!body) michael@0: return null(); michael@0: michael@0: if (comprehensionKind != NotGenerator && pc->lastYieldOffset != startYieldOffset) { michael@0: reportWithOffset(ParseError, false, pc->lastYieldOffset, michael@0: JSMSG_BAD_GENEXP_BODY, js_yield_str); michael@0: return null(); michael@0: } michael@0: michael@0: return body; michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::arrayComprehension(uint32_t begin) michael@0: { michael@0: Node inner = comprehension(NotGenerator); michael@0: if (!inner) michael@0: return null(); michael@0: michael@0: MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_AFTER_ARRAY_COMPREHENSION); michael@0: michael@0: Node comp = handler.newList(PNK_ARRAYCOMP, inner); michael@0: if (!comp) michael@0: return null(); michael@0: michael@0: handler.setBeginPosition(comp, begin); michael@0: handler.setEndPosition(comp, pos().end); michael@0: michael@0: return comp; michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::generatorComprehension(uint32_t begin) michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR)); michael@0: michael@0: // We have no problem parsing generator comprehensions inside lazy michael@0: // functions, but the bytecode emitter currently can't handle them that way, michael@0: // because when it goes to emit the code for the inner generator function, michael@0: // it expects outer functions to have non-lazy scripts. michael@0: if (!abortIfSyntaxParser()) michael@0: return null(); michael@0: michael@0: Node genfn = generatorComprehensionLambda(StarGenerator, begin, null()); michael@0: if (!genfn) michael@0: return null(); michael@0: michael@0: Node result = handler.newList(PNK_GENEXP, genfn, JSOP_CALL); michael@0: if (!result) michael@0: return null(); michael@0: handler.setBeginPosition(result, begin); michael@0: handler.setEndPosition(result, pos().end); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::assignExprWithoutYield(unsigned msg) michael@0: { michael@0: uint32_t startYieldOffset = pc->lastYieldOffset; michael@0: Node res = assignExpr(); michael@0: if (res && pc->lastYieldOffset != startYieldOffset) { michael@0: reportWithOffset(ParseError, false, pc->lastYieldOffset, michael@0: msg, js_yield_str); michael@0: return null(); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: template michael@0: bool michael@0: Parser::argumentList(Node listNode, bool *isSpread) michael@0: { michael@0: if (tokenStream.matchToken(TOK_RP, TokenStream::Operand)) { michael@0: handler.setEndPosition(listNode, pos().end); michael@0: return true; michael@0: } michael@0: michael@0: uint32_t startYieldOffset = pc->lastYieldOffset; michael@0: bool arg0 = true; michael@0: michael@0: do { michael@0: bool spread = false; michael@0: uint32_t begin = 0; michael@0: if (tokenStream.matchToken(TOK_TRIPLEDOT, TokenStream::Operand)) { michael@0: spread = true; michael@0: begin = pos().begin; michael@0: *isSpread = true; michael@0: } michael@0: michael@0: Node argNode = assignExpr(); michael@0: if (!argNode) michael@0: return false; michael@0: if (spread) { michael@0: argNode = handler.newUnary(PNK_SPREAD, JSOP_NOP, begin, argNode); michael@0: if (!argNode) michael@0: return null(); michael@0: } michael@0: michael@0: if (handler.isOperationWithoutParens(argNode, PNK_YIELD) && michael@0: tokenStream.peekToken() == TOK_COMMA) { michael@0: report(ParseError, false, argNode, JSMSG_BAD_GENERATOR_SYNTAX, js_yield_str); michael@0: return false; michael@0: } michael@0: #if JS_HAS_GENERATOR_EXPRS michael@0: if (!spread && tokenStream.matchToken(TOK_FOR)) { michael@0: if (pc->lastYieldOffset != startYieldOffset) { michael@0: reportWithOffset(ParseError, false, pc->lastYieldOffset, michael@0: JSMSG_BAD_GENEXP_BODY, js_yield_str); michael@0: return false; michael@0: } michael@0: argNode = legacyGeneratorExpr(argNode); michael@0: if (!argNode) michael@0: return false; michael@0: if (!arg0 || tokenStream.peekToken() == TOK_COMMA) { michael@0: report(ParseError, false, argNode, JSMSG_BAD_GENERATOR_SYNTAX, js_generator_str); michael@0: return false; michael@0: } michael@0: } michael@0: #endif michael@0: arg0 = false; michael@0: michael@0: handler.addList(listNode, argNode); michael@0: } while (tokenStream.matchToken(TOK_COMMA)); michael@0: michael@0: if (tokenStream.getToken() != TOK_RP) { michael@0: report(ParseError, false, null(), JSMSG_PAREN_AFTER_ARGS); michael@0: return false; michael@0: } michael@0: handler.setEndPosition(listNode, pos().end); michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::memberExpr(TokenKind tt, bool allowCallSyntax) michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(tt)); michael@0: michael@0: Node lhs; michael@0: michael@0: JS_CHECK_RECURSION(context, return null()); michael@0: michael@0: /* Check for new expression first. */ michael@0: if (tt == TOK_NEW) { michael@0: lhs = handler.newList(PNK_NEW, null(), JSOP_NEW); michael@0: if (!lhs) michael@0: return null(); michael@0: michael@0: tt = tokenStream.getToken(TokenStream::Operand); michael@0: Node ctorExpr = memberExpr(tt, false); michael@0: if (!ctorExpr) michael@0: return null(); michael@0: michael@0: handler.addList(lhs, ctorExpr); michael@0: michael@0: if (tokenStream.matchToken(TOK_LP)) { michael@0: bool isSpread = false; michael@0: if (!argumentList(lhs, &isSpread)) michael@0: return null(); michael@0: if (isSpread) michael@0: handler.setOp(lhs, JSOP_SPREADNEW); michael@0: } michael@0: } else { michael@0: lhs = primaryExpr(tt); michael@0: if (!lhs) michael@0: return null(); michael@0: } michael@0: michael@0: while ((tt = tokenStream.getToken()) > TOK_EOF) { michael@0: Node nextMember; michael@0: if (tt == TOK_DOT) { michael@0: tt = tokenStream.getToken(TokenStream::KeywordIsName); michael@0: if (tt == TOK_ERROR) michael@0: return null(); michael@0: if (tt == TOK_NAME) { michael@0: PropertyName *field = tokenStream.currentName(); michael@0: nextMember = handler.newPropertyAccess(lhs, field, pos().end); michael@0: if (!nextMember) michael@0: return null(); michael@0: } else { michael@0: report(ParseError, false, null(), JSMSG_NAME_AFTER_DOT); michael@0: return null(); michael@0: } michael@0: } else if (tt == TOK_LB) { michael@0: Node propExpr = expr(); michael@0: if (!propExpr) michael@0: return null(); michael@0: michael@0: MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_IN_INDEX); michael@0: michael@0: nextMember = handler.newPropertyByValue(lhs, propExpr, pos().end); michael@0: if (!nextMember) michael@0: return null(); michael@0: } else if (allowCallSyntax && tt == TOK_LP) { michael@0: JSOp op = JSOP_CALL; michael@0: nextMember = handler.newList(PNK_CALL, null(), JSOP_CALL); michael@0: if (!nextMember) michael@0: return null(); michael@0: michael@0: if (JSAtom *atom = handler.isName(lhs)) { michael@0: if (atom == context->names().eval) { michael@0: /* Select JSOP_EVAL and flag pc as heavyweight. */ michael@0: op = JSOP_EVAL; michael@0: pc->sc->setBindingsAccessedDynamically(); michael@0: michael@0: /* michael@0: * In non-strict mode code, direct calls to eval can add michael@0: * variables to the call object. michael@0: */ michael@0: if (pc->sc->isFunctionBox() && !pc->sc->strict) michael@0: pc->sc->asFunctionBox()->setHasExtensibleScope(); michael@0: } michael@0: } else if (JSAtom *atom = handler.isGetProp(lhs)) { michael@0: /* Select JSOP_FUNAPPLY given foo.apply(...). */ michael@0: if (atom == context->names().apply) { michael@0: op = JSOP_FUNAPPLY; michael@0: if (pc->sc->isFunctionBox()) michael@0: pc->sc->asFunctionBox()->usesApply = true; michael@0: } else if (atom == context->names().call) { michael@0: op = JSOP_FUNCALL; michael@0: } michael@0: } michael@0: michael@0: handler.setBeginPosition(nextMember, lhs); michael@0: handler.addList(nextMember, lhs); michael@0: michael@0: bool isSpread = false; michael@0: if (!argumentList(nextMember, &isSpread)) michael@0: return null(); michael@0: if (isSpread) michael@0: op = (op == JSOP_EVAL ? JSOP_SPREADEVAL : JSOP_SPREADCALL); michael@0: handler.setOp(nextMember, op); michael@0: } else { michael@0: tokenStream.ungetToken(); michael@0: return lhs; michael@0: } michael@0: michael@0: lhs = nextMember; michael@0: } michael@0: if (tt == TOK_ERROR) michael@0: return null(); michael@0: return lhs; michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::newName(PropertyName *name) michael@0: { michael@0: return handler.newName(name, pc->blockid(), pos()); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::identifierName() michael@0: { michael@0: RootedPropertyName name(context, tokenStream.currentName()); michael@0: Node pn = newName(name); michael@0: if (!pn) michael@0: return null(); michael@0: michael@0: if (!pc->inDeclDestructuring && !noteNameUse(name, pn)) michael@0: return null(); michael@0: michael@0: return pn; michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::stringLiteral() michael@0: { michael@0: JSAtom *atom = tokenStream.currentToken().atom(); michael@0: michael@0: // Large strings are fast to parse but slow to compress. Stop compression on michael@0: // them, so we don't wait for a long time for compression to finish at the michael@0: // end of compilation. michael@0: const size_t HUGE_STRING = 50000; michael@0: if (sct && sct->active() && atom->length() >= HUGE_STRING) michael@0: sct->abort(); michael@0: michael@0: return handler.newStringLiteral(atom, pos()); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::newRegExp() michael@0: { michael@0: // Create the regexp even when doing a syntax parse, to check the regexp's syntax. michael@0: const jschar *chars = tokenStream.getTokenbuf().begin(); michael@0: size_t length = tokenStream.getTokenbuf().length(); michael@0: RegExpFlag flags = tokenStream.currentToken().regExpFlags(); michael@0: michael@0: Rooted reobj(context); michael@0: if (RegExpStatics *res = context->global()->getRegExpStatics()) michael@0: reobj = RegExpObject::create(context, res, chars, length, flags, &tokenStream); michael@0: else michael@0: reobj = RegExpObject::createNoStatics(context, chars, length, flags, &tokenStream); michael@0: michael@0: if (!reobj) michael@0: return null(); michael@0: michael@0: return handler.newRegExp(reobj, pos(), *this); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::arrayInitializer() michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_LB)); michael@0: michael@0: uint32_t begin = pos().begin; michael@0: Node literal = handler.newArrayLiteral(begin, pc->blockidGen); michael@0: if (!literal) michael@0: return null(); michael@0: michael@0: if (tokenStream.matchToken(TOK_RB, TokenStream::Operand)) { michael@0: /* michael@0: * Mark empty arrays as non-constant, since we cannot easily michael@0: * determine their type. michael@0: */ michael@0: handler.setListFlag(literal, PNX_NONCONST); michael@0: } else if (tokenStream.matchToken(TOK_FOR, TokenStream::Operand)) { michael@0: // ES6 array comprehension. michael@0: return arrayComprehension(begin); michael@0: } else { michael@0: bool spread = false, missingTrailingComma = false; michael@0: uint32_t index = 0; michael@0: for (; ; index++) { michael@0: if (index == JSObject::NELEMENTS_LIMIT) { michael@0: report(ParseError, false, null(), JSMSG_ARRAY_INIT_TOO_BIG); michael@0: return null(); michael@0: } michael@0: michael@0: TokenKind tt = tokenStream.peekToken(TokenStream::Operand); michael@0: if (tt == TOK_RB) michael@0: break; michael@0: michael@0: if (tt == TOK_COMMA) { michael@0: tokenStream.consumeKnownToken(TOK_COMMA); michael@0: if (!handler.addElision(literal, pos())) michael@0: return null(); michael@0: } else if (tt == TOK_TRIPLEDOT) { michael@0: spread = true; michael@0: tokenStream.consumeKnownToken(TOK_TRIPLEDOT); michael@0: uint32_t begin = pos().begin; michael@0: Node inner = assignExpr(); michael@0: if (!inner) michael@0: return null(); michael@0: if (!handler.addSpreadElement(literal, begin, inner)) michael@0: return null(); michael@0: } else { michael@0: Node element = assignExpr(); michael@0: if (!element) michael@0: return null(); michael@0: if (foldConstants && !FoldConstants(context, &element, this)) michael@0: return null(); michael@0: if (!handler.addArrayElement(literal, element)) michael@0: return null(); michael@0: } michael@0: michael@0: if (tt != TOK_COMMA) { michael@0: /* If we didn't already match TOK_COMMA in above case. */ michael@0: if (!tokenStream.matchToken(TOK_COMMA)) { michael@0: missingTrailingComma = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * At this point, (index == 0 && missingTrailingComma) implies one michael@0: * element initialiser was parsed. michael@0: * michael@0: * A legacy array comprehension of the form: michael@0: * michael@0: * [i * j for (i in o) for (j in p) if (i != j)] michael@0: * michael@0: * translates to roughly the following let expression: michael@0: * michael@0: * let (array = new Array, i, j) { michael@0: * for (i in o) let { michael@0: * for (j in p) michael@0: * if (i != j) michael@0: * array.push(i * j) michael@0: * } michael@0: * array michael@0: * } michael@0: * michael@0: * where array is a nameless block-local variable. The "roughly" means michael@0: * that an implementation may optimize away the array.push. A legacy michael@0: * array comprehension opens exactly one block scope, no matter how many michael@0: * for heads it contains. michael@0: * michael@0: * Each let () {...} or for (let ...) ... compiles to: michael@0: * michael@0: * JSOP_PUSHN // Push space for block-scoped locals. michael@0: * (JSOP_PUSHBLOCKSCOPE ) // If a local is aliased, push on scope michael@0: * // chain. michael@0: * ... michael@0: * JSOP_DEBUGLEAVEBLOCK // Invalidate any DebugScope proxies. michael@0: * JSOP_POPBLOCKSCOPE? // Pop off scope chain, if needed. michael@0: * JSOP_POPN // Pop space for block-scoped locals. michael@0: * michael@0: * where is a literal object representing the block scope, michael@0: * with properties, naming each var declared in the block. michael@0: * michael@0: * Each var declaration in a let-block binds a name in at compile michael@0: * time. A block-local var is accessed by the JSOP_GETLOCAL and michael@0: * JSOP_SETLOCAL ops. These ops have an immediate operand, the local michael@0: * slot's stack index from fp->spbase. michael@0: * michael@0: * The legacy array comprehension iteration step, array.push(i * j) in michael@0: * the example above, is done by ; JSOP_ARRAYPUSH , where michael@0: * is the index of array's stack slot. michael@0: */ michael@0: if (index == 0 && !spread && tokenStream.matchToken(TOK_FOR) && missingTrailingComma) michael@0: return legacyArrayComprehension(literal); michael@0: michael@0: MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_AFTER_LIST); michael@0: } michael@0: handler.setEndPosition(literal, pos().end); michael@0: return literal; michael@0: } michael@0: michael@0: static JSAtom* michael@0: DoubleToAtom(ExclusiveContext *cx, double value) michael@0: { michael@0: // This is safe because doubles can not be moved. michael@0: Value tmp = DoubleValue(value); michael@0: return ToAtom(cx, HandleValue::fromMarkedLocation(&tmp)); michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::objectLiteral() michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_LC)); michael@0: michael@0: /* michael@0: * A map from property names we've seen thus far to a mask of property michael@0: * assignment types. michael@0: */ michael@0: AtomIndexMap seen; michael@0: michael@0: enum AssignmentType { michael@0: GET = 0x1, michael@0: SET = 0x2, michael@0: VALUE = 0x4 | GET | SET michael@0: }; michael@0: michael@0: Node literal = handler.newObjectLiteral(pos().begin); michael@0: if (!literal) michael@0: return null(); michael@0: michael@0: RootedAtom atom(context); michael@0: for (;;) { michael@0: TokenKind ltok = tokenStream.getToken(TokenStream::KeywordIsName); michael@0: if (ltok == TOK_RC) michael@0: break; michael@0: michael@0: JSOp op = JSOP_INITPROP; michael@0: Node propname; michael@0: switch (ltok) { michael@0: case TOK_NUMBER: michael@0: atom = DoubleToAtom(context, tokenStream.currentToken().number()); michael@0: if (!atom) michael@0: return null(); michael@0: propname = newNumber(tokenStream.currentToken()); michael@0: break; michael@0: michael@0: case TOK_NAME: { michael@0: atom = tokenStream.currentName(); michael@0: if (atom == context->names().get) { michael@0: op = JSOP_INITPROP_GETTER; michael@0: } else if (atom == context->names().set) { michael@0: op = JSOP_INITPROP_SETTER; michael@0: } else { michael@0: propname = handler.newIdentifier(atom, pos()); michael@0: if (!propname) michael@0: return null(); michael@0: break; michael@0: } michael@0: michael@0: // We have parsed |get| or |set|. Look for an accessor property michael@0: // name next. michael@0: TokenKind tt = tokenStream.getToken(TokenStream::KeywordIsName); michael@0: if (tt == TOK_NAME) { michael@0: atom = tokenStream.currentName(); michael@0: propname = newName(atom->asPropertyName()); michael@0: if (!propname) michael@0: return null(); michael@0: } else if (tt == TOK_STRING) { michael@0: atom = tokenStream.currentToken().atom(); michael@0: michael@0: uint32_t index; michael@0: if (atom->isIndex(&index)) { michael@0: propname = handler.newNumber(index, NoDecimal, pos()); michael@0: if (!propname) michael@0: return null(); michael@0: atom = DoubleToAtom(context, index); michael@0: if (!atom) michael@0: return null(); michael@0: } else { michael@0: propname = stringLiteral(); michael@0: if (!propname) michael@0: return null(); michael@0: } michael@0: } else if (tt == TOK_NUMBER) { michael@0: atom = DoubleToAtom(context, tokenStream.currentToken().number()); michael@0: if (!atom) michael@0: return null(); michael@0: propname = newNumber(tokenStream.currentToken()); michael@0: if (!propname) michael@0: return null(); michael@0: } else { michael@0: // Not an accessor property after all. michael@0: tokenStream.ungetToken(); michael@0: propname = handler.newIdentifier(atom, pos()); michael@0: if (!propname) michael@0: return null(); michael@0: op = JSOP_INITPROP; michael@0: break; michael@0: } michael@0: michael@0: JS_ASSERT(op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER); michael@0: break; michael@0: } michael@0: michael@0: case TOK_STRING: { michael@0: atom = tokenStream.currentToken().atom(); michael@0: uint32_t index; michael@0: if (atom->isIndex(&index)) { michael@0: propname = handler.newNumber(index, NoDecimal, pos()); michael@0: if (!propname) michael@0: return null(); michael@0: } else { michael@0: propname = stringLiteral(); michael@0: if (!propname) michael@0: return null(); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: default: michael@0: report(ParseError, false, null(), JSMSG_BAD_PROP_ID); michael@0: return null(); michael@0: } michael@0: michael@0: if (op == JSOP_INITPROP) { michael@0: TokenKind tt = tokenStream.getToken(); michael@0: Node propexpr; michael@0: if (tt == TOK_COLON) { michael@0: propexpr = assignExpr(); michael@0: if (!propexpr) michael@0: return null(); michael@0: michael@0: if (foldConstants && !FoldConstants(context, &propexpr, this)) michael@0: return null(); michael@0: michael@0: /* michael@0: * Treat initializers which mutate __proto__ as non-constant, michael@0: * so that we can later assume singleton objects delegate to michael@0: * the default Object.prototype. michael@0: */ michael@0: if (!handler.isConstant(propexpr) || atom == context->names().proto) michael@0: handler.setListFlag(literal, PNX_NONCONST); michael@0: michael@0: if (!handler.addPropertyDefinition(literal, propname, propexpr)) michael@0: return null(); michael@0: } michael@0: else if (ltok == TOK_NAME && (tt == TOK_COMMA || tt == TOK_RC)) { michael@0: /* michael@0: * Support, e.g., |var {x, y} = o| as destructuring shorthand michael@0: * for |var {x: x, y: y} = o|, per proposed JS2/ES4 for JS1.8. michael@0: */ michael@0: if (!abortIfSyntaxParser()) michael@0: return null(); michael@0: tokenStream.ungetToken(); michael@0: if (!tokenStream.checkForKeyword(atom->charsZ(), atom->length(), nullptr)) michael@0: return null(); michael@0: PropertyName *name = handler.isName(propname); michael@0: JS_ASSERT(atom); michael@0: propname = newName(name); michael@0: if (!propname) michael@0: return null(); michael@0: if (!handler.addShorthandPropertyDefinition(literal, propname)) michael@0: return null(); michael@0: } michael@0: else { michael@0: report(ParseError, false, null(), JSMSG_COLON_AFTER_ID); michael@0: return null(); michael@0: } michael@0: } else { michael@0: /* NB: Getter function in { get x(){} } is unnamed. */ michael@0: Rooted funName(context, nullptr); michael@0: TokenStream::Position start(keepAtoms); michael@0: tokenStream.tell(&start); michael@0: Node accessor = functionDef(funName, start, op == JSOP_INITPROP_GETTER ? Getter : Setter, michael@0: Expression, NotGenerator); michael@0: if (!accessor) michael@0: return null(); michael@0: if (!handler.addAccessorPropertyDefinition(literal, propname, accessor, op)) michael@0: return null(); michael@0: } michael@0: michael@0: /* michael@0: * Check for duplicate property names. Duplicate data properties michael@0: * only conflict in strict mode. Duplicate getter or duplicate michael@0: * setter halves always conflict. A data property conflicts with michael@0: * any part of an accessor property. michael@0: */ michael@0: AssignmentType assignType; michael@0: if (op == JSOP_INITPROP) michael@0: assignType = VALUE; michael@0: else if (op == JSOP_INITPROP_GETTER) michael@0: assignType = GET; michael@0: else if (op == JSOP_INITPROP_SETTER) michael@0: assignType = SET; michael@0: else michael@0: MOZ_ASSUME_UNREACHABLE("bad opcode in object initializer"); michael@0: michael@0: AtomIndexAddPtr p = seen.lookupForAdd(atom); michael@0: if (p) { michael@0: jsatomid index = p.value(); michael@0: AssignmentType oldAssignType = AssignmentType(index); michael@0: if ((oldAssignType & assignType) && michael@0: (oldAssignType != VALUE || assignType != VALUE || pc->sc->needStrictChecks())) michael@0: { michael@0: JSAutoByteString name; michael@0: if (!AtomToPrintableString(context, atom, &name)) michael@0: return null(); michael@0: michael@0: ParseReportKind reportKind = michael@0: (oldAssignType == VALUE && assignType == VALUE && !pc->sc->needStrictChecks()) michael@0: ? ParseWarning michael@0: : (pc->sc->needStrictChecks() ? ParseStrictError : ParseError); michael@0: if (!report(reportKind, pc->sc->strict, null(), michael@0: JSMSG_DUPLICATE_PROPERTY, name.ptr())) michael@0: { michael@0: return null(); michael@0: } michael@0: } michael@0: p.value() = assignType | oldAssignType; michael@0: } else { michael@0: if (!seen.add(p, atom, assignType)) michael@0: return null(); michael@0: } michael@0: michael@0: TokenKind tt = tokenStream.getToken(); michael@0: if (tt == TOK_RC) michael@0: break; michael@0: if (tt != TOK_COMMA) { michael@0: report(ParseError, false, null(), JSMSG_CURLY_AFTER_LIST); michael@0: return null(); michael@0: } michael@0: } michael@0: michael@0: handler.setEndPosition(literal, pos().end); michael@0: return literal; michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::primaryExpr(TokenKind tt) michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(tt)); michael@0: JS_CHECK_RECURSION(context, return null()); michael@0: michael@0: switch (tt) { michael@0: case TOK_FUNCTION: michael@0: return functionExpr(); michael@0: michael@0: case TOK_LB: michael@0: return arrayInitializer(); michael@0: michael@0: case TOK_LC: michael@0: return objectLiteral(); michael@0: michael@0: case TOK_LET: michael@0: return letBlock(LetExpresion); michael@0: michael@0: case TOK_LP: michael@0: return parenExprOrGeneratorComprehension(); michael@0: michael@0: case TOK_STRING: michael@0: return stringLiteral(); michael@0: michael@0: case TOK_YIELD: michael@0: if (!checkYieldNameValidity()) michael@0: return null(); michael@0: // Fall through. michael@0: case TOK_NAME: michael@0: return identifierName(); michael@0: michael@0: case TOK_REGEXP: michael@0: return newRegExp(); michael@0: michael@0: case TOK_NUMBER: michael@0: return newNumber(tokenStream.currentToken()); michael@0: michael@0: case TOK_TRUE: michael@0: return handler.newBooleanLiteral(true, pos()); michael@0: case TOK_FALSE: michael@0: return handler.newBooleanLiteral(false, pos()); michael@0: case TOK_THIS: michael@0: return handler.newThisLiteral(pos()); michael@0: case TOK_NULL: michael@0: return handler.newNullLiteral(pos()); michael@0: michael@0: case TOK_RP: michael@0: // Not valid expression syntax, but this is valid in an arrow function michael@0: // with no params: `() => body`. michael@0: if (tokenStream.peekToken() == TOK_ARROW) { michael@0: tokenStream.ungetToken(); // put back right paren michael@0: michael@0: // Now just return something that will allow parsing to continue. michael@0: // It doesn't matter what; when we reach the =>, we will rewind and michael@0: // reparse the whole arrow function. See Parser::assignExpr. michael@0: return handler.newNullLiteral(pos()); michael@0: } michael@0: report(ParseError, false, null(), JSMSG_SYNTAX_ERROR); michael@0: return null(); michael@0: michael@0: case TOK_TRIPLEDOT: michael@0: // Not valid expression syntax, but this is valid in an arrow function michael@0: // with a rest param: `(a, b, ...rest) => body`. michael@0: if (tokenStream.matchToken(TOK_NAME) && michael@0: tokenStream.matchToken(TOK_RP) && michael@0: tokenStream.peekToken() == TOK_ARROW) michael@0: { michael@0: tokenStream.ungetToken(); // put back right paren michael@0: michael@0: // Return an arbitrary expression node. See case TOK_RP above. michael@0: return handler.newNullLiteral(pos()); michael@0: } michael@0: report(ParseError, false, null(), JSMSG_SYNTAX_ERROR); michael@0: return null(); michael@0: michael@0: case TOK_ERROR: michael@0: /* The scanner or one of its subroutines reported the error. */ michael@0: return null(); michael@0: michael@0: default: michael@0: report(ParseError, false, null(), JSMSG_SYNTAX_ERROR); michael@0: return null(); michael@0: } michael@0: } michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::parenExprOrGeneratorComprehension() michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_LP)); michael@0: uint32_t begin = pos().begin; michael@0: uint32_t startYieldOffset = pc->lastYieldOffset; michael@0: michael@0: if (tokenStream.matchToken(TOK_FOR, TokenStream::Operand)) michael@0: return generatorComprehension(begin); michael@0: michael@0: /* michael@0: * Always accept the 'in' operator in a parenthesized expression, michael@0: * where it's unambiguous, even if we might be parsing the init of a michael@0: * for statement. michael@0: */ michael@0: bool oldParsingForInit = pc->parsingForInit; michael@0: pc->parsingForInit = false; michael@0: Node pn = expr(); michael@0: pc->parsingForInit = oldParsingForInit; michael@0: michael@0: if (!pn) michael@0: return null(); michael@0: michael@0: #if JS_HAS_GENERATOR_EXPRS michael@0: if (tokenStream.matchToken(TOK_FOR)) { michael@0: if (pc->lastYieldOffset != startYieldOffset) { michael@0: reportWithOffset(ParseError, false, pc->lastYieldOffset, michael@0: JSMSG_BAD_GENEXP_BODY, js_yield_str); michael@0: return null(); michael@0: } michael@0: if (handler.isOperationWithoutParens(pn, PNK_COMMA)) { michael@0: report(ParseError, false, null(), michael@0: JSMSG_BAD_GENERATOR_SYNTAX, js_generator_str); michael@0: return null(); michael@0: } michael@0: pn = legacyGeneratorExpr(pn); michael@0: if (!pn) michael@0: return null(); michael@0: handler.setBeginPosition(pn, begin); michael@0: if (tokenStream.getToken() != TOK_RP) { michael@0: report(ParseError, false, null(), michael@0: JSMSG_BAD_GENERATOR_SYNTAX, js_generator_str); michael@0: return null(); michael@0: } michael@0: handler.setEndPosition(pn, pos().end); michael@0: handler.setInParens(pn); michael@0: return pn; michael@0: } michael@0: #endif /* JS_HAS_GENERATOR_EXPRS */ michael@0: michael@0: pn = handler.setInParens(pn); michael@0: michael@0: MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_IN_PAREN); michael@0: michael@0: return pn; michael@0: } michael@0: michael@0: // Legacy generator comprehensions can sometimes appear without parentheses. michael@0: // For example: michael@0: // michael@0: // foo(x for (x in bar)) michael@0: // michael@0: // In this case the parens are part of the call, and not part of the generator michael@0: // comprehension. This can happen in these contexts: michael@0: // michael@0: // if (_) michael@0: // while (_) {} michael@0: // do {} while (_) michael@0: // switch (_) {} michael@0: // with (_) {} michael@0: // foo(_) // must be first and only argument michael@0: // michael@0: // This is not the case for ES6 generator comprehensions; they must always be in michael@0: // parentheses. michael@0: michael@0: template michael@0: typename ParseHandler::Node michael@0: Parser::exprInParens() michael@0: { michael@0: JS_ASSERT(tokenStream.isCurrentTokenType(TOK_LP)); michael@0: uint32_t begin = pos().begin; michael@0: uint32_t startYieldOffset = pc->lastYieldOffset; michael@0: michael@0: /* michael@0: * Always accept the 'in' operator in a parenthesized expression, michael@0: * where it's unambiguous, even if we might be parsing the init of a michael@0: * for statement. michael@0: */ michael@0: bool oldParsingForInit = pc->parsingForInit; michael@0: pc->parsingForInit = false; michael@0: Node pn = expr(); michael@0: pc->parsingForInit = oldParsingForInit; michael@0: michael@0: if (!pn) michael@0: return null(); michael@0: michael@0: #if JS_HAS_GENERATOR_EXPRS michael@0: if (tokenStream.matchToken(TOK_FOR)) { michael@0: if (pc->lastYieldOffset != startYieldOffset) { michael@0: reportWithOffset(ParseError, false, pc->lastYieldOffset, michael@0: JSMSG_BAD_GENEXP_BODY, js_yield_str); michael@0: return null(); michael@0: } michael@0: if (handler.isOperationWithoutParens(pn, PNK_COMMA)) { michael@0: report(ParseError, false, null(), michael@0: JSMSG_BAD_GENERATOR_SYNTAX, js_generator_str); michael@0: return null(); michael@0: } michael@0: pn = legacyGeneratorExpr(pn); michael@0: if (!pn) michael@0: return null(); michael@0: handler.setBeginPosition(pn, begin); michael@0: } michael@0: #endif /* JS_HAS_GENERATOR_EXPRS */ michael@0: michael@0: return pn; michael@0: } michael@0: michael@0: template class Parser; michael@0: template class Parser; michael@0: michael@0: } /* namespace frontend */ michael@0: } /* namespace js */