| //------------------------------------------------------------------------------------------------------- |
| // Copyright (C) Microsoft. All rights reserved. |
| // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. |
| //------------------------------------------------------------------------------------------------------- |
| #include "ParserPch.h" |
| #include "FormalsUtil.h" |
| #include "../Runtime/Language/SourceDynamicProfileManager.h" |
| |
| #include "ByteCode/ByteCodeSerializer.h" |
| |
| |
| #if DBG_DUMP |
| void PrintPnodeWIndent(ParseNode *pnode, int indentAmt); |
| #endif |
| |
| const char* const nopNames[knopLim] = { |
| #define PTNODE(nop,sn,pc,nk,grfnop,json) sn, |
| #include "ptlist.h" |
| }; |
| void printNop(int nop) { |
| Output::Print(_u("%S\n"), nopNames[nop]); |
| } |
| |
| const uint ParseNode::mpnopgrfnop[knopLim] = |
| { |
| #define PTNODE(nop,sn,pc,nk,grfnop,json) grfnop, |
| #include "ptlist.h" |
| }; |
| |
| bool Parser::IsES6DestructuringEnabled() const |
| { |
| return m_scriptContext->GetConfig()->IsES6DestructuringEnabled(); |
| } |
| |
| struct BlockInfoStack |
| { |
| StmtNest pstmt; |
| ParseNodeBlock *pnodeBlock; |
| ParseNodePtr *m_ppnodeLex; // lexical variable list tail |
| BlockInfoStack *pBlockInfoOuter; // containing block's BlockInfoStack |
| BlockInfoStack *pBlockInfoFunction; // nearest function's BlockInfoStack (if pnodeBlock is a function, this points to itself) |
| }; |
| |
| #if DEBUG |
| Parser::Parser(Js::ScriptContext* scriptContext, BOOL strictMode, PageAllocator *alloc, bool isBackground, size_t size) |
| #else |
| Parser::Parser(Js::ScriptContext* scriptContext, BOOL strictMode, PageAllocator *alloc, bool isBackground) |
| #endif |
| : m_nodeAllocator(_u("Parser"), alloc ? alloc : scriptContext->GetThreadContext()->GetPageAllocator(), Parser::OutOfMemory), |
| m_cactIdentToNodeLookup(0), |
| m_grfscr(fscrNil), |
| m_length(0), |
| m_originalLength(0), |
| m_nextFunctionId(nullptr), |
| m_sourceContextInfo(nullptr), |
| #if ENABLE_BACKGROUND_PARSING |
| m_isInBackground(isBackground), |
| m_hasParallelJob(false), |
| m_doingFastScan(false), |
| #endif |
| m_nextBlockId(0), |
| m_tempGuestArenaReleased(false), |
| m_tempGuestArena(scriptContext->GetTemporaryGuestAllocator(_u("ParserRegex")), scriptContext->GetRecycler()), |
| // use the GuestArena directly for keeping the RegexPattern* alive during byte code generation |
| m_registeredRegexPatterns(m_tempGuestArena->GetAllocator()), |
| |
| m_scriptContext(scriptContext), |
| m_token(), // should initialize to 0/nullptrs |
| m_scan(this, &m_token, scriptContext), |
| |
| m_currentNodeNonLambdaFunc(nullptr), |
| m_currentNodeNonLambdaDeferredFunc(nullptr), |
| m_currentNodeFunc(nullptr), |
| m_currentNodeDeferredFunc(nullptr), |
| m_currentNodeProg(nullptr), |
| m_currDeferredStub(nullptr), |
| m_currDeferredStubCount(0), |
| m_pCurrentAstSize(nullptr), |
| m_ppnodeScope(nullptr), |
| m_ppnodeExprScope(nullptr), |
| m_ppnodeVar(nullptr), |
| m_inDeferredNestedFunc(false), |
| m_reparsingLambdaParams(false), |
| m_disallowImportExportStmt(false), |
| m_isInParsingArgList(false), |
| m_hasDestructuringPattern(false), |
| m_hasDeferredShorthandInitError(false), |
| m_deferCommaError(false), |
| m_pnestedCount(nullptr), |
| |
| wellKnownPropertyPids(), // should initialize to nullptrs |
| m_sourceLim(0), |
| m_functionBody(nullptr), |
| m_parseType(ParseType_Upfront), |
| |
| m_arrayDepth(0), |
| m_funcInArrayDepth(0), |
| m_funcInArray(0), |
| m_scopeCountNoAst(0), |
| |
| m_funcParenExprDepth(0), |
| m_deferEllipsisError(false), |
| m_deferEllipsisErrorLoc(), // calls default initializer |
| m_deferCommaErrorLoc(), |
| m_tryCatchOrFinallyDepth(0), |
| |
| m_pstmtCur(nullptr), |
| m_currentBlockInfo(nullptr), |
| m_currentScope(nullptr), |
| |
| currBackgroundParseItem(nullptr), |
| backgroundParseItems(nullptr), |
| fastScannedRegExpNodes(nullptr), |
| |
| m_currentDynamicBlock(nullptr), |
| |
| m_UsesArgumentsAtGlobal(false), |
| |
| m_fUseStrictMode(strictMode), |
| m_InAsmMode(false), |
| m_deferAsmJs(true), |
| m_fExpectExternalSource(FALSE), |
| m_deferringAST(FALSE), |
| m_stoppedDeferredParse(FALSE) |
| { |
| AssertMsg(size == sizeof(Parser), "verify conditionals affecting the size of Parser agree"); |
| Assert(scriptContext != nullptr); |
| |
| // init PID members |
| InitPids(); |
| } |
| |
| Parser::~Parser(void) |
| { |
| this->ReleaseTemporaryGuestArena(); |
| |
| #if ENABLE_BACKGROUND_PARSING |
| if (this->m_hasParallelJob) |
| { |
| // Let the background threads know that they can decommit their arena pages. |
| BackgroundParser *bgp = m_scriptContext->GetBackgroundParser(); |
| Assert(bgp); |
| if (bgp->Processor()->ProcessesInBackground()) |
| { |
| JsUtil::BackgroundJobProcessor *processor = static_cast<JsUtil::BackgroundJobProcessor*>(bgp->Processor()); |
| |
| bool result = processor->IterateBackgroundThreads([&](JsUtil::ParallelThreadData *threadData)->bool { |
| threadData->canDecommit = true; |
| return false; |
| }); |
| Assert(result); |
| } |
| } |
| #endif |
| } |
| |
| void Parser::OutOfMemory() |
| { |
| throw ParseExceptionObject(ERRnoMemory); |
| } |
| |
| LPCWSTR Parser::GetTokenString(tokens token) |
| { |
| switch (token) |
| { |
| case tkNone : return _u(""); |
| case tkEOF : return _u("end of script"); |
| case tkIntCon : return _u("integer literal"); |
| case tkFltCon : return _u("float literal"); |
| case tkStrCon : return _u("string literal"); |
| case tkRegExp : return _u("regular expression literal"); |
| |
| // keywords |
| case tkABSTRACT : return _u("abstract"); |
| case tkASSERT : return _u("assert"); |
| case tkAWAIT : return _u("await"); |
| case tkBOOLEAN : return _u("boolean"); |
| case tkBREAK : return _u("break"); |
| case tkBYTE : return _u("byte"); |
| case tkCASE : return _u("case"); |
| case tkCATCH : return _u("catch"); |
| case tkCHAR : return _u("char"); |
| case tkCONTINUE : return _u("continue"); |
| case tkDEBUGGER : return _u("debugger"); |
| case tkDECIMAL : return _u("decimal"); |
| case tkDEFAULT : return _u("default"); |
| case tkDELETE : return _u("delete"); |
| case tkDO : return _u("do"); |
| case tkDOUBLE : return _u("double"); |
| case tkELSE : return _u("else"); |
| case tkENSURE : return _u("ensure"); |
| case tkEVENT : return _u("event"); |
| case tkFALSE : return _u("false"); |
| case tkFINAL : return _u("final"); |
| case tkFINALLY : return _u("finally"); |
| case tkFLOAT : return _u("float"); |
| case tkFOR : return _u("for"); |
| case tkFUNCTION : return _u("function"); |
| case tkGET : return _u("get"); |
| case tkGOTO : return _u("goto"); |
| case tkIF : return _u("if"); |
| case tkIN : return _u("in"); |
| case tkINSTANCEOF : return _u("instanceof"); |
| case tkINT : return _u("int"); |
| case tkINTERNAL : return _u("internal"); |
| case tkINVARIANT : return _u("invariant"); |
| case tkLONG : return _u("long"); |
| case tkNAMESPACE : return _u("namespace"); |
| case tkNATIVE : return _u("native"); |
| case tkNEW : return _u("new"); |
| case tkNULL : return _u("null"); |
| case tkREQUIRE : return _u("require"); |
| case tkRETURN : return _u("return"); |
| case tkSBYTE : return _u("sbyte"); |
| case tkSET : return _u("set"); |
| case tkSHORT : return _u("short"); |
| case tkSWITCH : return _u("switch"); |
| case tkSYNCHRONIZED : return _u("synchronized"); |
| case tkTHIS : return _u("this"); |
| case tkTHROW : return _u("throw"); |
| case tkTHROWS : return _u("throws"); |
| case tkTRANSIENT : return _u("transient"); |
| case tkTRUE : return _u("true"); |
| case tkTRY : return _u("try"); |
| case tkTYPEOF : return _u("typeof"); |
| case tkUINT : return _u("uint"); |
| case tkULONG : return _u("ulong"); |
| case tkUSE : return _u("use"); |
| case tkUSHORT : return _u("ushort"); |
| case tkVAR : return _u("var"); |
| case tkVOID : return _u("void"); |
| case tkVOLATILE : return _u("volatile"); |
| case tkWHILE : return _u("while"); |
| case tkWITH : return _u("with"); |
| |
| // Future reserved words that become keywords in ES6 |
| case tkCLASS : return _u("class"); |
| case tkCONST : return _u("const"); |
| case tkEXPORT : return _u("export"); |
| case tkEXTENDS : return _u("extends"); |
| case tkIMPORT : return _u("import"); |
| case tkLET : return _u("let"); |
| case tkSUPER : return _u("super"); |
| case tkYIELD : return _u("yield"); |
| |
| // Future reserved words in strict and non-strict modes |
| case tkENUM : return _u("enum"); |
| |
| // Additional future reserved words in strict mode |
| case tkIMPLEMENTS : return _u("implements"); |
| case tkINTERFACE : return _u("interface"); |
| case tkPACKAGE : return _u("package"); |
| case tkPRIVATE : return _u("private"); |
| case tkPROTECTED : return _u("protected"); |
| case tkPUBLIC : return _u("public"); |
| case tkSTATIC : return _u("static"); |
| |
| case tkID: return _u("identifier"); |
| |
| // Non-operator non-identifier tokens |
| case tkSColon: return _u(";"); |
| case tkRParen: return _u(")"); |
| case tkRBrack: return _u("]"); |
| case tkLCurly: return _u("{"); |
| case tkRCurly: return _u("}"); |
| |
| // Operator non-identifier tokens |
| case tkComma: return _u(","); |
| case tkDArrow: return _u("=>"); |
| case tkAsg: return _u("="); |
| case tkAsgAdd: return _u("+="); |
| case tkAsgSub: return _u("-="); |
| case tkAsgMul: return _u("*="); |
| case tkAsgDiv: return _u("/="); |
| case tkAsgExpo: return _u("**="); |
| case tkAsgMod: return _u("%="); |
| case tkAsgAnd: return _u("&="); |
| case tkAsgXor: return _u("^="); |
| case tkAsgOr: return _u("|="); |
| case tkAsgLsh: return _u("<<="); |
| case tkAsgRsh: return _u(">>="); |
| case tkAsgRs2: return _u(">>>="); |
| case tkQMark: return _u("?"); |
| case tkColon: return _u(":"); |
| case tkLogOr: return _u("||"); |
| case tkLogAnd: return _u("&&"); |
| case tkOr: return _u("|"); |
| case tkXor: return _u("^"); |
| case tkAnd: return _u("&"); |
| case tkEQ: return _u("=="); |
| case tkNE: return _u("!="); |
| case tkEqv: return _u("==="); |
| case tkNEqv: return _u("!=="); |
| case tkLT: return _u("<"); |
| case tkLE: return _u("<="); |
| case tkGT: return _u(">"); |
| case tkGE: return _u(">="); |
| case tkLsh: return _u("<<"); |
| case tkRsh: return _u(">>"); |
| case tkRs2: return _u(">>>"); |
| case tkAdd: return _u("+"); |
| case tkSub: return _u("-"); |
| case tkExpo: return _u("**"); |
| case tkStar: return _u("*"); |
| case tkDiv: return _u("/"); |
| case tkPct: return _u("%"); |
| case tkTilde: return _u("~"); |
| case tkBang: return _u("!"); |
| case tkInc: return _u("++"); |
| case tkDec: return _u("--"); |
| case tkEllipsis: return _u("..."); |
| case tkLParen: return _u("("); |
| case tkLBrack: return _u("["); |
| case tkDot: return _u("."); |
| |
| default: |
| return _u("unknown token"); |
| } |
| } |
| |
| void Parser::Error(HRESULT hr, LPCWSTR stringOne, LPCWSTR stringTwo) |
| { |
| throw ParseExceptionObject(hr, stringOne, stringTwo); |
| } |
| |
| void Parser::Error(HRESULT hr, ParseNodePtr pnode) |
| { |
| if (pnode && pnode->ichLim) |
| { |
| Error(hr, pnode->ichMin, pnode->ichLim); |
| } |
| else |
| { |
| Error(hr); |
| } |
| } |
| |
| void Parser::Error(HRESULT hr, charcount_t ichMin, charcount_t ichLim, LPCWSTR stringOne, LPCWSTR stringTwo) |
| { |
| this->GetScanner()->SetErrorPosition(ichMin, ichLim); |
| Error(hr, stringOne, stringTwo); |
| } |
| |
| void Parser::IdentifierExpectedError(const Token& token) |
| { |
| Assert(token.tk != tkID); |
| |
| HRESULT hr; |
| if (token.IsReservedWord()) |
| { |
| if (token.IsKeyword()) |
| { |
| hr = ERRKeywordNotId; |
| } |
| else |
| { |
| Assert(token.IsFutureReservedWord(true)); |
| if (token.IsFutureReservedWord(false)) |
| { |
| // Future reserved word in strict and non-strict modes |
| hr = ERRFutureReservedWordNotId; |
| } |
| else |
| { |
| // Future reserved word only in strict mode. The token would have been converted to tkID by the scanner if not |
| // in strict mode. |
| Assert(IsStrictMode()); |
| hr = ERRFutureReservedWordInStrictModeNotId; |
| } |
| } |
| } |
| else |
| { |
| hr = ERRnoIdent; |
| } |
| |
| Error(hr); |
| } |
| |
| HRESULT Parser::ValidateSyntax(LPCUTF8 pszSrc, size_t encodedCharCount, bool isGenerator, bool isAsync, CompileScriptException *pse, void (Parser::*validateFunction)()) |
| { |
| Assert(pszSrc); |
| |
| PROBE_STACK_NO_DISPOSE(m_scriptContext, Js::Constants::MinStackDefault); |
| |
| HRESULT hr; |
| SmartFPUControl smartFpuControl; |
| bool handled = false; |
| |
| BOOL fDeferSave = m_deferringAST; |
| try |
| { |
| hr = NOERROR; |
| |
| m_length = encodedCharCount; |
| m_originalLength = encodedCharCount; |
| |
| // make sure deferred parsing is turned off |
| ULONG grfscr = fscrNil; |
| |
| // Give the scanner the source and get the first token |
| this->GetScanner()->SetText(pszSrc, 0, encodedCharCount, 0, false, grfscr); |
| this->GetScanner()->SetYieldIsKeywordRegion(isGenerator); |
| this->GetScanner()->SetAwaitIsKeywordRegion(isAsync); |
| this->GetScanner()->Scan(); |
| |
| uint nestedCount = 0; |
| m_pnestedCount = &nestedCount; |
| |
| ParseNodePtr pnodeScope = nullptr; |
| m_ppnodeScope = &pnodeScope; |
| m_ppnodeExprScope = nullptr; |
| |
| uint nextFunctionId = 0; |
| m_nextFunctionId = &nextFunctionId; |
| |
| m_inDeferredNestedFunc = false; |
| m_deferringAST = true; |
| |
| m_nextBlockId = 0; |
| |
| ParseNodeFnc *pnodeFnc = CreateAllowDeferNodeForOpT<knopFncDecl>(); |
| pnodeFnc->SetIsGenerator(isGenerator); |
| pnodeFnc->SetIsAsync(isAsync); |
| |
| m_ppnodeVar = &pnodeFnc->pnodeVars; |
| m_currentNodeFunc = pnodeFnc; |
| m_currentNodeDeferredFunc = NULL; |
| m_sourceContextInfo = nullptr; |
| AssertMsg(m_pstmtCur == NULL, "Statement stack should be empty when we start parse function body"); |
| |
| ParseNodeBlock * block = StartParseBlock<false>(PnodeBlockType::Function, ScopeType_FunctionBody); |
| (this->*validateFunction)(); |
| FinishParseBlock(block); |
| |
| pnodeFnc->ichLim = this->GetScanner()->IchLimTok(); |
| pnodeFnc->cbLim = this->GetScanner()->IecpLimTok(); |
| pnodeFnc->pnodeVars = nullptr; |
| |
| // there should be nothing after successful parsing for a given construct |
| if (m_token.tk != tkEOF) |
| Error(ERRsyntax); |
| |
| m_deferringAST = fDeferSave; |
| } |
| catch (ParseExceptionObject& e) |
| { |
| m_deferringAST = fDeferSave; |
| hr = e.GetError(); |
| hr = pse->ProcessError(this->GetScanner(), hr, /* pnodeBase */ NULL, e.GetStringOne(), e.GetStringTwo()); |
| handled = true; |
| } |
| |
| if (handled == false && nullptr != pse && FAILED(hr)) |
| { |
| hr = pse->ProcessError(this->GetScanner(), hr, /* pnodeBase */ NULL); |
| } |
| return hr; |
| } |
| |
| HRESULT Parser::ParseSourceInternal( |
| __out ParseNodeProg ** parseTree, LPCUTF8 pszSrc, size_t offsetInBytes, size_t encodedCharCount, charcount_t offsetInChars, |
| bool isUtf8, ULONG grfscr, CompileScriptException *pse, Js::LocalFunctionId * nextFunctionId, ULONG lineNumber, SourceContextInfo * sourceContextInfo) |
| { |
| Assert(parseTree); |
| Assert(pszSrc); |
| |
| if (this->IsBackgroundParser()) |
| { |
| PROBE_STACK_NO_DISPOSE(m_scriptContext, Js::Constants::MinStackDefault); |
| } |
| else |
| { |
| PROBE_STACK(m_scriptContext, Js::Constants::MinStackDefault); |
| } |
| |
| #ifdef PROFILE_EXEC |
| m_scriptContext->ProfileBegin(Js::ParsePhase); |
| #endif |
| JS_ETW_INTERNAL(EventWriteJSCRIPT_PARSE_START(m_scriptContext, 0)); |
| |
| *parseTree = NULL; |
| m_sourceLim = 0; |
| |
| m_grfscr = grfscr; |
| m_sourceContextInfo = sourceContextInfo; |
| |
| ParseNodeProg * pnodeBase = NULL; |
| HRESULT hr; |
| SmartFPUControl smartFpuControl; |
| bool handled = false; |
| |
| try |
| { |
| if ((grfscr & fscrIsModuleCode) != 0) |
| { |
| // Module source flag should not be enabled unless module is enabled |
| Assert(m_scriptContext->GetConfig()->IsES6ModuleEnabled()); |
| |
| // Module code is always strict mode code. |
| this->m_fUseStrictMode = TRUE; |
| } |
| |
| if ((grfscr & fscrUseStrictMode) != 0) |
| { |
| this->m_fUseStrictMode = TRUE; |
| } |
| |
| // parse the source |
| pnodeBase = Parse(pszSrc, offsetInBytes, encodedCharCount, offsetInChars, isUtf8, grfscr, lineNumber, nextFunctionId, pse); |
| |
| Assert(pnodeBase); |
| |
| // Record the actual number of words parsed. |
| m_sourceLim = pnodeBase->ichLim - offsetInChars; |
| |
| // TODO: The assert can be false positive in some scenarios and chuckj to fix it later |
| // Assert(utf8::ByteIndexIntoCharacterIndex(pszSrc + offsetInBytes, encodedCharCount, isUtf8 ? utf8::doDefault : utf8::doAllowThreeByteSurrogates) == m_sourceLim); |
| |
| #if DBG_DUMP |
| if (Js::Configuration::Global.flags.Trace.IsEnabled(Js::ParsePhase)) |
| { |
| PrintPnodeWIndent(pnodeBase, 4); |
| fflush(stdout); |
| } |
| #endif |
| |
| *parseTree = pnodeBase; |
| |
| hr = NOERROR; |
| } |
| catch (ParseExceptionObject& e) |
| { |
| hr = e.GetError(); |
| hr = pse->ProcessError(this->GetScanner(), hr, pnodeBase, e.GetStringOne(), e.GetStringTwo()); |
| handled = true; |
| } |
| catch (Js::AsmJsParseException&) |
| { |
| hr = JSERR_AsmJsCompileError; |
| } |
| |
| if (handled == false && FAILED(hr)) |
| { |
| hr = pse->ProcessError(this->GetScanner(), hr, pnodeBase); |
| } |
| |
| #if ENABLE_BACKGROUND_PARSING |
| if (this->m_hasParallelJob) |
| { |
| ///// Wait here for remaining jobs to finish. Then look for errors, do final const bindings. |
| // pleath TODO: If there are remaining jobs, let the main thread help finish them. |
| BackgroundParser *bgp = m_scriptContext->GetBackgroundParser(); |
| Assert(bgp); |
| |
| CompileScriptException se; |
| this->WaitForBackgroundJobs(bgp, &se); |
| |
| BackgroundParseItem *failedItem = bgp->GetFailedBackgroundParseItem(); |
| if (failedItem) |
| { |
| CompileScriptException *bgPse = failedItem->GetPSE(); |
| Assert(bgPse); |
| *pse = *bgPse; |
| hr = failedItem->GetHR(); |
| bgp->SetFailedBackgroundParseItem(nullptr); |
| } |
| |
| if (this->fastScannedRegExpNodes != nullptr) |
| { |
| this->FinishBackgroundRegExpNodes(); |
| } |
| |
| for (BackgroundParseItem *item = this->backgroundParseItems; item; item = item->GetNext()) |
| { |
| Parser *parser = item->GetParser(); |
| parser->FinishBackgroundPidRefs(item, this != parser); |
| } |
| } |
| #endif |
| |
| // done with the scanner |
| this->GetScanner()->Clear(); |
| |
| #ifdef PROFILE_EXEC |
| m_scriptContext->ProfileEnd(Js::ParsePhase); |
| #endif |
| JS_ETW_INTERNAL(EventWriteJSCRIPT_PARSE_STOP(m_scriptContext, 0)); |
| |
| return hr; |
| } |
| |
| #if ENABLE_BACKGROUND_PARSING |
| void Parser::WaitForBackgroundJobs(BackgroundParser *bgp, CompileScriptException *pse) |
| { |
| // The scan of the script is done, but there may be unfinished background jobs in the queue. |
| // Enlist the main thread to help with those. |
| BackgroundParseItem *item; |
| if (!*bgp->GetPendingBackgroundItemsPtr()) |
| { |
| // We're done. |
| return; |
| } |
| |
| // Save parser state, since we'll need to restore it in order to bind references correctly later. |
| this->m_isInBackground = true; |
| this->SetCurrBackgroundParseItem(nullptr); |
| uint blockIdSave = this->m_nextBlockId; |
| uint functionIdSave = *this->m_nextFunctionId; |
| StmtNest *pstmtSave = this->m_pstmtCur; |
| |
| if (!bgp->Processor()->ProcessesInBackground()) |
| { |
| // No background thread. Just walk the jobs with no locking and process them. |
| for (item = bgp->GetNextUnprocessedItem(); item; item = bgp->GetNextUnprocessedItem()) |
| { |
| bgp->Processor()->RemoveJob(item); |
| bool succeeded = bgp->Process(item, this, pse); |
| bgp->JobProcessed(item, succeeded); |
| } |
| Assert(!*bgp->GetPendingBackgroundItemsPtr()); |
| } |
| else |
| { |
| // Background threads. We need to have the critical section in order to: |
| // - Check for unprocessed jobs; |
| // - Remove jobs from the processor queue; |
| // - Do JobsProcessed work (such as removing jobs from the BackgroundParser's unprocessed list). |
| CriticalSection *pcs = static_cast<JsUtil::BackgroundJobProcessor*>(bgp->Processor())->GetCriticalSection(); |
| pcs->Enter(); |
| for (;;) |
| { |
| // Grab a job (in lock) |
| item = bgp->GetNextUnprocessedItem(); |
| if (item == nullptr) |
| { |
| break; |
| } |
| bgp->Processor()->RemoveJob(item); |
| pcs->Leave(); |
| |
| // Process job (if there is one) (outside lock) |
| bool succeeded = bgp->Process(item, this, pse); |
| |
| pcs->Enter(); |
| bgp->JobProcessed(item, succeeded); |
| } |
| pcs->Leave(); |
| |
| // Wait for the background threads to finish jobs they're already processing (if any). |
| // TODO: Replace with a proper semaphore. |
| while (*bgp->GetPendingBackgroundItemsPtr()); |
| } |
| |
| Assert(!*bgp->GetPendingBackgroundItemsPtr()); |
| |
| // Restore parser state. |
| this->m_pstmtCur = pstmtSave; |
| this->m_isInBackground = false; |
| this->m_nextBlockId = blockIdSave; |
| *this->m_nextFunctionId = functionIdSave; |
| } |
| |
| void Parser::FinishBackgroundPidRefs(BackgroundParseItem *item, bool isOtherParser) |
| { |
| for (BlockInfoStack *blockInfo = item->GetParseContext()->currentBlockInfo; blockInfo; blockInfo = blockInfo->pBlockInfoOuter) |
| { |
| if (isOtherParser) |
| { |
| this->BindPidRefs<true>(blockInfo, item->GetMaxBlockId()); |
| } |
| else |
| { |
| this->BindPidRefs<false>(blockInfo, item->GetMaxBlockId()); |
| } |
| } |
| } |
| |
| void Parser::FinishBackgroundRegExpNodes() |
| { |
| // We have a list of RegExp nodes that we saw on the UI thread in functions we're parallel parsing, |
| // and for each background job we have a list of RegExp nodes for which we couldn't allocate patterns. |
| // We need to copy the pattern pointers from the UI thread nodes to the corresponding nodes on the |
| // background nodes. |
| // There may be UI thread nodes for which there are no background thread equivalents, because the UI thread |
| // has to assume that the background thread won't defer anything. |
| |
| // Note that because these lists (and the list of background jobs) are SList's built by prepending, they are |
| // all in reverse lexical order. |
| |
| Assert(!this->IsBackgroundParser()); |
| Assert(this->fastScannedRegExpNodes); |
| Assert(this->backgroundParseItems != nullptr); |
| |
| BackgroundParseItem *currBackgroundItem; |
| |
| #if DBG |
| for (currBackgroundItem = this->backgroundParseItems; |
| currBackgroundItem; |
| currBackgroundItem = currBackgroundItem->GetNext()) |
| { |
| if (currBackgroundItem->RegExpNodeList()) |
| { |
| FOREACH_DLIST_ENTRY(ParseNodePtr, ArenaAllocator, pnode, currBackgroundItem->RegExpNodeList()) |
| { |
| Assert(pnode->AsParseNodeRegExp()->regexPattern == nullptr); |
| } |
| NEXT_DLIST_ENTRY; |
| } |
| } |
| #endif |
| |
| // Hook up the patterns allocated on the main thread to the nodes created on the background thread. |
| // Walk the list of foreground nodes, advancing through the work items and looking up each item. |
| // Note that the background thread may have chosen to defer a given RegEx literal, so not every foreground |
| // node will have a matching background node. Doesn't matter for correctness. |
| // (It's inefficient, of course, to have to restart the inner loop from the beginning of the work item's |
| // list, but it should be unusual to have many RegExes in a single work item's chunk of code. Figure out how |
| // to start the inner loop from a known internal node within the list if that turns out to be important.) |
| currBackgroundItem = this->backgroundParseItems; |
| FOREACH_DLIST_ENTRY(ParseNodePtr, ArenaAllocator, pnodeFgnd, this->fastScannedRegExpNodes) |
| { |
| Assert(pnodeFgnd->nop == knopRegExp); |
| Assert(pnodeFgnd->AsParseNodeRegExp()->regexPattern != nullptr); |
| bool quit = false; |
| |
| while (!quit) |
| { |
| // Find the next work item with a RegEx in it. |
| while (currBackgroundItem && currBackgroundItem->RegExpNodeList() == nullptr) |
| { |
| currBackgroundItem = currBackgroundItem->GetNext(); |
| } |
| if (!currBackgroundItem) |
| { |
| break; |
| } |
| |
| // Walk the RegExps in the work item. |
| FOREACH_DLIST_ENTRY(ParseNodePtr, ArenaAllocator, pnodeBgnd, currBackgroundItem->RegExpNodeList()) |
| { |
| Assert(pnodeBgnd->nop == knopRegExp); |
| |
| if (pnodeFgnd->ichMin <= pnodeBgnd->ichMin) |
| { |
| // Either we found a match, or the next background node is past the foreground node. |
| // In any case, we can stop searching. |
| if (pnodeFgnd->ichMin == pnodeBgnd->ichMin) |
| { |
| Assert(pnodeFgnd->ichLim == pnodeBgnd->ichLim); |
| pnodeBgnd->AsParseNodeRegExp()->regexPattern = pnodeFgnd->AsParseNodeRegExp()->regexPattern; |
| } |
| quit = true; |
| break; |
| } |
| } |
| NEXT_DLIST_ENTRY; |
| |
| if (!quit) |
| { |
| // Need to advance to the next work item. |
| currBackgroundItem = currBackgroundItem->GetNext(); |
| } |
| } |
| } |
| NEXT_DLIST_ENTRY; |
| |
| #if DBG |
| for (currBackgroundItem = this->backgroundParseItems; |
| currBackgroundItem; |
| currBackgroundItem = currBackgroundItem->GetNext()) |
| { |
| if (currBackgroundItem->RegExpNodeList()) |
| { |
| FOREACH_DLIST_ENTRY(ParseNodePtr, ArenaAllocator, pnode, currBackgroundItem->RegExpNodeList()) |
| { |
| Assert(pnode->AsParseNodeRegExp()->regexPattern != nullptr); |
| } |
| NEXT_DLIST_ENTRY; |
| } |
| } |
| #endif |
| } |
| #endif |
| |
| LabelId* Parser::CreateLabelId(IdentPtr pid) |
| { |
| LabelId* pLabelId; |
| |
| pLabelId = (LabelId*)m_nodeAllocator.Alloc(sizeof(LabelId)); |
| if (NULL == pLabelId) |
| Error(ERRnoMemory); |
| pLabelId->pid = pid; |
| pLabelId->next = NULL; |
| |
| return pLabelId; |
| } |
| |
| /***************************************************************************** |
| The following set of routines allocate parse tree nodes of various kinds. |
| They catch an exception on out of memory. |
| *****************************************************************************/ |
| void |
| Parser::AddAstSize(int size) |
| { |
| Assert(!this->m_deferringAST); |
| Assert(m_pCurrentAstSize != NULL); |
| *m_pCurrentAstSize += size; |
| } |
| |
| void |
| Parser::AddAstSizeAllowDefer(int size) |
| { |
| if (!this->m_deferringAST) |
| { |
| AddAstSize(size); |
| } |
| } |
| |
| // StaticCreate |
| ParseNodeVar * Parser::StaticCreateTempNode(ParseNode* initExpr, ArenaAllocator * alloc) |
| { |
| ParseNodeVar * pnode = Anew(alloc, ParseNodeVar, knopTemp, 0, 0, nullptr); |
| pnode->pnodeInit = initExpr; |
| return pnode; |
| } |
| |
| ParseNodeUni * Parser::StaticCreateTempRef(ParseNode* tempNode, ArenaAllocator * alloc) |
| { |
| return Anew(alloc, ParseNodeUni, knopTempRef, 0, 0, tempNode); |
| } |
| |
| // Create Node with limit |
| template <OpCode nop> |
| typename OpCodeTrait<nop>::ParseNodeType * Parser::CreateNodeForOpT(charcount_t ichMin, charcount_t ichLim) |
| { |
| Assert(!this->m_deferringAST); |
| typename OpCodeTrait<nop>::ParseNodeType * pnode = StaticCreateNodeT<nop>(&m_nodeAllocator, ichMin, ichLim); |
| AddAstSize(sizeof(typename OpCodeTrait<nop>::ParseNodeType)); |
| return pnode; |
| } |
| |
| template <OpCode nop> |
| typename OpCodeTrait<nop>::ParseNodeType * Parser::CreateAllowDeferNodeForOpT(charcount_t ichMin, charcount_t ichLim) |
| { |
| CompileAssert(OpCodeTrait<nop>::AllowDefer); |
| typename OpCodeTrait<nop>::ParseNodeType * pnode = StaticCreateNodeT<nop>(&m_nodeAllocator, ichMin, ichLim); |
| AddAstSizeAllowDefer(sizeof(typename OpCodeTrait<nop>::ParseNodeType)); |
| return pnode; |
| } |
| |
| |
| #if DBG |
| static const int g_mpnopcbNode[] = |
| { |
| #define PTNODE(nop,sn,pc,nk,ok,json) sizeof(ParseNode##nk), |
| #include "ptlist.h" |
| }; |
| |
| void VerifyNodeSize(OpCode nop, int size) |
| { |
| Assert(nop >= 0 && nop < knopLim); |
| __analysis_assume(nop < knopLim); |
| Assert(g_mpnopcbNode[nop] == size); |
| } |
| #endif |
| |
| // Create ParseNodeUni |
| ParseNodeUni * Parser::CreateUniNode(OpCode nop, ParseNodePtr pnode1) |
| { |
| charcount_t ichMin; |
| charcount_t ichLim; |
| |
| if (nullptr == pnode1) |
| { |
| // no ops |
| ichMin = this->GetScanner()->IchMinTok(); |
| ichLim = this->GetScanner()->IchLimTok(); |
| } |
| else |
| { |
| // 1 op |
| ichMin = pnode1->ichMin; |
| ichLim = pnode1->ichLim; |
| this->CheckArguments(pnode1); |
| } |
| return CreateUniNode(nop, pnode1, ichMin, ichLim); |
| } |
| |
| ParseNodeUni * Parser::CreateUniNode(OpCode nop, ParseNodePtr pnode1, charcount_t ichMin, charcount_t ichLim) |
| { |
| Assert(!this->m_deferringAST); |
| DebugOnly(VerifyNodeSize(nop, sizeof(ParseNodeUni))); |
| ParseNodeUni * pnode = Anew(&m_nodeAllocator, ParseNodeUni, nop, ichMin, ichLim, pnode1); |
| AddAstSize(sizeof(ParseNodeUni)); |
| return pnode; |
| } |
| |
| // Create ParseNodeBin |
| ParseNodeBin * Parser::StaticCreateBinNode(OpCode nop, ParseNodePtr pnode1, ParseNodePtr pnode2, ArenaAllocator* alloc, charcount_t ichMin, charcount_t ichLim) |
| { |
| DebugOnly(VerifyNodeSize(nop, sizeof(ParseNodeBin))); |
| return Anew(alloc, ParseNodeBin, nop, ichMin, ichLim, pnode1, pnode2); |
| } |
| |
| ParseNodeBin * Parser::CreateBinNode(OpCode nop, ParseNodePtr pnode1, ParseNodePtr pnode2) |
| { |
| Assert(!this->m_deferringAST); |
| charcount_t ichMin; |
| charcount_t ichLim; |
| |
| if (nullptr == pnode1) |
| { |
| // no ops |
| Assert(nullptr == pnode2); |
| ichMin = this->GetScanner()->IchMinTok(); |
| ichLim = this->GetScanner()->IchLimTok(); |
| } |
| else |
| { |
| if (nullptr == pnode2) |
| { |
| // 1 op |
| ichMin = pnode1->ichMin; |
| ichLim = pnode1->ichLim; |
| } |
| else |
| { |
| // 2 ops |
| ichMin = pnode1->ichMin; |
| ichLim = pnode2->ichLim; |
| if (nop != knopDot && nop != knopIndex) |
| { |
| this->CheckArguments(pnode2); |
| } |
| } |
| if (nop != knopDot && nop != knopIndex) |
| { |
| this->CheckArguments(pnode1); |
| } |
| } |
| |
| return CreateBinNode(nop, pnode1, pnode2, ichMin, ichLim); |
| } |
| |
| |
| ParseNodeBin * Parser::CreateBinNode(OpCode nop, ParseNodePtr pnode1, |
| ParseNodePtr pnode2, charcount_t ichMin, charcount_t ichLim) |
| { |
| Assert(!this->m_deferringAST); |
| ParseNodeBin * pnode = StaticCreateBinNode(nop, pnode1, pnode2, &m_nodeAllocator, ichMin, ichLim); |
| AddAstSize(sizeof(ParseNodeBin)); |
| return pnode; |
| } |
| |
| // Create ParseNodeTri |
| ParseNodeTri * Parser::CreateTriNode(OpCode nop, ParseNodePtr pnode1, |
| ParseNodePtr pnode2, ParseNodePtr pnode3) |
| { |
| charcount_t ichMin; |
| charcount_t ichLim; |
| |
| if (nullptr == pnode1) |
| { |
| // no ops |
| Assert(nullptr == pnode2); |
| Assert(nullptr == pnode3); |
| ichMin = this->GetScanner()->IchMinTok(); |
| ichLim = this->GetScanner()->IchLimTok(); |
| } |
| else if (nullptr == pnode2) |
| { |
| // 1 op |
| Assert(nullptr == pnode3); |
| ichMin = pnode1->ichMin; |
| ichLim = pnode1->ichLim; |
| } |
| else if (nullptr == pnode3) |
| { |
| // 2 op |
| ichMin = pnode1->ichMin; |
| ichLim = pnode2->ichLim; |
| } |
| else |
| { |
| // 3 ops |
| ichMin = pnode1->ichMin; |
| ichLim = pnode3->ichLim; |
| } |
| |
| return CreateTriNode(nop, pnode1, pnode2, pnode3, ichMin, ichLim); |
| } |
| |
| ParseNodeTri * Parser::CreateTriNode(OpCode nop, ParseNodePtr pnode1, |
| ParseNodePtr pnode2, ParseNodePtr pnode3, |
| charcount_t ichMin, charcount_t ichLim) |
| { |
| Assert(!this->m_deferringAST); |
| DebugOnly(VerifyNodeSize(nop, sizeof(ParseNodeTri))); |
| ParseNodeTri * pnode = Anew(&m_nodeAllocator, ParseNodeTri, nop, ichMin, ichLim); |
| AddAstSize(sizeof(ParseNodeTri)); |
| |
| pnode->pnode1 = pnode1; |
| pnode->pnode2 = pnode2; |
| pnode->pnode3 = pnode3; |
| |
| return pnode; |
| } |
| |
| // Create ParseNodeBlock |
| ParseNodeBlock * |
| Parser::StaticCreateBlockNode(ArenaAllocator* alloc, charcount_t ichMin, charcount_t ichLim, int blockId, PnodeBlockType blockType) |
| { |
| return Anew(alloc, ParseNodeBlock, ichMin, ichLim, blockId, blockType); |
| } |
| |
| ParseNodeBlock * Parser::CreateBlockNode(PnodeBlockType blockType) |
| { |
| return CreateBlockNode(this->GetScanner()->IchMinTok(), this->GetScanner()->IchLimTok(), blockType); |
| } |
| |
| ParseNodeBlock * Parser::CreateBlockNode(charcount_t ichMin, charcount_t ichLim, PnodeBlockType blockType) |
| { |
| Assert(OpCodeTrait<knopBlock>::AllowDefer); |
| ParseNodeBlock * pnode = StaticCreateBlockNode(&m_nodeAllocator, ichMin, ichLim, this->m_nextBlockId++, blockType); |
| AddAstSizeAllowDefer(sizeof(ParseNodeBlock)); |
| return pnode; |
| } |
| |
| // Create ParseNodeVar |
| ParseNodeVar * Parser::CreateDeclNode(OpCode nop, IdentPtr pid, SymbolType symbolType, bool errorOnRedecl) |
| { |
| Assert(nop == knopVarDecl || nop == knopLetDecl || nop == knopConstDecl); |
| ParseNodeVar * pnode = Anew(&m_nodeAllocator, ParseNodeVar, nop, this->GetScanner()->IchMinTok(), this->GetScanner()->IchLimTok(), pid); |
| if (symbolType != STUnknown) |
| { |
| pnode->sym = AddDeclForPid(pnode, pid, symbolType, errorOnRedecl); |
| } |
| |
| return pnode; |
| } |
| |
| ParseNodeInt * Parser::CreateIntNode(int32 lw) |
| { |
| Assert(!this->m_deferringAST); |
| ParseNodeInt * pnode = Anew(&m_nodeAllocator, ParseNodeInt, this->GetScanner()->IchMinTok(), this->GetScanner()->IchLimTok(), lw); |
| AddAstSize(sizeof(ParseNodeInt)); |
| return pnode; |
| } |
| |
| ParseNodeStr * Parser::CreateStrNode(IdentPtr pid) |
| { |
| Assert(!this->m_deferringAST); |
| ParseNodeStr * pnode = Anew(&m_nodeAllocator, ParseNodeStr, this->GetScanner()->IchMinTok(), this->GetScanner()->IchLimTok(), pid); |
| pnode->grfpn |= PNodeFlags::fpnCanFlattenConcatExpr; |
| AddAstSize(sizeof(ParseNodeStr)); |
| return pnode; |
| } |
| |
| ParseNodeBigInt * Parser::CreateBigIntNode(IdentPtr pid) |
| { |
| Assert(!this->m_deferringAST); |
| ParseNodeBigInt * pnode = Anew(&m_nodeAllocator, ParseNodeBigInt, this->GetScanner()->IchMinTok(), this->GetScanner()->IchLimTok(), pid); |
| pnode->isNegative = false; |
| AddAstSize(sizeof(ParseNodeBigInt)); |
| return pnode; |
| } |
| |
| ParseNodeName * Parser::CreateNameNode(IdentPtr pid) |
| { |
| ParseNodeName * pnode = Anew(&m_nodeAllocator, ParseNodeName, this->GetScanner()->IchMinTok(), this->GetScanner()->IchLimTok(), pid); |
| AddAstSizeAllowDefer(sizeof(ParseNodeName)); |
| return pnode; |
| } |
| |
| ParseNodeName * Parser::CreateNameNode(IdentPtr pid, PidRefStack * ref, charcount_t ichMin, charcount_t ichLim) |
| { |
| ParseNodeName * pnode = Anew(&m_nodeAllocator, ParseNodeName, ichMin, ichLim, pid); |
| pnode->SetSymRef(ref); |
| AddAstSize(sizeof(ParseNodeName)); |
| return pnode; |
| } |
| |
| ParseNodeSpecialName * Parser::CreateSpecialNameNode(IdentPtr pid, PidRefStack * ref, charcount_t ichMin, charcount_t ichLim) |
| { |
| Assert(!this->m_deferringAST); |
| ParseNodeSpecialName * pnode = Anew(&m_nodeAllocator, ParseNodeSpecialName, ichMin, ichLim, pid); |
| pnode->SetSymRef(ref); |
| if (pid == wellKnownPropertyPids._this) |
| { |
| pnode->isThis = true; |
| } |
| else if (pid == wellKnownPropertyPids._super || pid == wellKnownPropertyPids._superConstructor) |
| { |
| pnode->isSuper = true; |
| } |
| |
| AddAstSize(sizeof(ParseNodeSpecialName)); |
| return pnode; |
| } |
| |
| ParseNodeSuperReference * Parser::CreateSuperReferenceNode(OpCode nop, ParseNodeSpecialName * pnode1, ParseNodePtr pnode2) |
| { |
| Assert(!this->m_deferringAST); |
| Assert(pnode1 && pnode1->isSuper); |
| Assert(pnode2 != nullptr); |
| Assert(nop == knopDot || nop == knopIndex); |
| |
| ParseNodeSuperReference * pnode = Anew(&m_nodeAllocator, ParseNodeSuperReference, nop, pnode1->ichMin, pnode2->ichLim, pnode1, pnode2); |
| AddAstSize(sizeof(ParseNodeSuperReference)); |
| |
| return pnode; |
| } |
| |
| ParseNodeProg * Parser::CreateProgNode(bool isModuleSource, ULONG lineNumber) |
| { |
| ParseNodeProg * pnodeProg; |
| |
| if (isModuleSource) |
| { |
| pnodeProg = CreateNodeForOpT<knopModule>(); |
| |
| // knopModule is not actually handled anywhere since we would need to handle it everywhere we could |
| // have knopProg and it would be treated exactly the same except for import/export statements. |
| // We are only using it as a way to get the correct size for PnModule. |
| // Consider: Should we add a flag to PnProg which is false but set to true in PnModule? |
| // If we do, it can't be a virtual method since the parse nodes are all in a union. |
| pnodeProg->nop = knopProg; |
| } |
| else |
| { |
| pnodeProg = CreateNodeForOpT<knopProg>(); |
| } |
| |
| pnodeProg->cbMin = this->GetScanner()->IecpMinTok(); |
| pnodeProg->cbStringMin = pnodeProg->cbMin; |
| pnodeProg->lineNumber = lineNumber; |
| pnodeProg->homeObjLocation = Js::Constants::NoRegister; |
| pnodeProg->superRestrictionState = SuperRestrictionState::Disallowed; |
| return pnodeProg; |
| } |
| |
| ParseNodeCall * Parser::CreateCallNode(OpCode nop, ParseNodePtr pnode1, ParseNodePtr pnode2) |
| { |
| charcount_t ichMin; |
| charcount_t ichLim; |
| |
| if (nullptr == pnode1) |
| { |
| Assert(nullptr == pnode2); |
| ichMin = this->GetScanner()->IchMinTok(); |
| ichLim = this->GetScanner()->IchLimTok(); |
| } |
| else |
| { |
| ichMin = pnode1->ichMin; |
| ichLim = pnode2 == nullptr ? pnode1->ichLim : pnode2->ichLim; |
| |
| if (pnode1->nop == knopDot || pnode1->nop == knopIndex) |
| { |
| this->CheckArguments(pnode1->AsParseNodeBin()->pnode1); |
| } |
| } |
| return CreateCallNode(nop, pnode1, pnode2, ichMin, ichLim); |
| } |
| |
| |
| |
| ParseNodeCall * Parser::CreateCallNode(OpCode nop, ParseNodePtr pnode1, ParseNodePtr pnode2, charcount_t ichMin, charcount_t ichLim) |
| { |
| Assert(!this->m_deferringAST); |
| |
| // Classes, derived from ParseNodeCall, can be created here as well, |
| // as long as their size matches kcbPnCall (that is, they don't add |
| // any data members of their own). |
| DebugOnly(VerifyNodeSize(nop, sizeof(ParseNodeCall))); |
| ParseNodeCall* pnode = Anew(&m_nodeAllocator, ParseNodeCall, nop, ichMin, ichLim, pnode1, pnode2); |
| AddAstSize(sizeof(ParseNodeCall)); |
| |
| return pnode; |
| } |
| |
| ParseNodeSuperCall * Parser::CreateSuperCallNode(ParseNodeSpecialName * pnode1, ParseNodePtr pnode2) |
| { |
| Assert(!this->m_deferringAST); |
| Assert(pnode1 && pnode1->isSuper); |
| |
| ParseNodeSuperCall* pnode = Anew(&m_nodeAllocator, ParseNodeSuperCall, knopCall, pnode1->ichMin, pnode2 == nullptr ? pnode1->ichLim : pnode2->ichLim, pnode1, pnode2); |
| AddAstSize(sizeof(ParseNodeSuperCall)); |
| return pnode; |
| } |
| |
| ParseNodeParamPattern * Parser::CreateParamPatternNode(ParseNode * pnode1) |
| { |
| ParseNodeParamPattern * paramPatternNode = CreateNodeForOpT<knopParamPattern>(pnode1->ichMin, pnode1->ichLim); |
| paramPatternNode->pnode1 = pnode1; |
| paramPatternNode->pnodeNext = nullptr; |
| paramPatternNode->location = Js::Constants::NoRegister; |
| return paramPatternNode; |
| } |
| |
| ParseNodeParamPattern * Parser::CreateDummyParamPatternNode(charcount_t ichMin) |
| { |
| ParseNodeParamPattern * paramPatternNode = CreateNodeForOpT<knopParamPattern>(ichMin); |
| paramPatternNode->pnode1 = nullptr; |
| paramPatternNode->pnodeNext = nullptr; |
| paramPatternNode->location = Js::Constants::NoRegister; |
| return paramPatternNode; |
| } |
| |
| ParseNodeObjLit * Parser::CreateObjectPatternNode(ParseNodePtr pnodeMemberList, charcount_t ichMin, charcount_t ichLim, bool convertToPattern) { |
| // Count the number of non-rest members in the object |
| uint32 staticCount = 0; |
| uint32 computedCount = 0; |
| bool hasRest = false; |
| ParseNodePtr pnodeMemberNodeList = convertToPattern ? nullptr : pnodeMemberList; |
| if (pnodeMemberList != nullptr) |
| { |
| Assert(pnodeMemberList->nop == knopList || |
| (!convertToPattern && pnodeMemberList->nop == knopObjectPatternMember) || |
| convertToPattern || |
| pnodeMemberList->nop == knopEllipsis); |
| ForEachItemInList(pnodeMemberList, [&](ParseNodePtr item) { |
| ParseNodePtr memberNode = convertToPattern ? ConvertMemberToMemberPattern(item) : item; |
| if (convertToPattern) |
| { |
| AppendToList(&pnodeMemberNodeList, memberNode); |
| } |
| if (memberNode->nop != knopEllipsis) |
| { |
| ParseNodePtr nameNode = memberNode->AsParseNodeBin()->pnode1; |
| Assert(nameNode->nop == knopComputedName || nameNode->nop == knopStr); |
| if (nameNode->nop == knopComputedName) |
| { |
| computedCount++; |
| } |
| else |
| { |
| staticCount++; |
| } |
| } |
| else |
| { |
| hasRest = true; |
| } |
| }); |
| } |
| |
| ParseNodeObjLit * objectPatternNode = CreateNodeForOpT<knopObjectPattern>(ichMin, ichLim); |
| objectPatternNode->pnode1 = pnodeMemberNodeList; |
| objectPatternNode->computedCount = computedCount; |
| objectPatternNode->staticCount = staticCount; |
| objectPatternNode->hasRest = hasRest; |
| return objectPatternNode; |
| } |
| |
| Symbol* Parser::AddDeclForPid(ParseNodeVar * pnodeVar, IdentPtr pid, SymbolType symbolType, bool errorOnRedecl) |
| { |
| Assert(pnodeVar->IsVarLetOrConst()); |
| |
| PidRefStack *refForUse = nullptr, *refForDecl = nullptr; |
| BlockInfoStack *blockInfo; |
| bool fBlockScope = false; |
| if (pnodeVar->nop != knopVarDecl || symbolType == STFunction) |
| { |
| Assert(m_pstmtCur); |
| if (m_pstmtCur->GetNop() != knopBlock) |
| { |
| // Let/const declared in a bare statement context. |
| Error(ERRDeclOutOfStmt); |
| } |
| |
| if (m_pstmtCur->pstmtOuter && m_pstmtCur->pstmtOuter->GetNop() == knopSwitch) |
| { |
| // Let/const declared inside a switch block (requiring conservative use-before-decl check). |
| pnodeVar->isSwitchStmtDecl = true; |
| } |
| |
| fBlockScope = pnodeVar->nop != knopVarDecl || |
| ( |
| !GetCurrentBlockInfo()->pnodeBlock->scope || |
| GetCurrentBlockInfo()->pnodeBlock->scope->GetScopeType() != ScopeType_GlobalEvalBlock |
| ); |
| } |
| if (fBlockScope) |
| { |
| blockInfo = GetCurrentBlockInfo(); |
| } |
| else |
| { |
| blockInfo = GetCurrentFunctionBlockInfo(); |
| } |
| |
| refForDecl = this->FindOrAddPidRef(pid, blockInfo->pnodeBlock->blockId, GetCurrentFunctionNode()->functionId); |
| |
| if (refForDecl == nullptr) |
| { |
| Error(ERRnoMemory); |
| } |
| |
| if (refForDecl->funcId != GetCurrentFunctionNode()->functionId) |
| { |
| // Fix up the function id, which is incorrect if we're reparsing lambda parameters |
| Assert(this->m_reparsingLambdaParams); |
| refForDecl->funcId = GetCurrentFunctionNode()->functionId; |
| } |
| |
| if (blockInfo == GetCurrentBlockInfo()) |
| { |
| refForUse = refForDecl; |
| } |
| else |
| { |
| refForUse = this->PushPidRef(pid); |
| } |
| pnodeVar->symRef = refForUse->GetSymRef(); |
| Symbol *sym = refForDecl->GetSym(); |
| if (sym != nullptr) |
| { |
| // Multiple declarations in the same scope. 3 possibilities: error, existing one wins, new one wins. |
| switch (pnodeVar->nop) |
| { |
| case knopLetDecl: |
| case knopConstDecl: |
| if (!sym->GetDecl()->AsParseNodeVar()->isBlockScopeFncDeclVar && !sym->IsArguments()) |
| { |
| // If the built-in arguments is shadowed then don't throw |
| Assert(errorOnRedecl); |
| // Redeclaration error. |
| Error(ERRRedeclaration); |
| } |
| else |
| { |
| // (New) let/const hides the (old) var |
| sym->SetSymbolType(symbolType); |
| sym->SetDecl(pnodeVar); |
| } |
| break; |
| case knopVarDecl: |
| if (m_currentScope->GetScopeType() == ScopeType_Parameter && !sym->IsArguments()) |
| { |
| // If this is a parameter list, mark the scope to indicate that it has duplicate definition unless it is shadowing the default arguments symbol. |
| // If later this turns out to be a non-simple param list (like function f(a, a, c = 1) {}) then it is a SyntaxError to have duplicate formals. |
| m_currentScope->SetHasDuplicateFormals(); |
| } |
| |
| if (sym->GetDecl() == nullptr) |
| { |
| sym->SetDecl(pnodeVar); |
| break; |
| } |
| switch (sym->GetDecl()->nop) |
| { |
| case knopLetDecl: |
| case knopConstDecl: |
| // Destructuring made possible to have the formals to be the let bind. But that shouldn't throw the error. |
| if (errorOnRedecl && (!IsES6DestructuringEnabled() || sym->GetSymbolType() != STFormal)) |
| { |
| Error(ERRRedeclaration); |
| } |
| // If !errorOnRedecl, (old) let/const hides the (new) var, so do nothing. |
| break; |
| case knopVarDecl: |
| // Legal redeclaration. Who wins? |
| if (errorOnRedecl || sym->GetDecl()->AsParseNodeVar()->isBlockScopeFncDeclVar || sym->IsArguments()) |
| { |
| if (symbolType == STFormal || |
| (symbolType == STFunction && sym->GetSymbolType() != STFormal) || |
| sym->GetSymbolType() == STVariable) |
| { |
| // New decl wins. |
| sym->SetSymbolType(symbolType); |
| sym->SetDecl(pnodeVar); |
| } |
| } |
| break; |
| } |
| break; |
| } |
| } |
| else |
| { |
| Scope *scope = blockInfo->pnodeBlock->scope; |
| if (scope == nullptr) |
| { |
| Assert(blockInfo->pnodeBlock->blockType == PnodeBlockType::Regular); |
| scope = Anew(&m_nodeAllocator, Scope, &m_nodeAllocator, ScopeType_Block); |
| if (this->IsCurBlockInLoop()) |
| { |
| scope->SetIsBlockInLoop(); |
| } |
| blockInfo->pnodeBlock->scope = scope; |
| PushScope(scope); |
| } |
| |
| ParseNodeFnc * pnodeFnc = GetCurrentFunctionNode(); |
| if (scope->GetScopeType() == ScopeType_GlobalEvalBlock) |
| { |
| Assert(fBlockScope); |
| Assert(scope->GetEnclosingScope() == m_currentNodeProg->scope); |
| // Check for same-named decl in Global scope. |
| CheckRedeclarationErrorForBlockId(pid, 0); |
| } |
| else if (scope->GetScopeType() == ScopeType_Global && (this->m_grfscr & fscrEvalCode) && |
| !(m_functionBody && m_functionBody->GetScopeInfo())) |
| { |
| // Check for same-named decl in GlobalEvalBlock scope. Note that this is not necessary |
| // if we're compiling a deferred nested function and the global scope was restored from cached info, |
| // because in that case we don't need a GlobalEvalScope. |
| Assert(!fBlockScope || (this->m_grfscr & fscrConsoleScopeEval) == fscrConsoleScopeEval); |
| CheckRedeclarationErrorForBlockId(pid, 1); |
| } |
| else if (!pnodeFnc->IsBodyAndParamScopeMerged() |
| && scope->GetScopeType() == ScopeType_FunctionBody |
| && (pnodeVar->nop == knopLetDecl || pnodeVar->nop == knopConstDecl)) |
| { |
| // In case of split scope function when we add a new let or const declaration to the body |
| // we have to check whether the param scope already has the same symbol defined. |
| CheckRedeclarationErrorForBlockId(pid, pnodeFnc->pnodeScopes->blockId); |
| } |
| |
| if (!sym) |
| { |
| const char16 *name = reinterpret_cast<const char16*>(pid->Psz()); |
| int nameLength = pid->Cch(); |
| SymbolName const symName(name, nameLength); |
| |
| Assert(!scope->FindLocalSymbol(symName)); |
| sym = Anew(&m_nodeAllocator, Symbol, symName, pnodeVar, symbolType); |
| scope->AddNewSymbol(sym); |
| sym->SetPid(pid); |
| } |
| refForDecl->SetSym(sym); |
| } |
| return sym; |
| } |
| |
| void Parser::CheckRedeclarationErrorForBlockId(IdentPtr pid, int blockId) |
| { |
| // If the ref stack entry for the blockId contains a sym then throw redeclaration error |
| PidRefStack *pidRefOld = pid->GetPidRefForScopeId(blockId); |
| if (pidRefOld && pidRefOld->GetSym() && !pidRefOld->GetSym()->IsArguments()) |
| { |
| Error(ERRRedeclaration); |
| } |
| } |
| |
| bool Parser::IsCurBlockInLoop() const |
| { |
| for (StmtNest *stmt = this->m_pstmtCur; stmt != nullptr; stmt = stmt->pstmtOuter) |
| { |
| OpCode nop = stmt->GetNop(); |
| if (ParseNode::Grfnop(nop) & fnopContinue) |
| { |
| return true; |
| } |
| if (nop == knopFncDecl) |
| { |
| return false; |
| } |
| } |
| return false; |
| } |
| |
| void Parser::RestorePidRefForSym(Symbol *sym) |
| { |
| IdentPtr pid = this->GetHashTbl()->PidHashNameLen(sym->GetName().GetBuffer(), sym->GetName().GetLength()); |
| Assert(pid); |
| sym->SetPid(pid); |
| PidRefStack *ref = this->PushPidRef(pid); |
| ref->SetSym(sym); |
| } |
| |
| void Parser::CheckPidIsValid(IdentPtr pid, bool autoArgumentsObject) |
| { |
| if (IsStrictMode()) |
| { |
| // in strict mode, variable named 'eval' cannot be created |
| if (pid == wellKnownPropertyPids.eval) |
| { |
| Error(ERREvalUsage); |
| } |
| else if (pid == wellKnownPropertyPids.arguments && !autoArgumentsObject) |
| { |
| Error(ERRArgsUsage); |
| } |
| } |
| } |
| |
| // CreateVarDecl needs m_ppnodeVar to be pointing to the right function. |
| // Post-parsing rewriting during bytecode gen may have m_ppnodeVar pointing to the last parsed function. |
| // This function sets up m_ppnodeVar to point to the given pnodeFnc and creates the new var declaration. |
| // This prevents accidentally adding var declarations to the last parsed function. |
| ParseNodeVar * Parser::AddVarDeclNode(IdentPtr pid, ParseNodeFnc * pnodeFnc) |
| { |
| AnalysisAssert(pnodeFnc); |
| |
| ParseNodePtr *const ppnodeVarSave = m_ppnodeVar; |
| |
| m_ppnodeVar = &pnodeFnc->pnodeVars; |
| while (*m_ppnodeVar != nullptr) |
| { |
| m_ppnodeVar = &(*m_ppnodeVar)->AsParseNodeVar()->pnodeNext; |
| } |
| |
| ParseNodeVar * pnode = CreateVarDeclNode(pid, STUnknown, false, 0, /* checkReDecl = */ false); |
| |
| m_ppnodeVar = ppnodeVarSave; |
| |
| return pnode; |
| } |
| |
| ParseNodeVar * Parser::CreateModuleImportDeclNode(IdentPtr localName) |
| { |
| ParseNodeVar * declNode = CreateBlockScopedDeclNode(localName, knopConstDecl); |
| Symbol* sym = declNode->sym; |
| |
| sym->SetIsModuleExportStorage(true); |
| sym->SetIsModuleImport(true); |
| |
| return declNode; |
| } |
| |
| ParseNodeVar * Parser::CreateVarDeclNode(IdentPtr pid, SymbolType symbolType, bool autoArgumentsObject, ParseNodePtr pnodeFnc, bool errorOnRedecl) |
| { |
| ParseNodeVar * pnode = CreateDeclNode(knopVarDecl, pid, symbolType, errorOnRedecl); |
| |
| // Append the variable to the end of the current variable list. |
| Assert(m_ppnodeVar); |
| pnode->pnodeNext = *m_ppnodeVar; |
| *m_ppnodeVar = pnode; |
| if (nullptr != pid) |
| { |
| // this is not a temp - make sure temps go after this node |
| Assert(pid); |
| m_ppnodeVar = &pnode->pnodeNext; |
| CheckPidIsValid(pid, autoArgumentsObject); |
| } |
| |
| return pnode; |
| } |
| |
| ParseNodeVar * Parser::CreateBlockScopedDeclNode(IdentPtr pid, OpCode nodeType) |
| { |
| Assert(nodeType == knopConstDecl || nodeType == knopLetDecl); |
| |
| ParseNodeVar * pnode = CreateDeclNode(nodeType, pid, STVariable, true); |
| |
| if (nullptr != pid) |
| { |
| Assert(pid); |
| AddVarDeclToBlock(pnode); |
| CheckPidIsValid(pid); |
| } |
| |
| return pnode; |
| } |
| |
| void Parser::AddVarDeclToBlock(ParseNodeVar *pnode) |
| { |
| Assert(pnode->nop == knopConstDecl || pnode->nop == knopLetDecl); |
| |
| // Maintain a combined list of let and const declarations to keep |
| // track of declaration order. |
| |
| Assert(m_currentBlockInfo->m_ppnodeLex); |
| *m_currentBlockInfo->m_ppnodeLex = pnode; |
| m_currentBlockInfo->m_ppnodeLex = &pnode->pnodeNext; |
| pnode->pnodeNext = nullptr; |
| } |
| |
| void Parser::SetCurrentStatement(StmtNest *stmt) |
| { |
| m_pstmtCur = stmt; |
| } |
| |
| template<bool buildAST> |
| ParseNodeBlock * Parser::StartParseBlockWithCapacity(PnodeBlockType blockType, ScopeType scopeType, int capacity) |
| { |
| Scope *scope = nullptr; |
| |
| // Block scopes are not created lazily in the case where we're repopulating a persisted scope. |
| |
| scope = Anew(&m_nodeAllocator, Scope, &m_nodeAllocator, scopeType, capacity); |
| PushScope(scope); |
| |
| return StartParseBlockHelper<buildAST>(blockType, scope, nullptr); |
| } |
| |
| template<bool buildAST> |
| ParseNodeBlock * Parser::StartParseBlock(PnodeBlockType blockType, ScopeType scopeType, LabelId* pLabelId) |
| { |
| Scope *scope = nullptr; |
| // Block scopes are created lazily when we discover block-scoped content. |
| if (scopeType != ScopeType_Unknown && scopeType != ScopeType_Block) |
| { |
| scope = Anew(&m_nodeAllocator, Scope, &m_nodeAllocator, scopeType); |
| PushScope(scope); |
| } |
| |
| return StartParseBlockHelper<buildAST>(blockType, scope, pLabelId); |
| } |
| |
| template<bool buildAST> |
| ParseNodeBlock * Parser::StartParseBlockHelper(PnodeBlockType blockType, Scope *scope, LabelId* pLabelId) |
| { |
| ParseNodeBlock * pnodeBlock = CreateBlockNode(blockType); |
| pnodeBlock->scope = scope; |
| BlockInfoStack *newBlockInfo = PushBlockInfo(pnodeBlock); |
| |
| PushStmt<buildAST>(&newBlockInfo->pstmt, pnodeBlock, knopBlock, pLabelId); |
| |
| return pnodeBlock; |
| } |
| |
| void Parser::PushScope(Scope *scope) |
| { |
| Assert(scope); |
| scope->SetEnclosingScope(m_currentScope); |
| m_currentScope = scope; |
| } |
| |
| void Parser::PopScope(Scope *scope) |
| { |
| Assert(scope == m_currentScope); |
| m_currentScope = scope->GetEnclosingScope(); |
| scope->SetEnclosingScope(nullptr); |
| } |
| |
| void Parser::PushFuncBlockScope(ParseNodeBlock * pnodeBlock, ParseNodePtr **ppnodeScopeSave, ParseNodePtr **ppnodeExprScopeSave) |
| { |
| // Maintain the scope tree. |
| |
| pnodeBlock->pnodeScopes = nullptr; |
| pnodeBlock->pnodeNext = nullptr; |
| |
| // Insert this block into the active list of scopes (m_ppnodeExprScope or m_ppnodeScope). |
| // Save the current block's "next" pointer as the new endpoint of that list. |
| if (m_ppnodeExprScope) |
| { |
| *ppnodeScopeSave = m_ppnodeScope; |
| |
| Assert(*m_ppnodeExprScope == nullptr); |
| *m_ppnodeExprScope = pnodeBlock; |
| *ppnodeExprScopeSave = &pnodeBlock->pnodeNext; |
| } |
| else |
| { |
| Assert(m_ppnodeScope); |
| Assert(*m_ppnodeScope == nullptr); |
| *m_ppnodeScope = pnodeBlock; |
| *ppnodeScopeSave = &pnodeBlock->pnodeNext; |
| |
| *ppnodeExprScopeSave = m_ppnodeExprScope; |
| } |
| |
| // Advance the global scope list pointer to the new block's child list. |
| m_ppnodeScope = &pnodeBlock->pnodeScopes; |
| // Set m_ppnodeExprScope to NULL to make that list inactive. |
| m_ppnodeExprScope = nullptr; |
| } |
| |
| void Parser::PopFuncBlockScope(ParseNodePtr *ppnodeScopeSave, ParseNodePtr *ppnodeExprScopeSave) |
| { |
| Assert(m_ppnodeExprScope == nullptr || *m_ppnodeExprScope == nullptr); |
| m_ppnodeExprScope = ppnodeExprScopeSave; |
| |
| Assert(m_ppnodeScope); |
| Assert(nullptr == *m_ppnodeScope); |
| m_ppnodeScope = ppnodeScopeSave; |
| } |
| |
| template<bool buildAST> |
| ParseNodeBlock * Parser::ParseBlock(LabelId* pLabelId) |
| { |
| ParseNodeBlock * pnodeBlock = nullptr; |
| ParseNodePtr *ppnodeScopeSave = nullptr; |
| ParseNodePtr *ppnodeExprScopeSave = nullptr; |
| |
| pnodeBlock = StartParseBlock<buildAST>(PnodeBlockType::Regular, ScopeType_Block, pLabelId); |
| |
| BlockInfoStack* outerBlockInfo = m_currentBlockInfo->pBlockInfoOuter; |
| if (outerBlockInfo != nullptr && outerBlockInfo->pnodeBlock != nullptr |
| && outerBlockInfo->pnodeBlock->scope != nullptr |
| && outerBlockInfo->pnodeBlock->scope->GetScopeType() == ScopeType_CatchParamPattern) |
| { |
| // If we are parsing the catch block then destructured params can have let declarations. Let's add them to the new block. |
| for (ParseNodePtr pnode = m_currentBlockInfo->pBlockInfoOuter->pnodeBlock->pnodeLexVars; pnode; pnode = pnode->AsParseNodeVar()->pnodeNext) |
| { |
| PidRefStack* ref = PushPidRef(pnode->AsParseNodeVar()->sym->GetPid()); |
| ref->SetSym(pnode->AsParseNodeVar()->sym); |
| } |
| } |
| |
| ChkCurTok(tkLCurly, ERRnoLcurly); |
| ParseNodePtr * ppnodeList = nullptr; |
| if (buildAST) |
| { |
| PushFuncBlockScope(pnodeBlock, &ppnodeScopeSave, &ppnodeExprScopeSave); |
| ppnodeList = &pnodeBlock->pnodeStmt; |
| } |
| |
| ParseStmtList<buildAST>(ppnodeList); |
| |
| if (buildAST) |
| { |
| PopFuncBlockScope(ppnodeScopeSave, ppnodeExprScopeSave); |
| } |
| |
| FinishParseBlock(pnodeBlock); |
| |
| ChkCurTok(tkRCurly, ERRnoRcurly); |
| |
| return pnodeBlock; |
| } |
| |
| bool Parser::IsSpecialName(IdentPtr pid) |
| { |
| return pid == wellKnownPropertyPids._this || |
| pid == wellKnownPropertyPids._super || |
| pid == wellKnownPropertyPids._superConstructor || |
| pid == wellKnownPropertyPids._newTarget; |
| } |
| |
| ParseNodeSpecialName * Parser::ReferenceSpecialName(IdentPtr pid, charcount_t ichMin, charcount_t ichLim, bool createNode) |
| { |
| PidRefStack* ref = this->PushPidRef(pid); |
| |
| if (!createNode) |
| { |
| return nullptr; |
| } |
| |
| return CreateSpecialNameNode(pid, ref, ichMin, ichLim); |
| } |
| |
| ParseNodeVar * Parser::CreateSpecialVarDeclIfNeeded(ParseNodeFnc * pnodeFnc, IdentPtr pid, bool forceCreate) |
| { |
| Assert(pid != nullptr); |
| |
| PidRefStack* ref = pid->GetTopRef(); |
| |
| // If the function has a reference to pid or we set forceCreate, make a special var decl |
| if (forceCreate || (ref && ref->GetScopeId() >= m_currentBlockInfo->pnodeBlock->blockId)) |
| { |
| return this->CreateSpecialVarDeclNode(pnodeFnc, pid); |
| } |
| |
| return nullptr; |
| } |
| |
| void Parser::CreateSpecialSymbolDeclarations(ParseNodeFnc * pnodeFnc) |
| { |
| // Lambda function cannot have any special bindings. |
| if (pnodeFnc->IsLambda()) |
| { |
| return; |
| } |
| |
| bool isTopLevelEventHandler = (this->m_grfscr & fscrImplicitThis) && !pnodeFnc->IsNested(); |
| |
| // Create a 'this' symbol for non-lambda functions with references to 'this', and all class constructors and top level event hanlders. |
| ParseNodePtr varDeclNode = CreateSpecialVarDeclIfNeeded(pnodeFnc, wellKnownPropertyPids._this, pnodeFnc->IsClassConstructor() || isTopLevelEventHandler); |
| if (varDeclNode) |
| { |
| varDeclNode->AsParseNodeVar()->sym->SetIsThis(true); |
| |
| if (pnodeFnc->IsDerivedClassConstructor()) |
| { |
| varDeclNode->AsParseNodeVar()->sym->SetNeedDeclaration(true); |
| } |
| } |
| |
| // Create a 'new.target' symbol for any ordinary function with a reference and all class constructors. |
| varDeclNode = CreateSpecialVarDeclIfNeeded(pnodeFnc, wellKnownPropertyPids._newTarget, pnodeFnc->IsClassConstructor()); |
| if (varDeclNode) |
| { |
| varDeclNode->AsParseNodeVar()->sym->SetIsNewTarget(true); |
| } |
| |
| // Create a 'super' (as a reference) symbol. |
| varDeclNode = CreateSpecialVarDeclIfNeeded(pnodeFnc, wellKnownPropertyPids._super); |
| if (varDeclNode) |
| { |
| varDeclNode->AsParseNodeVar()->sym->SetIsSuper(true); |
| } |
| |
| // Create a 'super' (as the call target for super()) symbol only for derived class constructors. |
| varDeclNode = CreateSpecialVarDeclIfNeeded(pnodeFnc, wellKnownPropertyPids._superConstructor); |
| if (varDeclNode) |
| { |
| varDeclNode->AsParseNodeVar()->sym->SetIsSuperConstructor(true); |
| } |
| } |
| |
| void Parser::FinishParseBlock(ParseNodeBlock *pnodeBlock, bool needScanRCurly) |
| { |
| Assert(m_currentBlockInfo != nullptr && pnodeBlock == m_currentBlockInfo->pnodeBlock); |
| |
| if (needScanRCurly) |
| { |
| // Only update the ichLim if we were expecting an RCurly. If there is an |
| // expression body without a necessary RCurly, the correct ichLim will |
| // have been set already. |
| pnodeBlock->ichLim = this->GetScanner()->IchLimTok(); |
| } |
| |
| BindPidRefs<false>(GetCurrentBlockInfo(), m_nextBlockId - 1); |
| |
| PopStmt(&m_currentBlockInfo->pstmt); |
| |
| PopBlockInfo(); |
| |
| Scope *scope = pnodeBlock->scope; |
| if (scope) |
| { |
| PopScope(scope); |
| } |
| } |
| |
| void Parser::FinishParseFncExprScope(ParseNodeFnc * pnodeFnc, ParseNodeBlock * pnodeFncExprScope) |
| { |
| int fncExprScopeId = pnodeFncExprScope->blockId; |
| ParseNodePtr pnodeName = pnodeFnc->pnodeName; |
| if (pnodeName) |
| { |
| Assert(pnodeName->nop == knopVarDecl); |
| BindPidRefsInScope(pnodeName->AsParseNodeVar()->pid, pnodeName->AsParseNodeVar()->sym, fncExprScopeId, m_nextBlockId - 1); |
| } |
| FinishParseBlock(pnodeFncExprScope); |
| } |
| |
| template <const bool backgroundPidRef> |
| void Parser::BindPidRefs(BlockInfoStack *blockInfo, uint maxBlockId) |
| { |
| // We need to bind all assignments in order to emit assignment to 'const' error |
| int blockId = blockInfo->pnodeBlock->blockId; |
| |
| Scope *scope = blockInfo->pnodeBlock->scope; |
| if (scope) |
| { |
| auto bindPidRefs = [blockId, maxBlockId, this](Symbol *sym) |
| { |
| ParseNodePtr pnode = sym->GetDecl(); |
| IdentPtr pid; |
| #if PROFILE_DICTIONARY |
| int depth = 0; |
| #endif |
| Assert(pnode); |
| switch (pnode->nop) |
| { |
| case knopVarDecl: |
| case knopLetDecl: |
| case knopConstDecl: |
| pid = pnode->AsParseNodeVar()->pid; |
| if (backgroundPidRef) |
| { |
| pid = this->GetHashTbl()->FindExistingPid(pid->Psz(), pid->Psz() + pid->Cch(), pid->Cch(), pid->Hash(), nullptr, nullptr |
| #if PROFILE_DICTIONARY |
| , depth |
| #endif |
| ); |
| if (pid == nullptr) |
| { |
| break; |
| } |
| } |
| this->BindPidRefsInScope(pid, sym, blockId, maxBlockId); |
| break; |
| case knopName: |
| pid = pnode->AsParseNodeName()->pid; |
| if (backgroundPidRef) |
| { |
| pid = this->GetHashTbl()->FindExistingPid(pid->Psz(), pid->Psz() + pid->Cch(), pid->Cch(), pid->Hash(), nullptr, nullptr |
| #if PROFILE_DICTIONARY |
| , depth |
| #endif |
| ); |
| if (pid == nullptr) |
| { |
| break; |
| } |
| } |
| this->BindPidRefsInScope(pid, sym, blockId, maxBlockId); |
| break; |
| default: |
| Assert(0); |
| break; |
| } |
| }; |
| |
| scope->ForEachSymbol(bindPidRefs); |
| } |
| } |
| |
| void Parser::BindPidRefsInScope(IdentPtr pid, Symbol *sym, int blockId, uint maxBlockId) |
| { |
| PidRefStack *ref, *nextRef, *lastRef = nullptr; |
| Js::LocalFunctionId funcId = GetCurrentFunctionNode()->functionId; |
| Assert(sym); |
| |
| if (pid->GetIsModuleExport() && IsTopLevelModuleFunc()) |
| { |
| sym->SetIsModuleExportStorage(true); |
| } |
| |
| bool hasFuncAssignment = sym->GetHasFuncAssignment(); |
| bool doesEscape = false; |
| |
| for (ref = pid->GetTopRef(); ref && ref->GetScopeId() >= blockId; ref = nextRef) |
| { |
| // Fix up sym* on PID ref. |
| Assert(!ref->GetSym() || ref->GetSym() == sym); |
| nextRef = ref->prev; |
| Assert(ref->GetScopeId() >= 0); |
| if ((uint)ref->GetScopeId() > maxBlockId) |
| { |
| lastRef = ref; |
| continue; |
| } |
| ref->SetSym(sym); |
| this->RemovePrevPidRef(pid, lastRef); |
| |
| if (ref->IsUsedInLdElem()) |
| { |
| sym->SetIsUsedInLdElem(true); |
| } |
| |
| if (ref->IsAssignment()) |
| { |
| sym->PromoteAssignmentState(); |
| if (sym->GetIsFormal()) |
| { |
| GetCurrentFunctionNode()->SetHasAnyWriteToFormals(true); |
| } |
| } |
| |
| if (ref->GetFuncScopeId() != funcId && !sym->GetIsGlobal() && !sym->GetIsModuleExportStorage()) |
| { |
| Assert(ref->GetFuncScopeId() > funcId); |
| sym->SetHasNonLocalReference(); |
| if (ref->IsDynamicBinding()) |
| { |
| sym->SetNeedsScopeObject(); |
| } |
| } |
| |
| if (ref->IsFuncAssignment()) |
| { |
| hasFuncAssignment = true; |
| } |
| |
| if (ref->IsEscape()) |
| { |
| doesEscape = true; |
| } |
| |
| if (m_currentNodeFunc && doesEscape && hasFuncAssignment) |
| { |
| if (m_sourceContextInfo ? |
| !PHASE_OFF_RAW(Js::DisableStackFuncOnDeferredEscapePhase, m_sourceContextInfo->sourceContextId, m_currentNodeFunc->functionId) : |
| !PHASE_OFF1(Js::DisableStackFuncOnDeferredEscapePhase)) |
| { |
| m_currentNodeFunc->SetNestedFuncEscapes(); |
| } |
| } |
| |
| if (ref->GetScopeId() == blockId) |
| { |
| break; |
| } |
| } |
| } |
| |
| void Parser::MarkEscapingRef(ParseNodePtr pnode, IdentToken *pToken) |
| { |
| if (m_currentNodeFunc == nullptr) |
| { |
| return; |
| } |
| if (pnode && pnode->nop == knopFncDecl) |
| { |
| this->SetNestedFuncEscapes(); |
| } |
| else if (pToken->pid) |
| { |
| PidRefStack *pidRef = pToken->pid->GetTopRef(); |
| if (pidRef->sym) |
| { |
| if (pidRef->sym->GetSymbolType() == STFunction) |
| { |
| this->SetNestedFuncEscapes(); |
| } |
| } |
| else |
| { |
| pidRef->isEscape = true; |
| } |
| } |
| } |
| |
| void Parser::SetNestedFuncEscapes() const |
| { |
| if (m_sourceContextInfo ? |
| !PHASE_OFF_RAW(Js::DisableStackFuncOnDeferredEscapePhase, m_sourceContextInfo->sourceContextId, m_currentNodeFunc->functionId) : |
| !PHASE_OFF1(Js::DisableStackFuncOnDeferredEscapePhase)) |
| { |
| m_currentNodeFunc->SetNestedFuncEscapes(); |
| } |
| } |
| |
| void Parser::PopStmt(StmtNest *pStmt) |
| { |
| Assert(pStmt == m_pstmtCur); |
| SetCurrentStatement(m_pstmtCur->pstmtOuter); |
| } |
| |
| BlockInfoStack *Parser::PushBlockInfo(ParseNodeBlock * pnodeBlock) |
| { |
| BlockInfoStack *newBlockInfo = (BlockInfoStack *)m_nodeAllocator.Alloc(sizeof(BlockInfoStack)); |
| Assert(nullptr != newBlockInfo); |
| |
| newBlockInfo->pnodeBlock = pnodeBlock; |
| newBlockInfo->pBlockInfoOuter = m_currentBlockInfo; |
| newBlockInfo->m_ppnodeLex = &pnodeBlock->pnodeLexVars; |
| |
| if (pnodeBlock->blockType != PnodeBlockType::Regular) |
| { |
| newBlockInfo->pBlockInfoFunction = newBlockInfo; |
| } |
| else |
| { |
| Assert(m_currentBlockInfo); |
| newBlockInfo->pBlockInfoFunction = m_currentBlockInfo->pBlockInfoFunction; |
| } |
| |
| m_currentBlockInfo = newBlockInfo; |
| return newBlockInfo; |
| } |
| |
| void Parser::PopBlockInfo() |
| { |
| Assert(m_currentBlockInfo); |
| PopDynamicBlock(); |
| m_currentBlockInfo = m_currentBlockInfo->pBlockInfoOuter; |
| } |
| |
| void Parser::PushDynamicBlock() |
| { |
| Assert(GetCurrentBlock()); |
| int blockId = GetCurrentBlock()->blockId; |
| if (m_currentDynamicBlock && m_currentDynamicBlock->id == blockId) |
| { |
| return; |
| } |
| BlockIdsStack *info = (BlockIdsStack *)m_nodeAllocator.Alloc(sizeof(BlockIdsStack)); |
| if (nullptr == info) |
| { |
| Error(ERRnoMemory); |
| } |
| |
| info->id = blockId; |
| info->prev = m_currentDynamicBlock; |
| m_currentDynamicBlock = info; |
| } |
| |
| void Parser::PopDynamicBlock() |
| { |
| int blockId = GetCurrentDynamicBlockId(); |
| if (GetCurrentBlock()->blockId != blockId || blockId == -1) |
| { |
| return; |
| } |
| Assert(m_currentDynamicBlock); |
| |
| this->GetHashTbl()->VisitPids([&](IdentPtr pid) { |
| for (PidRefStack *ref = pid->GetTopRef(); ref && ref->GetScopeId() >= blockId; ref = ref->prev) |
| { |
| ref->SetDynamicBinding(); |
| } |
| }); |
| |
| m_currentDynamicBlock = m_currentDynamicBlock->prev; |
| } |
| |
| int Parser::GetCurrentDynamicBlockId() const |
| { |
| return m_currentDynamicBlock ? m_currentDynamicBlock->id : -1; |
| } |
| |
| ParseNodeFnc *Parser::GetCurrentFunctionNode() |
| { |
| if (m_currentNodeDeferredFunc != nullptr) |
| { |
| return m_currentNodeDeferredFunc; |
| } |
| else if (m_currentNodeFunc != nullptr) |
| { |
| return m_currentNodeFunc; |
| } |
| else |
| { |
| AssertMsg(GetFunctionBlock()->blockType == PnodeBlockType::Global, |
| "Most likely we are trying to find a syntax error, related to 'let' or 'const' in deferred parsing mode with disabled support of 'let' and 'const'"); |
| return m_currentNodeProg; |
| } |
| } |
| |
| ParseNodeFnc *Parser::GetCurrentNonLambdaFunctionNode() |
| { |
| if (m_currentNodeNonLambdaDeferredFunc != nullptr) |
| { |
| return m_currentNodeNonLambdaDeferredFunc; |
| } |
| return m_currentNodeNonLambdaFunc; |
| |
| } |
| void Parser::RegisterRegexPattern(UnifiedRegex::RegexPattern *const regexPattern) |
| { |
| Assert(regexPattern); |
| |
| // ensure a no-throw add behavior here, to catch out of memory exceptions, using the guest arena allocator |
| if (!m_registeredRegexPatterns.PrependNoThrow(m_tempGuestArena->GetAllocator(), regexPattern)) |
| { |
| Parser::Error(ERRnoMemory); |
| } |
| } |
| |
| void Parser::CaptureState(ParserState *state) |
| { |
| Assert(state != nullptr); |
| |
| state->m_funcInArraySave = m_funcInArray; |
| state->m_funcInArrayDepthSave = m_funcInArrayDepth; |
| state->m_nestedCountSave = *m_pnestedCount; |
| state->m_ppnodeScopeSave = m_ppnodeScope; |
| state->m_ppnodeExprScopeSave = m_ppnodeExprScope; |
| state->m_pCurrentAstSizeSave = m_pCurrentAstSize; |
| state->m_nextBlockId = m_nextBlockId; |
| |
| Assert(state->m_ppnodeScopeSave == nullptr || *state->m_ppnodeScopeSave == nullptr); |
| Assert(state->m_ppnodeExprScopeSave == nullptr || *state->m_ppnodeExprScopeSave == nullptr); |
| |
| #if DEBUG |
| state->m_currentBlockInfo = m_currentBlockInfo; |
| #endif |
| } |
| |
| void Parser::RestoreStateFrom(ParserState *state) |
| { |
| Assert(state != nullptr); |
| Assert(state->m_currentBlockInfo == m_currentBlockInfo); |
| |
| m_funcInArray = state->m_funcInArraySave; |
| m_funcInArrayDepth = state->m_funcInArrayDepthSave; |
| *m_pnestedCount = state->m_nestedCountSave; |
| m_pCurrentAstSize = state->m_pCurrentAstSizeSave; |
| m_nextBlockId = state->m_nextBlockId; |
| |
| if (state->m_ppnodeScopeSave != nullptr) |
| { |
| *state->m_ppnodeScopeSave = nullptr; |
| } |
| |
| if (state->m_ppnodeExprScopeSave != nullptr) |
| { |
| *state->m_ppnodeExprScopeSave = nullptr; |
| } |
| |
| m_ppnodeScope = state->m_ppnodeScopeSave; |
| m_ppnodeExprScope = state->m_ppnodeExprScopeSave; |
| } |
| |
| void Parser::AddToNodeListEscapedUse(ParseNode ** ppnodeList, ParseNode *** pppnodeLast, |
| ParseNode * pnodeAdd) |
| { |
| AddToNodeList(ppnodeList, pppnodeLast, pnodeAdd); |
| pnodeAdd->SetIsInList(); |
| } |
| |
| void Parser::AddToNodeList(ParseNode ** ppnodeList, ParseNode *** pppnodeLast, |
| ParseNode * pnodeAdd) |
| { |
| Assert(!this->m_deferringAST); |
| if (nullptr == *pppnodeLast) |
| { |
| // should be an empty list |
| Assert(nullptr == *ppnodeList); |
| |
| *ppnodeList = pnodeAdd; |
| *pppnodeLast = ppnodeList; |
| } |
| else |
| { |
| // |
| Assert(*ppnodeList); |
| Assert(**pppnodeLast); |
| |
| ParseNode *pnodeT = CreateBinNode(knopList, **pppnodeLast, pnodeAdd); |
| **pppnodeLast = pnodeT; |
| *pppnodeLast = &pnodeT->AsParseNodeBin()->pnode2; |
| } |
| } |
| |
| // Check reference to "arguments" that indicates the object may escape. |
| void Parser::CheckArguments(ParseNodePtr pnode) |
| { |
| if (m_currentNodeFunc && this->NodeIsIdent(pnode, wellKnownPropertyPids.arguments)) |
| { |
| m_currentNodeFunc->SetHasHeapArguments(); |
| } |
| } |
| |
| // Check use of "arguments" that requires instantiation of the object. |
| void Parser::CheckArgumentsUse(IdentPtr pid, ParseNodeFnc * pnodeFnc) |
| { |
| if (pid == wellKnownPropertyPids.arguments) |
| { |
| if (pnodeFnc != nullptr && pnodeFnc != m_currentNodeProg) |
| { |
| pnodeFnc->SetUsesArguments(TRUE); |
| } |
| else |
| { |
| m_UsesArgumentsAtGlobal = true; |
| } |
| } |
| } |
| |
| void Parser::CheckStrictModeEvalArgumentsUsage(IdentPtr pid, ParseNodePtr pnode) |
| { |
| if (pid != nullptr) |
| { |
| // In strict mode, 'eval' / 'arguments' cannot be assigned to. |
| if (pid == wellKnownPropertyPids.eval) |
| { |
| Error(ERREvalUsage, pnode); |
| } |
| |
| if (pid == wellKnownPropertyPids.arguments) |
| { |
| Error(ERRArgsUsage, pnode); |
| } |
| } |
| } |
| |
| void Parser::ReduceDeferredScriptLength(size_t chars) |
| { |
| // If we're in deferred mode, subtract the given char count from the total length, |
| // and see if this puts us under the deferral threshold. |
| if (((m_grfscr & (fscrCanDeferFncParse | fscrWillDeferFncParse)) == (fscrCanDeferFncParse | fscrWillDeferFncParse)) && |
| ( |
| PHASE_OFF1(Js::DeferEventHandlersPhase) || |
| (m_grfscr & fscrGlobalCode) |
| ) |
| ) |
| { |
| if (m_length > chars) |
| { |
| m_length -= chars; |
| } |
| else |
| { |
| m_length = 0; |
| } |
| if (m_length < Parser::GetDeferralThreshold(this->m_sourceContextInfo->IsSourceProfileLoaded())) |
| { |
| // Stop deferring. |
| m_grfscr &= ~fscrWillDeferFncParse; |
| m_stoppedDeferredParse = TRUE; |
| } |
| } |
| } |
| |
| void Parser::EnsureStackAvailable() |
| { |
| bool isInterrupt = false; |
| if (!m_scriptContext->GetThreadContext()->IsStackAvailable(Js::Constants::MinStackCompile, &isInterrupt)) |
| { |
| Error(isInterrupt ? E_ABORT : VBSERR_OutOfStack); |
| } |
| } |
| |
| void Parser::ThrowNewTargetSyntaxErrForGlobalScope() |
| { |
| // If we are parsing a previously deferred function, we can skip throwing the SyntaxError for `new.target` at global scope. |
| // If we are at global scope, we would have thrown a SyntaxError when we did the Upfront parse pass and we would not have |
| // deferred the function in order to come back now and reparse it. |
| if (m_parseType == ParseType_Deferred) |
| { |
| return; |
| } |
| |
| if (GetCurrentNonLambdaFunctionNode() != nullptr) |
| { |
| return; |
| } |
| |
| if ((this->m_grfscr & fscrEval) != 0) |
| { |
| Js::JavascriptFunction * caller = nullptr; |
| if (Js::JavascriptStackWalker::GetCaller(&caller, m_scriptContext)) |
| { |
| Js::FunctionBody * callerBody = caller->GetFunctionBody(); |
| Assert(callerBody); |
| if (!callerBody->GetIsGlobalFunc() && !(callerBody->IsLambda() && callerBody->GetEnclosedByGlobalFunc())) |
| { |
| return; |
| } |
| } |
| } |
| |
| Error(ERRInvalidNewTarget); |
| } |
| |
| template<bool buildAST> |
| IdentPtr Parser::ParseMetaProperty(tokens metaParentKeyword, charcount_t ichMin, _Out_opt_ BOOL* pfCanAssign) |
| { |
| AssertMsg(metaParentKeyword == tkNEW, "Only supported for tkNEW parent keywords"); |
| AssertMsg(this->m_token.tk == tkDot, "We must be currently sitting on the dot after the parent keyword"); |
| |
| this->GetScanner()->Scan(); |
| |
| if (this->m_token.tk == tkID && this->m_token.GetIdentifier(this->GetHashTbl()) == this->GetTargetPid()) |
| { |
| ThrowNewTargetSyntaxErrForGlobalScope(); |
| if (pfCanAssign) |
| { |
| *pfCanAssign = FALSE; |
| } |
| return wellKnownPropertyPids._newTarget; |
| } |
| else |
| { |
| Error(ERRValidIfFollowedBy, _u("'new.'"), _u("'target'")); |
| } |
| } |
| |
| template<bool buildAST> |
| void Parser::ParseNamedImportOrExportClause(ModuleImportOrExportEntryList* importOrExportEntryList, bool isExportClause) |
| { |
| Assert(m_token.tk == tkLCurly); |
| Assert(importOrExportEntryList != nullptr); |
| |
| this->GetScanner()->Scan(); |
| |
| while (m_token.tk != tkRCurly && m_token.tk != tkEOF) |
| { |
| tokens firstToken = m_token.tk; |
| |
| if (!(m_token.IsIdentifier() || m_token.IsReservedWord())) |
| { |
| Error(ERRsyntax); |
| } |
| |
| IdentPtr identifierName = m_token.GetIdentifier(this->GetHashTbl()); |
| IdentPtr identifierAs = identifierName; |
| charcount_t offsetForError = this->GetScanner()->IchMinTok(); |
| |
| this->GetScanner()->Scan(); |
| |
| if (m_token.tk == tkID) |
| { |
| // We have the pattern "IdentifierName as" |
| if (wellKnownPropertyPids.as != m_token.GetIdentifier(this->GetHashTbl())) |
| { |
| Error(ERRsyntax); |
| } |
| |
| this->GetScanner()->Scan(); |
| |
| // If we are parsing an import statement, the token after 'as' must be a BindingIdentifier. |
| if (!isExportClause) |
| { |
| ChkCurTokNoScan(tkID, ERRValidIfFollowedBy, _u("'as'"), _u("an identifier.")); |
| } |
| |
| if (!(m_token.IsIdentifier() || m_token.IsReservedWord())) |
| { |
| Error(ERRValidIfFollowedBy, _u("'as'"), _u("an identifier.")); |
| } |
| |
| identifierAs = m_token.GetIdentifier(this->GetHashTbl()); |
| |
| // Scan to the next token. |
| this->GetScanner()->Scan(); |
| } |
| else if (!isExportClause && firstToken != tkID) |
| { |
| // If we are parsing an import statement and this ImportSpecifier clause did not have |
| // 'as ImportedBinding' at the end of it, identifierName must be a BindingIdentifier. |
| Error(ERRsyntax); |
| } |
| |
| if (m_token.tk == tkComma) |
| { |
| // Consume a trailing comma |
| this->GetScanner()->Scan(); |
| } |
| |
| if (isExportClause) |
| { |
| identifierName->SetIsModuleExport(); |
| AddModuleImportOrExportEntry(importOrExportEntryList, nullptr, identifierName, identifierAs, nullptr, offsetForError); |
| } |
| else if (buildAST) |
| { |
| // The name we will use 'as' this import/export is a binding identifier in import statements. |
| CreateModuleImportDeclNode(identifierAs); |
| AddModuleImportOrExportEntry(importOrExportEntryList, identifierName, identifierAs, nullptr, nullptr); |
| } |
| } |
| |
| // Final token in a named import or export clause must be a '}' |
| ChkCurTokNoScan(tkRCurly, ERRsyntax); |
| } |
| |
| IdentPtrList* Parser::GetRequestedModulesList() |
| { |
| return m_currentNodeProg->AsParseNodeModule()->requestedModules; |
| } |
| |
| void Parser::VerifyModuleLocalExportEntries() |
| { |
| ModuleImportOrExportEntryList* localExportRecordList = GetModuleLocalExportEntryList(); |
| if (localExportRecordList != nullptr) |
| { |
| localExportRecordList->Map([=](ModuleImportOrExportEntry exportEntry) { |
| if (exportEntry.pidRefStack!=nullptr) |
| { |
| if (exportEntry.pidRefStack->GetSym() == nullptr) |
| { |
| Error(ERRUndeclaredExportName, exportEntry.offset, exportEntry.localName->Cch(), exportEntry.localName->Psz()); |
| } |
| } |
| }); |
| } |
| } |
| |
| ModuleImportOrExportEntryList* Parser::GetModuleImportEntryList() |
| { |
| return m_currentNodeProg->AsParseNodeModule()->importEntries; |
| } |
| |
| ModuleImportOrExportEntryList* Parser::GetModuleLocalExportEntryList() |
| { |
| return m_currentNodeProg->AsParseNodeModule()->localExportEntries; |
| } |
| |
| ModuleImportOrExportEntryList* Parser::GetModuleIndirectExportEntryList() |
| { |
| return m_currentNodeProg->AsParseNodeModule()->indirectExportEntries; |
| } |
| |
| ModuleImportOrExportEntryList* Parser::GetModuleStarExportEntryList() |
| { |
| return m_currentNodeProg->AsParseNodeModule()->starExportEntries; |
| } |
| |
| IdentPtrList* Parser::EnsureRequestedModulesList() |
| { |
| if (m_currentNodeProg->AsParseNodeModule()->requestedModules == nullptr) |
| { |
| m_currentNodeProg->AsParseNodeModule()->requestedModules = Anew(&m_nodeAllocator, IdentPtrList, &m_nodeAllocator); |
| } |
| return m_currentNodeProg->AsParseNodeModule()->requestedModules; |
| } |
| |
| ModuleImportOrExportEntryList* Parser::EnsureModuleImportEntryList() |
| { |
| if (m_currentNodeProg->AsParseNodeModule()->importEntries == nullptr) |
| { |
| m_currentNodeProg->AsParseNodeModule()->importEntries = Anew(&m_nodeAllocator, ModuleImportOrExportEntryList, &m_nodeAllocator); |
| } |
| return m_currentNodeProg->AsParseNodeModule()->importEntries; |
| } |
| |
| ModuleImportOrExportEntryList* Parser::EnsureModuleLocalExportEntryList() |
| { |
| if (m_currentNodeProg->AsParseNodeModule()->localExportEntries == nullptr) |
| { |
| m_currentNodeProg->AsParseNodeModule()->localExportEntries = Anew(&m_nodeAllocator, ModuleImportOrExportEntryList, &m_nodeAllocator); |
| } |
| return m_currentNodeProg->AsParseNodeModule()->localExportEntries; |
| } |
| |
| ModuleImportOrExportEntryList* Parser::EnsureModuleIndirectExportEntryList() |
| { |
| if (m_currentNodeProg->AsParseNodeModule()->indirectExportEntries == nullptr) |
| { |
| m_currentNodeProg->AsParseNodeModule()->indirectExportEntries = Anew(&m_nodeAllocator, ModuleImportOrExportEntryList, &m_nodeAllocator); |
| } |
| return m_currentNodeProg->AsParseNodeModule()->indirectExportEntries; |
| } |
| |
| ModuleImportOrExportEntryList* Parser::EnsureModuleStarExportEntryList() |
| { |
| if (m_currentNodeProg->AsParseNodeModule()->starExportEntries == nullptr) |
| { |
| m_currentNodeProg->AsParseNodeModule()->starExportEntries = Anew(&m_nodeAllocator, ModuleImportOrExportEntryList, &m_nodeAllocator); |
| } |
| return m_currentNodeProg->AsParseNodeModule()->starExportEntries; |
| } |
| |
| void Parser::AddModuleSpecifier(IdentPtr moduleRequest) |
| { |
| IdentPtrList* requestedModulesList = EnsureRequestedModulesList(); |
| |
| if (!requestedModulesList->Has(moduleRequest)) |
| { |
| requestedModulesList->Prepend(moduleRequest); |
| } |
| } |
| |
| ModuleImportOrExportEntry* Parser::AddModuleImportOrExportEntry(ModuleImportOrExportEntryList* importOrExportEntryList, ModuleImportOrExportEntry* importOrExportEntry) |
| { |
| if (importOrExportEntry->exportName != nullptr) |
| { |
| CheckForDuplicateExportEntry(importOrExportEntry->exportName); |
| } |
| |
| importOrExportEntryList->Prepend(*importOrExportEntry); |
| |
| return importOrExportEntry; |
| } |
| |
| ModuleImportOrExportEntry* Parser::AddModuleImportOrExportEntry(ModuleImportOrExportEntryList* importOrExportEntryList, IdentPtr importName, IdentPtr localName, IdentPtr exportName, IdentPtr moduleRequest, charcount_t offsetForError) |
| { |
| ModuleImportOrExportEntry* importOrExportEntry = Anew(&m_nodeAllocator, ModuleImportOrExportEntry); |
| |
| importOrExportEntry->importName = importName; |
| importOrExportEntry->localName = localName; |
| importOrExportEntry->exportName = exportName; |
| importOrExportEntry->moduleRequest = moduleRequest; |
| importOrExportEntry->pidRefStack = offsetForError == 0 ? nullptr : PushPidRef(localName); |
| importOrExportEntry->offset = offsetForError; |
| |
| return AddModuleImportOrExportEntry(importOrExportEntryList, importOrExportEntry); |
| } |
| |
| void Parser::AddModuleLocalExportEntry(ParseNodePtr varDeclNode) |
| { |
| AssertOrFailFast(varDeclNode->nop == knopVarDecl || varDeclNode->nop == knopLetDecl || varDeclNode->nop == knopConstDecl); |
| |
| IdentPtr localName = varDeclNode->AsParseNodeVar()->pid; |
| varDeclNode->AsParseNodeVar()->sym->SetIsModuleExportStorage(true); |
| |
| AddModuleImportOrExportEntry(EnsureModuleLocalExportEntryList(), nullptr, localName, localName, nullptr); |
| } |
| |
| void Parser::CheckForDuplicateExportEntry(IdentPtr exportName) |
| { |
| if (m_currentNodeProg->AsParseNodeModule()->indirectExportEntries != nullptr) |
| { |
| CheckForDuplicateExportEntry(m_currentNodeProg->AsParseNodeModule()->indirectExportEntries, exportName); |
| } |
| |
| if (m_currentNodeProg->AsParseNodeModule()->localExportEntries != nullptr) |
| { |
| CheckForDuplicateExportEntry(m_currentNodeProg->AsParseNodeModule()->localExportEntries, exportName); |
| } |
| } |
| |
| void Parser::CheckForDuplicateExportEntry(ModuleImportOrExportEntryList* exportEntryList, IdentPtr exportName) |
| { |
| ModuleImportOrExportEntry* findResult = exportEntryList->Find([&](ModuleImportOrExportEntry exportEntry) |
| { |
| if (exportName == exportEntry.exportName) |
| { |
| return true; |
| } |
| return false; |
| }); |
| |
| if (findResult != nullptr) |
| { |
| Error(ERRDuplicateExport, exportName->Psz()); |
| } |
| } |
| |
| template<bool buildAST> |
| void Parser::ParseImportClause(ModuleImportOrExportEntryList* importEntryList, bool parsingAfterComma) |
| { |
| bool parsedNamespaceOrNamedImport = false; |
| |
| switch (m_token.tk) |
| { |
| case tkID: |
| // This is the default binding identifier. |
| |
| // If we already saw a comma in the import clause, this is a syntax error. |
| if (parsingAfterComma) |
| { |
| Error(ERRsyntax); |
| } |
| |
| if (buildAST) |
| { |
| IdentPtr localName = m_token.GetIdentifier(this->GetHashTbl()); |
| IdentPtr importName = wellKnownPropertyPids._default; |
| |
| CreateModuleImportDeclNode(localName); |
| AddModuleImportOrExportEntry(importEntryList, importName, localName, nullptr, nullptr); |
| } |
| |
| break; |
| |
| case tkLCurly: |
| // This begins a list of named imports. |
| ParseNamedImportOrExportClause<buildAST>(importEntryList, false); |
| |
| parsedNamespaceOrNamedImport = true; |
| break; |
| |
| case tkStar: |
| // This begins a namespace import clause. |
| // "* as ImportedBinding" |
| |
| // Token following * must be the identifier 'as' |
| this->GetScanner()->Scan(); |
| if (m_token.tk != tkID || wellKnownPropertyPids.as != m_token.GetIdentifier(this->GetHashTbl())) |
| { |
| Error(ERRsyntax); |
| } |
| |
| // Token following 'as' must be a binding identifier. |
| this->GetScanner()->Scan(); |
| ChkCurTokNoScan(tkID, ERRsyntax); |
| |
| if (buildAST) |
| { |
| IdentPtr localName = m_token.GetIdentifier(this->GetHashTbl()); |
| IdentPtr importName = wellKnownPropertyPids._star; |
| |
| CreateModuleImportDeclNode(localName); |
| AddModuleImportOrExportEntry(importEntryList, importName, localName, nullptr, nullptr); |
| } |
| |
| parsedNamespaceOrNamedImport = true; |
| break; |
| |
| default: |
| Error(ERRsyntax); |
| } |
| |
| this->GetScanner()->Scan(); |
| |
| if (m_token.tk == tkComma) |
| { |
| // There cannot be more than one comma in a module import clause. |
| // There cannot be a namespace import or named imports list on the left of the comma in a module import clause. |
| if (parsingAfterComma || parsedNamespaceOrNamedImport) |
| { |
| Error(ERRTokenAfter, _u(","), GetTokenString(this->GetScanner()->GetPrevious())); |
| } |
| |
| this->GetScanner()->Scan(); |
| |
| ParseImportClause<buildAST>(importEntryList, true); |
| } |
| } |
| |
| bool Parser::IsImportOrExportStatementValidHere() |
| { |
| ParseNodeFnc * curFunc = GetCurrentFunctionNode(); |
| |
| // Import must be located in the top scope of the module body. |
| return curFunc->nop == knopFncDecl |
| && curFunc->IsModule() |
| && this->m_currentBlockInfo->pnodeBlock == curFunc->pnodeBodyScope |
| && (this->m_grfscr & fscrEvalCode) != fscrEvalCode |
| && this->m_tryCatchOrFinallyDepth == 0 |
| && !this->m_disallowImportExportStmt; |
| } |
| |
| bool Parser::IsTopLevelModuleFunc() |
| { |
| ParseNodeFnc * curFunc = GetCurrentFunctionNode(); |
| return curFunc->nop == knopFncDecl && curFunc->IsModule(); |
| } |
| |
| template<bool buildAST> ParseNodePtr Parser::ParseImportCall() |
| { |
| this->GetScanner()->Scan(); |
| ParseNodePtr specifier = ParseExpr<buildAST>(koplCma, nullptr, /* fAllowIn */FALSE, /* fAllowEllipsis */FALSE); |
| if (m_token.tk != tkRParen) |
| { |
| Error(ERRnoRparen); |
| } |
| |
| this->GetScanner()->Scan(); |
| return buildAST ? CreateCallNode(knopCall, CreateNodeForOpT<knopImport>(), specifier) : nullptr; |
| } |
| |
| template<bool buildAST> |
| ParseNodePtr Parser::ParseImport() |
| { |
| Assert(m_scriptContext->GetConfig()->IsES6ModuleEnabled()); |
| Assert(m_token.tk == tkIMPORT); |
| |
| RestorePoint parsedImport; |
| this->GetScanner()->Capture(&parsedImport); |
| this->GetScanner()->Scan(); |
| |
| // import() |
| if (m_token.tk == tkLParen) |
| { |
| if (!m_scriptContext->GetConfig()->IsESDynamicImportEnabled()) |
| { |
| Error(ERRExperimental); |
| } |
| |
| ParseNodePtr pnode = ParseImportCall<buildAST>(); |
| BOOL fCanAssign; |
| IdentToken token; |
| return ParsePostfixOperators<buildAST>(pnode, TRUE, FALSE, FALSE, TRUE, &fCanAssign, &token); |
| } |
| |
| this->GetScanner()->SeekTo(parsedImport); |
| |
| if (!IsImportOrExportStatementValidHere()) |
| { |
| Error(ERRInvalidModuleImportOrExport); |
| } |
| |
| // We just parsed an import token. Next valid token is *, {, string constant, or binding identifier. |
| this->GetScanner()->Scan(); |
| |
| if (m_token.tk == tkStrCon) |
| { |
| // This import declaration has no import clause. |
| // "import ModuleSpecifier;" |
| if (buildAST) |
| { |
| AddModuleSpecifier(m_token.GetStr()); |
| } |
| |
| // Scan past the module identifier. |
| this->GetScanner()->Scan(); |
| } |
| else |
| { |
| ModuleImportOrExportEntryList importEntryList(&m_nodeAllocator); |
| |
| // Parse the import clause (default binding can only exist before the comma). |
| ParseImportClause<buildAST>(&importEntryList); |
| |
| // Token following import clause must be the identifier 'from' |
| IdentPtr moduleSpecifier = ParseImportOrExportFromClause<buildAST>(true); |
| |
| if (buildAST) |
| { |
| Assert(moduleSpecifier != nullptr); |
| |
| AddModuleSpecifier(moduleSpecifier); |
| |
| importEntryList.Map([this, moduleSpecifier](ModuleImportOrExportEntry& importEntry) { |
| importEntry.moduleRequest = moduleSpecifier; |
| AddModuleImportOrExportEntry(EnsureModuleImportEntryList(), &importEntry); |
| }); |
| } |
| |
| importEntryList.Clear(); |
| } |
| |
| // Import statement is actually a nop, we hoist all the imported bindings to the top of the module. |
| return nullptr; |
| } |
| |
| template<bool buildAST> |
| IdentPtr Parser::ParseImportOrExportFromClause(bool throwIfNotFound) |
| { |
| IdentPtr moduleSpecifier = nullptr; |
| |
| if (m_token.tk == tkID && wellKnownPropertyPids.from == m_token.GetIdentifier(this->GetHashTbl())) |
| { |
| this->GetScanner()->Scan(); |
| |
| // Token following the 'from' token must be a string constant - the module specifier. |
| ChkCurTokNoScan(tkStrCon, ERRsyntax); |
| |
| if (buildAST) |
| { |
| moduleSpecifier = m_token.GetStr(); |
| } |
| |
| this->GetScanner()->Scan(); |
| } |
| else if (throwIfNotFound) |
| { |
| Error(ERRsyntax); |
| } |
| |
| return moduleSpecifier; |
| } |
| |
| template<bool buildAST> |
| ParseNodePtr Parser::ParseDefaultExportClause() |
| { |
| Assert(m_token.tk == tkDEFAULT); |
| |
| this->GetScanner()->Scan(); |
| ParseNodePtr pnode = nullptr; |
| ushort flags = fFncNoFlgs; |
| |
| switch (m_token.tk) |
| { |
| case tkCLASS: |
| { |
| if (!m_scriptContext->GetConfig()->IsES6ClassAndExtendsEnabled()) |
| { |
| goto LDefault; |
| } |
| |
| // Before we parse the class itself we need to know if the class has an identifier name. |
| // If it does, we'll treat this class as an ordinary class declaration which will bind |
| // it to that name. Otherwise the class should parse as a nameless class expression and |
| // bind only to the export binding. |
| BOOL classHasName = false; |
| RestorePoint parsedClass; |
| this->GetScanner()->Capture(&parsedClass); |
| this->GetScanner()->Scan(); |
| |
| if (m_token.tk == tkID) |
| { |
| classHasName = true; |
| } |
| |
| this->GetScanner()->SeekTo(parsedClass); |
| ParseNodeClass * pnodeClass; |
| pnode = pnodeClass = ParseClassDecl<buildAST>(classHasName, nullptr, nullptr, nullptr); |
| |
| if (buildAST) |
| { |
| AnalysisAssert(pnode != nullptr); |
| Assert(pnode->nop == knopClassDecl); |
| |
| pnodeClass->SetIsDefaultModuleExport(true); |
| } |
| |
| break; |
| } |
| case tkID: |
| // If we parsed an async token, it could either modify the next token (if it is a |
| // function token) or it could be an identifier (let async = 0; export default async;). |
| // To handle both cases, when we parse an async token we need to keep the parser state |
| // and rewind if the next token is not function. |
| if (wellKnownPropertyPids.async == m_token.GetIdentifier(this->GetHashTbl())) |
| { |
| RestorePoint parsedAsync; |
| this->GetScanner()->Capture(&parsedAsync); |
| this->GetScanner()->Scan(); |
| if (m_token.tk == tkFUNCTION) |
| { |
| // Token after async is function, consume the async token and continue to parse the |
| // function as an async function. |
| flags |= fFncAsync; |
| goto LFunction; |
| } |
| // Token after async is not function, no idea what the async token is supposed to mean |
| // so rewind and let the default case handle it. |
| this->GetScanner()->SeekTo(parsedAsync); |
| } |
| goto LDefault; |
| break; |
| case tkFUNCTION: |
| { |
| LFunction: |
| // We just parsed a function token but we need to figure out if the function |
| // has an identifier name or not before we call the helper. |
| RestorePoint parsedFunction; |
| this->GetScanner()->Capture(&parsedFunction); |
| this->GetScanner()->Scan(); |
| |
| if (m_token.tk == tkStar) |
| { |
| // If we saw 'function*' that indicates we are going to parse a generator, |
| // but doesn't tell us if the generator has an identifier or not. |
| // Skip the '*' token for now as it doesn't matter yet. |
| this->GetScanner()->Scan(); |
| } |
| |
| // We say that if the function has an identifier name, it is a 'normal' declaration |
| // and should create a binding to that identifier as well as one for our default export. |
| if (m_token.tk == tkID) |
| { |
| flags |= fFncDeclaration; |
| } |
| else |
| { |
| flags |= fFncNoName; |
| } |
| |
| // Rewind back to the function token and let the helper handle the parsing. |
| this->GetScanner()->SeekTo(parsedFunction); |
| pnode = ParseFncDeclCheckScope<buildAST>(flags); |
| |
| if (buildAST) |
| { |
| AnalysisAssert(pnode != nullptr); |
| Assert(pnode->nop == knopFncDecl); |
| |
| pnode->AsParseNodeFnc()->SetIsDefaultModuleExport(true); |
| } |
| break; |
| } |
| default: |
| LDefault: |
| { |
| ParseNodePtr pnodeExpression = ParseExpr<buildAST>(); |
| |
| // Consider: Can we detect this syntax error earlier? |
| if (pnodeExpression && pnodeExpression->nop == knopComma) |
| { |
| Error(ERRsyntax); |
| } |
| |
| if (buildAST) |
| { |
| AnalysisAssert(pnodeExpression != nullptr); |
| |
| // Mark this node as the default module export. We need to make sure it is put into the correct |
| // module export slot when we emit the node. |
| ParseNodeExportDefault * pnodeExportDefault; |
| pnode = pnodeExportDefault = CreateNodeForOpT<knopExportDefault>(); |
| pnode->AsParseNodeExportDefault()->pnodeExpr = pnodeExpression; |
| } |
| break; |
| } |
| } |
| |
| IdentPtr exportName = wellKnownPropertyPids._default; |
| AddModuleImportOrExportEntry(EnsureModuleLocalExportEntryList(), nullptr, exportName, exportName, nullptr); |
| |
| return pnode; |
| } |
| |
| template<bool buildAST> |
| ParseNodePtr Parser::ParseExportDeclaration(bool *needTerminator) |
| { |
| Assert(m_scriptContext->GetConfig()->IsES6ModuleEnabled()); |
| Assert(m_token.tk == tkEXPORT); |
| |
| if (!IsImportOrExportStatementValidHere()) |
| { |
| Error(ERRInvalidModuleImportOrExport); |
| } |
| |
| ParseNodePtr pnode = nullptr; |
| IdentPtr moduleIdentifier = nullptr; |
| tokens declarationType; |
| |
| if (needTerminator != nullptr) |
| { |
| *needTerminator = false; |
| } |
| |
| // We just parsed an export token. Next valid tokens are *, {, var, let, const, async, function, class, default. |
| this->GetScanner()->Scan(); |
| |
| switch (m_token.tk) |
| { |
| case tkStar: |
| { |
| this->GetScanner()->Scan(); |
| IdentPtr exportName = nullptr; |
| |
| if (m_scriptContext->GetConfig()->IsESExportNsAsEnabled()) |
| { |
| // export * as name |
| if (m_token.tk == tkID) |
| { |
| // check for 'as' |
| if (wellKnownPropertyPids.as == m_token.GetIdentifier(this->GetHashTbl())) |
| { |
| // scan to the next token |
| this->GetScanner()->Scan(); |
| |
| // token after as must be an identifier |
| if (!(m_token.IsIdentifier() || m_token.IsReservedWord())) |
| { |
| Error(ERRValidIfFollowedBy, _u("'as'"), _u("an identifier.")); |
| } |
| |
| exportName = m_token.GetIdentifier(this->GetHashTbl()); |
| |
| // scan to next token |
| this->GetScanner()->Scan(); |
| } |
| } |
| } |
| |
| // A star token in an export declaration must be followed by a from clause which begins with a token 'from'. |
| moduleIdentifier = ParseImportOrExportFromClause<buildAST>(true); |
| |
| if (buildAST) |
| { |
| Assert(moduleIdentifier != nullptr); |
| |
| AddModuleSpecifier(moduleIdentifier); |
| |
| if (!exportName) |
| { |
| AddModuleImportOrExportEntry(EnsureModuleStarExportEntryList(), wellKnownPropertyPids._star, nullptr, nullptr, moduleIdentifier); |
| } |
| else |
| { |
| CheckForDuplicateExportEntry(exportName); |
| AddModuleImportOrExportEntry(EnsureModuleIndirectExportEntryList(), wellKnownPropertyPids._star, nullptr, exportName, moduleIdentifier); |
| } |
| } |
| |
| if (needTerminator != nullptr) |
| { |
| *needTerminator = true; |
| } |
| |
| break; |
| } |
| |
| case tkLCurly: |
| { |
| ModuleImportOrExportEntryList exportEntryList(&m_nodeAllocator); |
| |
| ParseNamedImportOrExportClause<buildAST>(&exportEntryList, true); |
| |
| this->GetScanner()->Scan(); |
| |
| // Export clause may be followed by a from clause. |
| moduleIdentifier = ParseImportOrExportFromClause<buildAST>(false); |
| |
| if (buildAST) |
| { |
| if (moduleIdentifier != nullptr) |
| { |
| AddModuleSpecifier(moduleIdentifier); |
| } |
| |
| exportEntryList.Map([this, moduleIdentifier](ModuleImportOrExportEntry& exportEntry) { |
| if (moduleIdentifier != nullptr) |
| { |
| exportEntry.moduleRequest = moduleIdentifier; |
| |
| // We need to swap localname and importname when this is a re-export. |
| exportEntry.importName = exportEntry.localName; |
| exportEntry.localName = nullptr; |
| |
| AddModuleImportOrExportEntry(EnsureModuleIndirectExportEntryList(), &exportEntry); |
| } |
| else |
| { |
| AddModuleImportOrExportEntry(EnsureModuleLocalExportEntryList(), &exportEntry); |
| } |
| }); |
| |
| exportEntryList.Clear(); |
| } |
| } |
| |
| if (needTerminator != nullptr) |
| { |
| *needTerminator = true; |
| } |
| |
| break; |
| |
| case tkID: |
| { |
| IdentPtr pid = m_token.GetIdentifier(this->GetHashTbl()); |
| |
| if (wellKnownPropertyPids.let == pid) |
| { |
| declarationType = tkLET; |
| goto ParseVarDecl; |
| } |
| if (wellKnownPropertyPids.async == pid && m_scriptContext->GetConfig()->IsES7AsyncAndAwaitEnabled()) |
| { |
| // In module export statements, async token is only valid if it's followed by function. |
| // We need to check here because ParseStatement would think 'async = 20' is a var decl. |
| RestorePoint parsedAsync; |
| this->GetScanner()->Capture(&parsedAsync); |
| this->GetScanner()->Scan(); |
| if (m_token.tk == tkFUNCTION) |
| { |
| // Token after async is function, rewind to the async token and let ParseStatement handle it. |
| this->GetScanner()->SeekTo(parsedAsync); |
| goto ParseFunctionDecl; |
| } |
| // Token after async is not function, it's a syntax error. |
| } |
| goto ErrorToken; |
| } |
| case tkVAR: |
| case tkLET: |
| case tkCONST: |
| { |
| declarationType = m_token.tk; |
| |
| ParseVarDecl: |
| this->GetScanner()->Scan(); |
| |
| pnode = ParseVariableDeclaration<buildAST>(declarationType, this->GetScanner()->IchMinTok()); |
| |
| if (buildAST) |
| { |
| ForEachItemInList(pnode, [&](ParseNodePtr item) { |
| if (item->nop == knopAsg) |
| { |
| Parser::MapBindIdentifier(item, [&](ParseNodePtr subItem) |
| { |
| AddModuleLocalExportEntry(subItem); |
| }); |
| } |
| else |
| { |
| AddModuleLocalExportEntry(item); |
| } |
| }); |
| } |
| } |
| break; |
| |
| case tkFUNCTION: |
| case tkCLASS: |
| { |
| ParseFunctionDecl: |
| pnode = ParseStatement<buildAST>(); |
| |
| if (buildAST) |
| { |
| IdentPtr localName; |
| if (pnode->nop == knopClassDecl) |
| { |
| ParseNodeClass * pnodeClass = pnode->AsParseNodeClass(); |
| pnodeClass->pnodeDeclName->sym->SetIsModuleExportStorage(true); |
| localName = pnodeClass->pnodeName->pid; |
| } |
| else |
| { |
| Assert(pnode->nop == knopFncDecl); |
| |
| ParseNodeFnc * pnodeFnc = pnode->AsParseNodeFnc(); |
| pnodeFnc->GetFuncSymbol()->SetIsModuleExportStorage(true); |
| localName = pnodeFnc->pid; |
| } |
| Assert(localName != nullptr); |
| |
| AddModuleImportOrExportEntry(EnsureModuleLocalExportEntryList(), nullptr, localName, localName, nullptr); |
| } |
| } |
| break; |
| |
| case tkDEFAULT: |
| { |
| pnode = ParseDefaultExportClause<buildAST>(); |
| } |
| break; |
| |
| default: |
| { |
| ErrorToken: |
| Error(ERRsyntax); |
| } |
| } |
| |
| return pnode; |
| } |
| |
| /*************************************************************************** |
| Parse an expression term. |
| ***************************************************************************/ |
| template<bool buildAST> |
| ParseNodePtr Parser::ParseTerm(BOOL fAllowCall, |
| LPCOLESTR pNameHint, |
| uint32 *pHintLength, |
| uint32 *pShortNameOffset, |
| _Inout_opt_ IdentToken* pToken /*= nullptr*/, |
| bool fUnaryOrParen /*= false*/, |
| BOOL fCanAssignToCall /*= TRUE*/, |
| _Out_opt_ BOOL* pfCanAssign /*= nullptr*/, |
| _Inout_opt_ BOOL* pfLikelyPattern /*= nullptr*/, |
| _Out_opt_ bool* pfIsDotOrIndex /*= nullptr*/, |
| _Inout_opt_ charcount_t *plastRParen /*= nullptr*/) |
| { |
| ParseNodePtr pnode = nullptr; |
| PidRefStack *savedTopAsyncRef = nullptr; |
| charcount_t ichMin = 0; |
| charcount_t ichLim = 0; |
| size_t iecpMin = 0; |
| size_t iecpLim = 0; |
| size_t iuMin; |
| IdentToken term; |
| BOOL fInNew = FALSE; |
| BOOL fCanAssign = TRUE; |
| bool isAsyncExpr = false; |
| bool isLambdaExpr = false; |
| bool isSpecialName = false; |
| IdentPtr pid = nullptr; |
| Assert(pToken == nullptr || pToken->tk == tkNone); // Must be empty initially |
| |
| PROBE_STACK_NO_DISPOSE(m_scriptContext, Js::Constants::MinStackParseOneTerm); |
| |
| switch (m_token.tk) |
| { |
| case tkID: |
| { |
| pid = m_token.GetIdentifier(this->GetHashTbl()); |
| ichMin = this->GetScanner()->IchMinTok(); |
| iecpMin = this->GetScanner()->IecpMinTok(); |
| ichLim = this->GetScanner()->IchLimTok(); |
| iecpLim = this->GetScanner()->IecpLimTok(); |
| |
| if (pid == wellKnownPropertyPids.async && |
| m_scriptContext->GetConfig()->IsES7AsyncAndAwaitEnabled()) |
| { |
| isAsyncExpr = true; |
| } |
| |
| // Put this into a block to avoid previousAwaitIsKeyword being not initialized after jump to LIdentifier |
| { |
| bool previousAwaitIsKeyword = this->GetScanner()->SetAwaitIsKeywordRegion(isAsyncExpr); |
| this->GetScanner()->Scan(); |
| this->GetScanner()->SetAwaitIsKeywordRegion(previousAwaitIsKeyword); |
| } |
| |
| // We search for an Async expression (a function declaration or an async lambda expression) |
| if (isAsyncExpr && !this->GetScanner()->FHadNewLine()) |
| { |
| if (m_token.tk == tkFUNCTION) |
| { |
| goto LFunction; |
| } |
| else if (m_token.tk == tkID || m_token.tk == tkAWAIT) |
| { |
| isLambdaExpr = true; |
| goto LFunction; |
| } |
| else if (m_token.tk == tkLParen) |
| { |
| // This is potentially an async arrow function. Save the state of the async references |
| // in case it needs to be restored. (Note that the case of a single parameter with no ()'s |
| // is detected upstream and need not be handled here.) |
| savedTopAsyncRef = pid->GetTopRef(); |
| } |
| } |
| |
| CheckArgumentsUse(pid, GetCurrentFunctionNode()); |
| |
| // Assume this pid is not special - overwrite when we parse a special name |
| isSpecialName = false; |
| |
| LIdentifier: |
| PidRefStack * ref = nullptr; |
| |
| // Don't push a reference if this is a single lambda parameter, because we'll reparse with |
| // a correct function ID. |
| if (m_token.tk != tkDArrow) |
| { |
| ref = this->PushPidRef(pid); |
| } |
| |
| if (buildAST) |
| { |
| if (isSpecialName) |
| { |
| pnode = CreateSpecialNameNode(pid, ref, ichMin, ichLim); |
| } |
| else |
| { |
| pnode = CreateNameNode(pid, ref, ichMin, ichLim); |
| } |
| } |
| else |
| { |
| // Remember the identifier start and end in case it turns out to be a statement label. |
| term.tk = tkID; |
| term.pid = pid; // Record the identifier for detection of eval |
| term.ichMin = static_cast<charcount_t>(iecpMin); |
| term.ichLim = static_cast<charcount_t>(iecpLim); |
| } |
| break; |
| } |
| |
| case tkSUPER: |
| ichMin = this->GetScanner()->IchMinTok(); |
| iecpMin = this->GetScanner()->IecpMinTok(); |
| ichLim = this->GetScanner()->IchLimTok(); |
| iecpLim = this->GetScanner()->IecpLimTok(); |
| |
| if (!m_scriptContext->GetConfig()->IsES6ClassAndExtendsEnabled()) |
| { |
| goto LUnknown; |
| } |
| |
| this->GetScanner()->Scan(); |
| |
| pid = ParseSuper<buildAST>(!!fAllowCall); |
| isSpecialName = true; |
| |
| // Super reference and super call need to push a pid ref to 'this' even when not building an AST |
| ReferenceSpecialName(wellKnownPropertyPids._this, ichMin, ichLim); |
| // Super call needs to reference 'new.target' |
| if (pid == wellKnownPropertyPids._superConstructor) |
| { |
| // super() will write to "this", so track the assignment. |
| PidRefStack *thisRef = wellKnownPropertyPids._this->GetTopRef(); |
| thisRef->isAsg = true; |
| ReferenceSpecialName(wellKnownPropertyPids._newTarget, ichMin, ichLim); |
| } |
| |
| goto LIdentifier; |
| |
| case tkTHIS: |
| ichMin = this->GetScanner()->IchMinTok(); |
| iecpMin = this->GetScanner()->IecpMinTok(); |
| ichLim = this->GetScanner()->IchLimTok(); |
| iecpLim = this->GetScanner()->IecpLimTok(); |
| |
| pid = wellKnownPropertyPids._this; |
| |
| this->GetScanner()->Scan(); |
| isSpecialName = true; |
| |
| goto LIdentifier; |
| |
| case tkLParen: |
| { |
| ichMin = this->GetScanner()->IchMinTok(); |
| iuMin = this->GetScanner()->IecpMinTok(); |
| |
| // If we are undeferring a function which has deferred stubs, we can check to see if the next deferred stub |
| // is a lambda at the current character. If it is, we know this LParen is the beginning of a lambda nested |
| // function and we can avoid parsing the next series of tokens as a parenthetical expression and reparsing |
| // after finding the => token. |
| if (buildAST && m_currDeferredStub != nullptr && GetCurrentFunctionNode() != nullptr && GetCurrentFunctionNode()->nestedCount < m_currDeferredStubCount) |
| { |
| DeferredFunctionStub* stub = m_currDeferredStub + GetCurrentFunctionNode()->nestedCount; |
| if (stub->ichMin == ichMin) |
| { |
| Assert((stub->fncFlags & kFunctionIsLambda) == kFunctionIsLambda); |
| |
| pnode = ParseFncDeclCheckScope<true>(fFncLambda); |
| break; |
| } |
| } |
| |
| this->GetScanner()->Scan(); |
| if (m_token.tk == tkRParen) |
| { |
| // Empty parens can only be legal as an empty parameter list to a lambda declaration. |
| // We're in a lambda if the next token is =>. |
| fAllowCall = FALSE; |
| this->GetScanner()->Scan(); |
| |
| // If the token after the right paren is not => or if there was a newline between () and => this is a syntax error |
| if (!IsDoingFastScan() && (m_token.tk != tkDArrow || this->GetScanner()->FHadNewLine())) |
| { |
| Error(ERRValidIfFollowedBy, _u("Lambda parameter list"), _u("'=>' on the same line")); |
| } |
| |
| if (buildAST) |
| { |
| pnode = CreateNodeForOpT<knopEmpty>(); |
| } |
| break; |
| } |
| |
| // Advance the block ID here in case this parenthetical expression turns out to be a lambda parameter list. |
| // That way the pid ref stacks will be created in their correct final form, and we can simply fix |
| // up function ID's. |
| uint saveNextBlockId = m_nextBlockId; |
| uint saveCurrBlockId = GetCurrentBlock()->blockId; |
| GetCurrentBlock()->blockId = m_nextBlockId++; |
| |
| AutoDeferErrorsRestore deferErrorRestore(this); |
| |
| this->m_funcParenExprDepth++; |
| pnode = ParseExpr<buildAST>(koplNo, &fCanAssign, TRUE, FALSE, nullptr, nullptr /*nameLength*/, nullptr /*pShortNameOffset*/, &term, true, nullptr, plastRParen); |
| this->m_funcParenExprDepth--; |
| |
| if (buildAST && plastRParen) |
| { |
| *plastRParen = this->GetScanner()->IchLimTok(); |
| } |
| |
| ChkCurTok(tkRParen, ERRnoRparen); |
| |
| GetCurrentBlock()->blockId = saveCurrBlockId; |
| if (m_token.tk == tkDArrow) |
| { |
| // We're going to rewind and reinterpret the expression as a parameter list. |
| // Put back the original next-block-ID so the existing pid ref stacks will be correct. |
| m_nextBlockId = saveNextBlockId; |
| } |
| else |
| { |
| // Emit a deferred ... error if one was parsed. |
| if (m_deferEllipsisError) |
| { |
| this->GetScanner()->SeekTo(m_deferEllipsisErrorLoc); |
| Error(ERRInvalidSpreadUse); |
| } |
| else if (m_deferCommaError) |
| { |
| // Emit a comma error if that was deferred. |
| this->GetScanner()->SeekTo(m_deferCommaErrorLoc); |
| Error(ERRsyntax); |
| } |
| } |
| |
| break; |
| } |
| |
| case tkIntCon: |
| if (IsStrictMode() && this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) |
| { |
| Error(ERRES5NoOctal); |
| } |
| |
| if (buildAST) |
| { |
| pnode = CreateIntNode(m_token.GetLong()); |
| } |
| fCanAssign = FALSE; |
| this->GetScanner()->Scan(); |
| break; |
| |
| case tkBigIntCon: |
| if (IsStrictMode() && this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) |
| { |
| Error(ERRES5NoOctal); |
| } |
| |
| if (buildAST) |
| { |
| pnode = CreateBigIntNode(m_token.GetBigInt()); |
| } |
| fCanAssign = FALSE; |
| this->GetScanner()->Scan(); |
| break; |
| |
| case tkFltCon: |
| if (IsStrictMode() && this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) |
| { |
| Error(ERRES5NoOctal); |
| } |
| |
| if (buildAST) |
| { |
| ParseNodeFloat * pnodeFloat; |
| pnode = pnodeFloat = CreateNodeForOpT<knopFlt>(); |
| pnodeFloat->dbl = m_token.GetDouble(); |
| pnodeFloat->maybeInt = m_token.GetDoubleMayBeInt(); |
| } |
| fCanAssign = FALSE; |
| this->GetScanner()->Scan(); |
| break; |
| |
| case tkStrCon: |
| if (IsStrictMode() && this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) |
| { |
| Error(ERRES5NoOctal); |
| } |
| |
| if (buildAST) |
| { |
| pnode = CreateStrNode(m_token.GetStr()); |
| } |
| else |
| { |
| // Subtract the string literal length from the total char count for the purpose |
| // of deciding whether to defer parsing and byte code generation. |
| this->ReduceDeferredScriptLength(this->GetScanner()->IchLimTok() - this->GetScanner()->IchMinTok()); |
| } |
| fCanAssign = FALSE; |
| this->GetScanner()->Scan(); |
| break; |
| |
| case tkTRUE: |
| if (buildAST) |
| { |
| pnode = CreateNodeForOpT<knopTrue>(); |
| } |
| fCanAssign = FALSE; |
| this->GetScanner()->Scan(); |
| break; |
| |
| case tkFALSE: |
| if (buildAST) |
| { |
| pnode = CreateNodeForOpT<knopFalse>(); |
| } |
| fCanAssign = FALSE; |
| this->GetScanner()->Scan(); |
| break; |
| |
| case tkNULL: |
| if (buildAST) |
| { |
| pnode = CreateNodeForOpT<knopNull>(); |
| } |
| fCanAssign = FALSE; |
| this->GetScanner()->Scan(); |
| break; |
| |
| case tkDiv: |
| case tkAsgDiv: |
| pnode = ParseRegExp<buildAST>(); |
| fCanAssign = FALSE; |
| this->GetScanner()->Scan(); |
| break; |
| |
| case tkNEW: |
| { |
| ichMin = this->GetScanner()->IchMinTok(); |
| iecpMin = this->GetScanner()->IecpMinTok(); |
| this->GetScanner()->Scan(); |
| |
| if (m_token.tk == tkDot && m_scriptContext->GetConfig()->IsES6ClassAndExtendsEnabled()) |
| { |
| pid = ParseMetaProperty<buildAST>(tkNEW, ichMin, &fCanAssign); |
| |
| ichLim = this->GetScanner()->IchLimTok(); |
| iecpLim = this->GetScanner()->IecpLimTok(); |
| |
| this->GetScanner()->Scan(); |
| isSpecialName = true; |
| |
| goto LIdentifier; |
| } |
| else |
| { |
| ParseNodePtr pnodeExpr = ParseTerm<buildAST>(FALSE, pNameHint, pHintLength, pShortNameOffset, nullptr, false, TRUE, nullptr, nullptr, nullptr, plastRParen); |
| if (buildAST) |
| { |
| pnode = CreateCallNode(knopNew, pnodeExpr, nullptr); |
| pnode->ichMin = ichMin; |
| } |
| fInNew = TRUE; |
| fCanAssign = FALSE; |
| } |
| break; |
| } |
| |
| case tkLBrack: |
| { |
| ichMin = this->GetScanner()->IchMinTok(); |
| this->GetScanner()->Scan(); |
| pnode = ParseArrayLiteral<buildAST>(); |
| if (buildAST) |
| { |
| pnode->ichMin = ichMin; |
| pnode->ichLim = this->GetScanner()->IchLimTok(); |
| } |
| |
| if (this->m_arrayDepth == 0) |
| { |
| Assert(this->GetScanner()->IchLimTok() - ichMin > m_funcInArray); |
| this->ReduceDeferredScriptLength(this->GetScanner()->IchLimTok() - ichMin - this->m_funcInArray); |
| this->m_funcInArray = 0; |
| this->m_funcInArrayDepth = 0; |
| } |
| ChkCurTok(tkRBrack, ERRnoRbrack); |
| if (!IsES6DestructuringEnabled()) |
| { |
| fCanAssign = FALSE; |
| } |
| else if (pfLikelyPattern != nullptr && !IsPostFixOperators()) |
| { |
| *pfLikelyPattern = TRUE; |
| } |
| break; |
| } |
| |
| case tkLCurly: |
| { |
| ichMin = this->GetScanner()->IchMinTok(); |
| this->GetScanner()->ScanForcingPid(); |
| ParseNodePtr pnodeMemberList = ParseMemberList<buildAST>(pNameHint, pHintLength); |
| if (buildAST) |
| { |
| pnode = CreateUniNode(knopObject, pnodeMemberList); |
| pnode->ichMin = ichMin; |
| pnode->ichLim = this->GetScanner()->IchLimTok(); |
| } |
| ChkCurTok(tkRCurly, ERRnoRcurly); |
| if (!IsES6DestructuringEnabled()) |
| { |
| fCanAssign = FALSE; |
| } |
| else if (pfLikelyPattern != nullptr && !IsPostFixOperators()) |
| { |
| *pfLikelyPattern = TRUE; |
| } |
| break; |
| } |
| |
| case tkFUNCTION: |
| { |
| LFunction: |
| if (m_grfscr & fscrDeferredFncExpression) |
| { |
| // The top-level deferred function body was defined by a function expression whose parsing was deferred. We are now |
| // parsing it, so unset the flag so that any nested functions are parsed normally. This flag is only applicable the |
| // first time we see it. |
| // |
| // Normally, deferred functions will be parsed in ParseStatement upon encountering the 'function' token. The first |
| // token of the source code of the function may not a 'function' token though, so we still need to reset this flag |
| // for the first function we parse. This can happen in compat modes, for instance, for a function expression enclosed |
| // in parentheses, where the legacy behavior was to include the parentheses in the function's source code. |
| m_grfscr &= ~fscrDeferredFncExpression; |
| } |
| ushort flags = fFncNoFlgs; |
| if (isLambdaExpr) |
| { |
| flags |= fFncLambda; |
| } |
| if (isAsyncExpr) |
| { |
| flags |= fFncAsync; |
| } |
| pnode = ParseFncDeclNoCheckScope<buildAST>(flags, SuperRestrictionState::Disallowed, pNameHint, /* needsPIDOnRCurlyScan */ false, fUnaryOrParen); |
| if (isAsyncExpr) |
| { |
| pnode->AsParseNodeFnc()->cbStringMin = iecpMin; |
| } |
| fCanAssign = FALSE; |
| break; |
| } |
| |
| case tkCLASS: |
| if (m_scriptContext->GetConfig()->IsES6ClassAndExtendsEnabled()) |
| { |
| pnode = ParseClassDecl<buildAST>(FALSE, pNameHint, pHintLength, pShortNameOffset); |
| } |
| else |
| { |
| goto LUnknown; |
| } |
| fCanAssign = FALSE; |
| break; |
| |
| case tkStrTmplBasic: |
| case tkStrTmplBegin: |
| pnode = ParseStringTemplateDecl<buildAST>(nullptr); |
| fCanAssign = FALSE; |
| break; |
| |
| case tkIMPORT: |
| if (m_scriptContext->GetConfig()->IsES6ModuleEnabled() && m_scriptContext->GetConfig()->IsESDynamicImportEnabled()) |
| { |
| if (!fAllowCall) |
| { |
| Error(ERRTokenAfter, _u("import"), _u("new")); |
| } |
| this->GetScanner()->Scan(); |
| ChkCurTokNoScan(tkLParen, ERRnoLparen); |
| pnode = ParseImportCall<buildAST>(); |
| } |
| else |
| { |
| goto LUnknown; |
| } |
| break; |
| |
| #if ENABLE_BACKGROUND_PARSING |
| case tkCASE: |
| { |
| if (!m_doingFastScan) |
| { |
| goto LUnknown; |
| } |
| ParseNodePtr pnodeUnused; |
| pnode = ParseCase<buildAST>(&pnodeUnused); |
| break; |
| } |
| |
| case tkELSE: |
| if (!m_doingFastScan) |
| { |
| goto LUnknown; |
| } |
| this->GetScanner()->Scan(); |
| ParseStatement<buildAST>(); |
| break; |
| #endif |
| |
| default: |
| LUnknown: |
| if (m_token.tk == tkNone) |
| { |
| Error(ERRInvalidIdentifier, m_token.GetIdentifier(this->GetHashTbl())->Psz(), GetTokenString(GetScanner()->GetPrevious())); |
| } |
| else if (m_token.IsKeyword()) |
| { |
| Error(ERRKeywordAfter, GetTokenString(m_token.tk), GetTokenString(GetScanner()->GetPrevious())); |
| } |
| else |
| { |
| Error(ERRTokenAfter, GetTokenString(m_token.tk), GetTokenString(GetScanner()->GetPrevious())); |
| } |
| break; |
| } |
| |
| pnode = ParsePostfixOperators<buildAST>(pnode, fAllowCall, fInNew, isAsyncExpr, fCanAssignToCall, &fCanAssign, &term, pfIsDotOrIndex); |
| |
| if (savedTopAsyncRef != nullptr && |
| this->m_token.tk == tkDArrow) |
| { |
| // This is an async arrow function; we're going to back up and reparse it. |
| // Make sure we don't leave behind a bogus reference to the 'async' identifier. |
| for (pid = wellKnownPropertyPids.async; pid->GetTopRef() != savedTopAsyncRef;) |
| { |
| Assert(pid->GetTopRef() != nullptr); |
| pid->RemovePrevPidRef(nullptr); |
| } |
| } |
| |
| // Pass back identifier if requested |
| if (pToken && term.tk == tkID) |
| { |
| *pToken = term; |
| } |
| |
| if (pfCanAssign) |
| { |
| *pfCanAssign = fCanAssign; |
| } |
| |
| return pnode; |
| } |
| |
| template <bool buildAST> |
| ParseNodeRegExp * Parser::ParseRegExp() |
| { |
| ParseNodeRegExp * pnode = nullptr; |
| |
| if (buildAST || IsDoingFastScan()) |
| { |
| this->GetScanner()->RescanRegExp(); |
| |
| #if ENABLE_BACKGROUND_PARSING |
| BOOL saveDeferringAST = this->m_deferringAST; |
| if (m_doingFastScan) |
| { |
| this->m_deferringAST = false; |
| } |
| #endif |
| pnode = CreateNodeForOpT<knopRegExp>(); |
| pnode->regexPattern = m_token.GetRegex(); |
| #if ENABLE_BACKGROUND_PARSING |
| if (m_doingFastScan) |
| { |
| this->m_deferringAST = saveDeferringAST; |
| this->AddFastScannedRegExpNode(pnode); |
| if (!buildAST) |
| { |
| pnode = nullptr; |
| } |
| } |
| else if (this->IsBackgroundParser()) |
| { |
| Assert(pnode->regexPattern == nullptr); |
| this->AddBackgroundRegExpNode(pnode); |
| } |
| #endif |
| } |
| else |
| { |
| this->GetScanner()->RescanRegExpNoAST(); |
| } |
| Assert(m_token.tk == tkRegExp); |
| |
| return pnode; |
| } |
| |
| BOOL Parser::NodeIsEvalName(ParseNodePtr pnode) |
| { |
| //WOOB 1107758 Special case of indirect eval binds to local scope in standards mode |
| return pnode->nop == knopName && (pnode->AsParseNodeName()->pid == wellKnownPropertyPids.eval); |
| } |
| |
| BOOL Parser::NodeIsSuperName(ParseNodePtr pnode) |
| { |
| return pnode->nop == knopName && (pnode->AsParseNodeName()->pid == wellKnownPropertyPids._superConstructor); |
| } |
| |
| BOOL Parser::NodeEqualsName(ParseNodePtr pnode, LPCOLESTR sz, uint32 cch) |
| { |
| return pnode->nop == knopName && |
| pnode->AsParseNodeName()->pid->Cch() == cch && |
| !wmemcmp(pnode->AsParseNodeName()->pid->Psz(), sz, cch); |
| } |
| |
| BOOL Parser::NodeIsIdent(ParseNodePtr pnode, IdentPtr pid) |
| { |
| for (;;) |
| { |
| switch (pnode->nop) |
| { |
| case knopName: |
| return (pnode->AsParseNodeName()->pid == pid); |
| |
| case knopComma: |
| pnode = pnode->AsParseNodeBin()->pnode2; |
| break; |
| |
| default: |
| return FALSE; |
| } |
| } |
| } |
| |
| template<bool buildAST> |
| ParseNodePtr Parser::ParsePostfixOperators( |
| ParseNodePtr pnode, |
| BOOL fAllowCall, |
| BOOL fInNew, |
| BOOL isAsyncExpr, |
| BOOL fCanAssignToCallResult, |
| BOOL *pfCanAssign, |
| _Inout_ IdentToken* pToken, |
| _Out_opt_ bool* pfIsDotOrIndex /*= nullptr */) |
| { |
| uint16 count = 0; |
| bool callOfConstants = false; |
| if (pfIsDotOrIndex) |
| { |
| *pfIsDotOrIndex = false; |
| } |
| |
| for (;;) |
| { |
| uint16 spreadArgCount = 0; |
| switch (m_token.tk) |
| { |
| case tkLParen: |
| { |
| AutoMarkInParsingArgs autoMarkInParsingArgs(this); |
| |
| if (fInNew) |
| { |
| ParseNodePtr pnodeArgs = ParseArgList<buildAST>(&callOfConstants, &spreadArgCount, &count); |
| if (buildAST) |
| { |
| Assert(pnode->nop == knopNew); |
| Assert(pnode->AsParseNodeCall()->pnodeArgs == nullptr); |
| pnode->AsParseNodeCall()->pnodeArgs = pnodeArgs; |
| pnode->AsParseNodeCall()->callOfConstants = callOfConstants; |
| pnode->AsParseNodeCall()->isApplyCall = false; |
| pnode->AsParseNodeCall()->isEvalCall = false; |
| pnode->AsParseNodeCall()->isSuperCall = false; |
| pnode->AsParseNodeCall()->hasDestructuring = m_hasDestructuringPattern; |
| Assert(!m_hasDestructuringPattern || count > 0); |
| pnode->AsParseNodeCall()->argCount = count; |
| pnode->AsParseNodeCall()->spreadArgCount = spreadArgCount; |
| pnode->ichLim = this->GetScanner()->IchLimTok(); |
| } |
| else |
| { |
| pnode = nullptr; |
| pToken->tk = tkNone; // This is no longer an identifier |
| } |
| fInNew = FALSE; |
| ChkCurTok(tkRParen, ERRnoRparen); |
| } |
| else |
| { |
| if (!fAllowCall) |
| { |
| return pnode; |
| } |
| |
| uint saveNextBlockId = m_nextBlockId; |
| uint saveCurrBlockId = GetCurrentBlock()->blockId; |
| |
| if (isAsyncExpr) |
| { |
| // Advance the block ID here in case this parenthetical expression turns out to be a lambda parameter list. |
| // That way the pid ref stacks will be created in their correct final form, and we can simply fix |
| // up function ID's. |
| GetCurrentBlock()->blockId = m_nextBlockId++; |
| } |
| |
| ParseNodePtr pnodeArgs = ParseArgList<buildAST>(&callOfConstants, &spreadArgCount, &count); |
| // We used to un-defer a deferred function body here if it was called as part of the expression that declared it. |
| // We now detect this case up front in ParseFncDecl, which is cheaper and simpler. |
| if (buildAST) |
| { |
| bool fCallIsEval = false; |
| |
| // Detect super() |
| if (this->NodeIsSuperName(pnode)) |
| { |
| pnode = CreateSuperCallNode(pnode->AsParseNodeSpecialName(), pnodeArgs); |
| Assert(pnode); |
| |
| pnode->AsParseNodeSuperCall()->pnodeThis = ReferenceSpecialName(wellKnownPropertyPids._this, pnode->ichMin, this->GetScanner()->IchLimTok(), true); |
| pnode->AsParseNodeSuperCall()->pnodeNewTarget = ReferenceSpecialName(wellKnownPropertyPids._newTarget, pnode->ichMin, this->GetScanner()->IchLimTok(), true); |
| } |
| else |
| { |
| pnode = CreateCallNode(knopCall, pnode, pnodeArgs); |
| Assert(pnode); |
| } |
| |
| // Detect call to "eval" and record it on the function. |
| // Note: we used to leave it up to the byte code generator to detect eval calls |
| // at global scope, but now it relies on the flag the parser sets, so set it here. |
| |
| if (count > 0 && this->NodeIsEvalName(pnode->AsParseNodeCall()->pnodeTarget)) |
| { |
| this->MarkEvalCaller(); |
| fCallIsEval = true; |
| |
| // Eval may reference any of the special symbols so we need to push refs to them here. |
| ReferenceSpecialName(wellKnownPropertyPids._this); |
| ReferenceSpecialName(wellKnownPropertyPids._newTarget); |
| ReferenceSpecialName(wellKnownPropertyPids._super); |
| ReferenceSpecialName(wellKnownPropertyPids._superConstructor); |
| ReferenceSpecialName(wellKnownPropertyPids.arguments); |
| } |
| |
| pnode->AsParseNodeCall()->callOfConstants = callOfConstants; |
| pnode->AsParseNodeCall()->spreadArgCount = spreadArgCount; |
| pnode->AsParseNodeCall()->isApplyCall = false; |
| pnode->AsParseNodeCall()->isEvalCall = fCallIsEval; |
| pnode->AsParseNodeCall()->hasDestructuring = m_hasDestructuringPattern; |
| Assert(!m_hasDestructuringPattern || count > 0); |
| pnode->AsParseNodeCall()->argCount = count; |
| pnode->ichLim = this->GetScanner()->IchLimTok(); |
| } |
| else |
| { |
| pnode = nullptr; |
| if (pToken->tk == tkID && pToken->pid == wellKnownPropertyPids.eval && count > 0) // Detect eval |
| { |
| this->MarkEvalCaller(); |
| |
| ReferenceSpecialName(wellKnownPropertyPids._this); |
| ReferenceSpecialName(wellKnownPropertyPids._newTarget); |
| ReferenceSpecialName(wellKnownPropertyPids._super); |
| ReferenceSpecialName(wellKnownPropertyPids._superConstructor); |
| ReferenceSpecialName(wellKnownPropertyPids.arguments); |
| } |
| pToken->tk = tkNone; // This is no longer an identifier |
| } |
| |
| ChkCurTok(tkRParen, ERRnoRparen); |
| |
| if (isAsyncExpr) |
| { |
| GetCurrentBlock()->blockId = saveCurrBlockId; |
| if (m_token.tk == tkDArrow) |
| { |
| // We're going to rewind and reinterpret the expression as a parameter list. |
| // Put back the original next-block-ID so the existing pid ref stacks will be correct. |
| m_nextBlockId = saveNextBlockId; |
| } |
| } |
| } |
| if (pfCanAssign) |
| { |
| *pfCanAssign = fCanAssignToCallResult && |
| (m_sourceContextInfo ? |
| !PHASE_ON_RAW(Js::EarlyErrorOnAssignToCallPhase, m_sourceContextInfo->sourceContextId, GetCurrentFunctionNode()->functionId) : |
| !PHASE_ON1(Js::EarlyErrorOnAssignToCallPhase)); |
| } |
| if (pfIsDotOrIndex) |
| { |
| *pfIsDotOrIndex = false; |
| } |
| break; |
| } |
| case tkLBrack: |
| { |
| this->GetScanner()->Scan(); |
| IdentToken tok; |
| ParseNodePtr pnodeExpr = ParseExpr<buildAST>(0, FALSE, TRUE, FALSE, nullptr, nullptr, nullptr, &tok); |
| if (buildAST) |
| { |
| AnalysisAssert(pnodeExpr); |
| if (pnode && pnode->nop == knopName && pnode->AsParseNodeName()->IsSpecialName() && pnode->AsParseNodeSpecialName()->isSuper) |
| { |
| pnode = CreateSuperReferenceNode(knopIndex, pnode->AsParseNodeSpecialName(), pnodeExpr); |
| pnode->AsParseNodeSuperReference()->pnodeThis = ReferenceSpecialName(wellKnownPropertyPids._this, pnode->ichMin, pnode->ichLim, true); |
| } |
| else |
| { |
| pnode = CreateBinNode(knopIndex, pnode, pnodeExpr); |
| } |
| |
| AnalysisAssert(pnode); |
| pnode->ichLim = this->GetScanner()->IchLimTok(); |
| } |
| else |
| { |
| pnode = nullptr; |
| pToken->tk = tkNone; // This is no longer an identifier |
| } |
| ChkCurTok(tkRBrack, ERRnoRbrack); |
| if (pfCanAssign) |
| { |
| *pfCanAssign = TRUE; |
| } |
| if (pfIsDotOrIndex) |
| { |
| *pfIsDotOrIndex = true; |
| } |
| |
| PidRefStack * topPidRef = nullptr; |
| if (buildAST) |
| { |
| if (pnodeExpr && pnodeExpr->nop == knopName) |
| { |
| topPidRef = pnodeExpr->AsParseNodeName()->pid->GetTopRef(); |
| } |
| } |
| else if (tok.tk == tkID) |
| { |
| topPidRef = tok.pid->GetTopRef(); |
| } |
| if (topPidRef) |
| { |
| topPidRef->SetIsUsedInLdElem(true); |
| } |
| |
| if (!buildAST) |
| { |
| break; |
| } |
| |
| bool shouldConvertToDot = false; |
| if (pnode->AsParseNodeBin()->pnode2->nop == knopStr) |
| { |
| // if the string is empty or contains escape character, we will not convert them to dot node |
| shouldConvertToDot = pnode->AsParseNodeBin()->pnode2->AsParseNodeStr()->pid->Cch() > 0 && !this->GetScanner()->IsEscapeOnLastTkStrCon(); |
| } |
| |
| if (shouldConvertToDot) |
| { |
| LPCOLESTR str = pnode->AsParseNodeBin()->pnode2->AsParseNodeStr()->pid->Psz(); |
| // See if we can convert o["p"] into o.p and o["0"] into o[0] since they're equivalent and the latter forms |
| // are faster |
| uint32 uintValue; |
| if (Js::JavascriptOperators::TryConvertToUInt32( |
| str, |
| pnode->AsParseNodeBin()->pnode2->AsParseNodeStr()->pid->Cch(), |
| &uintValue) && |
| !Js::TaggedInt::IsOverflow(uintValue)) // the optimization is not very useful if the number can't be represented as a TaggedInt |
| { |
| // No need to verify that uintValue != JavascriptArray::InvalidIndex since all nonnegative TaggedInts are valid indexes |
| auto intNode = CreateIntNode(uintValue); // implicit conversion from uint32 to int32 |
| pnode->AsParseNodeBin()->pnode2 = intNode; |
| } |
| // Field optimization (see GlobOpt::KillLiveElems) checks for value being a Number, |
| // and since NaN/Infinity is a number it won't kill o.NaN/o.Infinity which would cause a problem |
| // if we decide to hoist o.NaN/o.Infinity. |
| // We need to keep o["NaN"] and o["+/-Infinity"] as array element access (we don't hoist that but we may hoist field access), |
| // so no matter if it's killed by o[x] inside a loop, we make sure that we never hoist these. |
| // We need to follow same logic for strings that convert to a floating point number. |
| else |
| { |
| bool doConvertToProperty = false; // Convert a["x"] -> a.x. |
| if (!Parser::IsNaNOrInfinityLiteral<true>(str)) |
| { |
| const OLECHAR* terminalChar; |
| double dbl = Js::NumberUtilities::StrToDbl(str, &terminalChar, m_scriptContext); |
| bool convertsToFloat = !Js::NumberUtilities::IsNan(dbl); |
| doConvertToProperty = !convertsToFloat; |
| } |
| |
| if (doConvertToProperty) |
| { |
| ParseNodeName * pnodeNewExpr = CreateNameNode(pnodeExpr->AsParseNodeStr()->pid); |
| pnodeNewExpr->ichMin = pnodeExpr->ichMin; |
| pnodeNewExpr->ichLim = pnodeExpr->ichLim; |
| pnode->AsParseNodeBin()->pnode2 = pnodeNewExpr; |
| pnode->nop = knopDot; |
| pnode->grfpn |= PNodeFlags::fpnIndexOperator; |
| } |
| } |
| } |
| } |
| break; |
| |
| case tkDot: |
| { |
| ParseNodePtr name = nullptr; |
| OpCode opCode = knopDot; |
| |
| this->GetScanner()->Scan(); |
| if (!m_token.IsIdentifier()) |
| { |
| //allow reserved words in ES5 mode |
| if (!(m_token.IsReservedWord())) |
| { |
| IdentifierExpectedError(m_token); |
| } |
| } |
| // Note: see comment above about field optimization WRT NaN/Infinity/-Infinity. |
| // Convert a.Nan, a.Infinity into a["NaN"], a["Infinity"]. |
| // We don't care about -Infinity case here because x.-Infinity is invalid in JavaScript. |
| // Both NaN and Infinity are identifiers. |
| else if (buildAST && Parser::IsNaNOrInfinityLiteral<false>(m_token.GetIdentifier(this->GetHashTbl())->Psz())) |
| { |
| opCode = knopIndex; |
| } |
| |
| if (buildAST) |
| { |
| if (opCode == knopDot) |
| { |
| name = CreateNameNode(m_token.GetIdentifier(this->GetHashTbl())); |
| } |
| else |
| { |
| Assert(opCode == knopIndex); |
| name = CreateStrNode(m_token.GetIdentifier(this->GetHashTbl())); |
| } |
| if (pnode && pnode->nop == knopName && pnode->AsParseNodeName()->IsSpecialName() && pnode->AsParseNodeSpecialName()->isSuper) |
| { |
| pnode = CreateSuperReferenceNode(opCode, pnode->AsParseNodeSpecialName(), name); |
| pnode->AsParseNodeSuperReference()->pnodeThis = ReferenceSpecialName(wellKnownPropertyPids._this, pnode->ichMin, pnode->ichLim, true); |
| } |
| else |
| { |
| pnode = CreateBinNode(opCode, pnode, name); |
| } |
| } |
| else |
| { |
| pnode = nullptr; |
| pToken->tk = tkNone; |
| } |
| |
| if (pfCanAssign) |
| { |
| *pfCanAssign = TRUE; |
| } |
| if (pfIsDotOrIndex) |
| { |
| *pfIsDotOrIndex = true; |
| } |
| this->GetScanner()->Scan(); |
| |
| break; |
| } |
| |
| case tkStrTmplBasic: |
| case tkStrTmplBegin: |
| { |
| ParseNode* templateNode = nullptr; |
| if (pnode != nullptr) |
| { |
| AutoMarkInParsingArgs autoMarkInParsingArgs(this); |
| templateNode = ParseStringTemplateDecl<buildAST>(pnode); |
| } |
| else |
| { |
| templateNode = ParseStringTemplateDecl<buildAST>(pnode); |
| } |
| |
| if (!buildAST) |
| { |
| pToken->tk = tkNone; // This is no longer an identifier |
| } |
| |
| pnode = templateNode; |
| if (pfCanAssign) |
| { |
| *pfCanAssign = FALSE; |
| } |
| if (pfIsDotOrIndex) |
| { |
| *pfIsDotOrIndex = false; |
| } |
| break; |
| } |
| default: |
| return pnode; |
| } |
| } |
| } |
| |
| /*************************************************************************** |
| Look for an existing label with the given name. |
| ***************************************************************************/ |
| bool Parser::LabelExists(IdentPtr pid, LabelId* pLabelIdList) |
| { |
| StmtNest dummy; |
| dummy.pLabelId = pLabelIdList; |
| dummy.pstmtOuter = m_pstmtCur; |
| |
| // Look through each label list for the current stack of statements |
| for (StmtNest* pStmt = &dummy; pStmt != nullptr; pStmt = pStmt->pstmtOuter) |
| { |
| for (LabelId* pLabelId = pStmt->pLabelId; pLabelId != nullptr; pLabelId = pLabelId->next) |
| { |
| if (pLabelId->pid == pid) |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| // Currently only ints and floats are treated as constants in function call |
| // TODO: Check if we need for other constants as well |
| BOOL Parser::IsConstantInFunctionCall(ParseNodePtr pnode) |
| { |
| if (pnode->nop == knopInt && !Js::TaggedInt::IsOverflow(pnode->AsParseNodeInt()->lw)) |
| { |
| return TRUE; |
| } |
| |
| if (pnode->nop == knopFlt) |
| { |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| /*************************************************************************** |
| Parse a list of arguments. |
| ***************************************************************************/ |
| template<bool buildAST> |
| ParseNodePtr Parser::ParseArgList(bool *pCallOfConstants, uint16 *pSpreadArgCount, uint16 * pCount) |
| { |
| ParseNodePtr pnodeArg; |
| ParseNodePtr pnodeList = nullptr; |
| ParseNodePtr *lastNodeRef = nullptr; |
| |
| // Check for an empty list |
| Assert(m_token.tk == tkLParen); |
| |
| if (this->GetScanner()->Scan() == tkRParen) |
| { |
| return nullptr; |
| } |
| |
| *pCallOfConstants = true; |
| *pSpreadArgCount = 0; |
| |
| int count = 0; |
| while (true) |
| { |
| if (count >= Js::Constants::MaxAllowedArgs) |
| { |
| Error(ERRTooManyArgs); |
| } |
| // Allow spread in argument lists. |
| IdentToken token; |
| pnodeArg = ParseExpr<buildAST>(koplCma, nullptr, TRUE, /* fAllowEllipsis */TRUE, NULL, nullptr, nullptr, &token); |
| ++count; |
| this->MarkEscapingRef(pnodeArg, &token); |
| |
| if (buildAST) |
| { |
| this->CheckArguments(pnodeArg); |
| |
| if (*pCallOfConstants && !IsConstantInFunctionCall(pnodeArg)) |
| { |
| *pCallOfConstants = false; |
| } |
| |
| if (pnodeArg->nop == knopEllipsis) |
| { |
| (*pSpreadArgCount)++; |
| } |
| |
| AddToNodeListEscapedUse(&pnodeList, &lastNodeRef, pnodeArg); |
| } |
| if (m_token.tk != tkComma) |
| { |
| break; |
| } |
| this->GetScanner()->Scan(); |
| |
| if (m_token.tk == tkRParen && m_scriptContext->GetConfig()->IsES7TrailingCommaEnabled()) |
| { |
| break; |
| } |
| } |
| |
| if (pSpreadArgCount != nullptr && (*pSpreadArgCount) > 0) { |
| CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, SpreadFeature, m_scriptContext); |
| } |
| |
| *pCount = static_cast<uint16>(count); |
| if (buildAST) |
| { |
| Assert(lastNodeRef); |
| Assert(*lastNodeRef); |
| pnodeList->ichLim = (*lastNodeRef)->ichLim; |
| } |
| |
| return pnodeList; |
| } |
| |
| // Currently only ints are treated as constants in ArrayLiterals |
| BOOL Parser::IsConstantInArrayLiteral(ParseNodePtr pnode) |
| { |
| if (pnode->nop == knopInt && !Js::TaggedInt::IsOverflow(pnode->AsParseNodeInt()->lw)) |
| { |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| template<bool buildAST> |
| ParseNodeArrLit * Parser::ParseArrayLiteral() |
| { |
| ParseNodeArrLit * pnode = nullptr; |
| bool arrayOfTaggedInts = false; |
| bool arrayOfInts = false; |
| bool arrayOfNumbers = false; |
| bool hasMissingValues = false; |
| uint count = 0; |
| uint spreadCount = 0; |
| |
| ParseNodePtr pnode1 = ParseArrayList<buildAST>(&arrayOfTaggedInts, &arrayOfInts, &arrayOfNumbers, &hasMissingValues, &count, &spreadCount); |
| |
| if (buildAST) |
| { |
| pnode = CreateNodeForOpT<knopArray>(); |
| pnode->pnode1 = pnode1; |
| pnode->arrayOfTaggedInts = arrayOfTaggedInts; |
| pnode->arrayOfInts = arrayOfInts; |
| pnode->arrayOfNumbers = arrayOfNumbers; |
| pnode->hasMissingValues = hasMissingValues; |
| pnode->count = count; |
| pnode->spreadCount = spreadCount; |
| |
| if (pnode->pnode1) |
| { |
| this->CheckArguments(pnode->pnode1); |
| } |
| } |
| |
| return pnode; |
| } |
| |
| /*************************************************************************** |
| Create an ArrayLiteral node |
| Parse a list of array elements. [ a, b, , c, ] |
| ***************************************************************************/ |
| template<bool buildAST> |
| ParseNodePtr Parser::ParseArrayList(bool *pArrayOfTaggedInts, bool *pArrayOfInts, bool *pArrayOfNumbers, bool *pHasMissingValues, uint *count, uint *spreadCount) |
| { |
| ParseNodePtr pnodeArg = nullptr; |
| ParseNodePtr pnodeList = nullptr; |
| ParseNodePtr *lastNodeRef = nullptr; |
| |
| *count = 0; |
| |
| // Check for an empty list |
| if (tkRBrack == m_token.tk) |
| { |
| return nullptr; |
| } |
| |
| this->m_arrayDepth++; |
| bool arrayOfTaggedInts = buildAST; |
| bool arrayOfInts = buildAST; |
| bool arrayOfNumbers = buildAST; |
| bool arrayOfVarInts = false; |
| bool hasMissingValues = false; |
| |
| for (;;) |
| { |
| (*count)++; |
| if (tkComma == m_token.tk || tkRBrack == m_token.tk) |
| { |
| hasMissingValues = true; |
| arrayOfTaggedInts = false; |
| arrayOfInts = false; |
| arrayOfNumbers = false; |
| if (buildAST) |
| { |
| pnodeArg = CreateNodeForOpT<knopEmpty>(); |
| } |
| } |
| else |
| { |
| // Allow Spread in array literals. |
| pnodeArg = ParseExpr<buildAST>(koplCma, nullptr, TRUE, /* fAllowEllipsis */ TRUE); |
| if (buildAST) |
| { |
| if (pnodeArg->nop == knopEllipsis) |
| { |
| (*spreadCount)++; |
| } |
| this->CheckArguments(pnodeArg); |
| } |
| } |
| |
| #if DEBUG |
| if (m_grfscr & fscrEnforceJSON && !IsJSONValid(pnodeArg)) |
| { |
| Error(ERRsyntax); |
| } |
| #endif |
| |
| if (buildAST) |
| { |
| if (arrayOfNumbers) |
| { |
| if (pnodeArg->nop != knopInt) |
| { |
| arrayOfTaggedInts = false; |
| if (pnodeArg->nop != knopFlt) |
| { |
| // Not an array of constants. |
| arrayOfInts = false; |
| arrayOfNumbers = false; |
| } |
| else if (arrayOfInts && Js::JavascriptNumber::IsInt32OrUInt32(pnodeArg->AsParseNodeFloat()->dbl) && (!Js::JavascriptNumber::IsInt32(pnodeArg->AsParseNodeFloat()->dbl) || pnodeArg->AsParseNodeFloat()->dbl == -2147483648.0)) |
| { |
| // We've seen nothing but ints, and this is a uint32 but not an int32. |
| // Unless we see an actual float at some point, we want an array of vars |
| // so we can work with tagged ints. |
| arrayOfVarInts = true; |
| } |
| else |
| { |
| // Not an int array, but it may still be a float array. |
| arrayOfInts = false; |
| } |
| } |
| else |
| { |
| if (Js::SparseArraySegment<int32>::IsMissingItem((int32*)&pnodeArg->AsParseNodeInt()->lw)) |
| { |
| arrayOfInts = false; |
| } |
| if (Js::TaggedInt::IsOverflow(pnodeArg->AsParseNodeInt()->lw)) |
| { |
| arrayOfTaggedInts = false; |
| } |
| } |
| } |
| AddToNodeListEscapedUse(&pnodeList, &lastNodeRef, pnodeArg); |
| } |
| |
| if (tkComma != m_token.tk) |
| { |
| break; |
| } |
| this->GetScanner()->Scan(); |
| |
| if (tkRBrack == m_token.tk) |
| { |
| break; |
| } |
| } |
| |
| if (spreadCount != nullptr && *spreadCount > 0) { |
| CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, SpreadFeature, m_scriptContext); |
| } |
| |
| if (buildAST) |
| { |
| Assert(lastNodeRef); |
| Assert(*lastNodeRef); |
| pnodeList->ichLim = (*lastNodeRef)->ichLim; |
| |
| if (arrayOfVarInts && arrayOfInts) |
| { |
| arrayOfInts = false; |
| arrayOfNumbers = false; |
| } |
| *pArrayOfTaggedInts = arrayOfTaggedInts; |
| *pArrayOfInts = arrayOfInts; |
| *pArrayOfNumbers = arrayOfNumbers; |
| *pHasMissingValues = hasMissingValues; |
| } |
| this->m_arrayDepth--; |
| return pnodeList; |
| } |
| |
| Parser::MemberNameToTypeMap* Parser::CreateMemberNameMap(ArenaAllocator* pAllocator) |
| { |
| Assert(pAllocator); |
| return Anew(pAllocator, MemberNameToTypeMap, pAllocator, 5); |
| } |
| |
| template<bool buildAST> void Parser::ParseComputedName(ParseNodePtr* ppnodeName, LPCOLESTR* ppNameHint, LPCOLESTR* ppFullNameHint, uint32 *pNameLength, uint32 *pShortNameOffset) |
| { |
| this->GetScanner()->Scan(); |
| ParseNodePtr pnodeNameExpr = ParseExpr<buildAST>(koplCma, nullptr, TRUE, FALSE, *ppNameHint, pNameLength, pShortNameOffset); |
| if (buildAST) |
| { |
| *ppnodeName = CreateUniNode(knopComputedName, pnodeNameExpr, pnodeNameExpr->ichMin, pnodeNameExpr->ichLim); |
| } |
| |
| if (ppFullNameHint && buildAST && CONFIG_FLAG(UseFullName)) |
| { |
| *ppFullNameHint = FormatPropertyString(*ppNameHint, pnodeNameExpr, pNameLength, pShortNameOffset); |
| } |
| |
| ChkCurTokNoScan(tkRBrack, ERRnoRbrack); |
| } |
| |
| /*************************************************************************** |
| Parse a list of object set/get members, e.g.: |
| { get foo(){ ... }, set bar(arg) { ... } } |
| ***************************************************************************/ |
| template<bool buildAST> |
| ParseNodeBin * Parser::ParseMemberGetSet(OpCode nop, LPCOLESTR* ppNameHint, size_t iecpMin, charcount_t ichMin) |
| { |
| ParseNodePtr pnodeName = nullptr; |
| Assert(nop == knopGetMember || nop == knopSetMember); |
| Assert(ppNameHint); |
| IdentPtr pid = nullptr; |
| bool isComputedName = false; |
| |
| *ppNameHint = nullptr; |
| |
| switch (m_token.tk) |
| { |
| default: |
| if (!m_token.IsReservedWord()) |
| { |
| Error(ERRnoMemberIdent); |
| } |
| // fall through |
| case tkID: |
| pid = m_token.GetIdentifier(this->GetHashTbl()); |
| *ppNameHint = pid->Psz(); |
| if (buildAST) |
| { |
| pnodeName = CreateStrNode(pid); |
| } |
| break; |
| case tkStrCon: |
| if (IsStrictMode() && this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) |
| { |
| Error(ERRES5NoOctal); |
| } |
| pid = m_token.GetStr(); |
| *ppNameHint = pid->Psz(); |
| if (buildAST) |
| { |
| pnodeName = CreateStrNode(pid); |
| } |
| break; |
| |
| case tkIntCon: |
| if (IsStrictMode() && this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) |
| { |
| Error(ERRES5NoOctal); |
| } |
| |
| pid = this->GetScanner()->PidFromLong(m_token.GetLong()); |
| if (buildAST) |
| { |
| pnodeName = CreateStrNode(pid); |
| } |
| break; |
| |
| case tkFltCon: |
| if (IsStrictMode() && this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) |
| { |
| Error(ERRES5NoOctal); |
| } |
| |
| pid = this->GetScanner()->PidFromDbl(m_token.GetDouble()); |
| if (buildAST) |
| { |
| pnodeName = CreateStrNode(pid); |
| } |
| break; |
| |
| case tkLBrack: |
| // Computed property name: get|set [expr] () { } |
| if (!m_scriptContext->GetConfig()->IsES6ObjectLiteralsEnabled()) |
| { |
| Error(ERRnoMemberIdent); |
| } |
| LPCOLESTR emptyHint = nullptr; |
| uint32 offset = 0; |
| ParseComputedName<buildAST>(&pnodeName, &emptyHint, ppNameHint, &offset); |
| |
| isComputedName = true; |
| break; |
| } |
| |
| MemberType memberType; |
| ushort flags = fFncMethod | fFncNoName; |
| if (nop == knopGetMember) |
| { |
| memberType = MemberTypeGetter; |
| flags |= fFncNoArg; |
| } |
| else |
| { |
| Assert(nop == knopSetMember); |
| memberType = MemberTypeSetter; |
| flags |= fFncOneArg; |
| } |
| |
| ParseNodeFnc * pnodeFnc = ParseFncDeclNoCheckScope<buildAST>(flags, SuperRestrictionState::PropertyAllowed, *ppNameHint, |
| /*needsPIDOnRCurlyScan*/ false); |
| |
| pnodeFnc->cbStringMin = iecpMin; |
| |
| if (isComputedName) |
| { |
| pnodeFnc->SetHasComputedName(); |
| } |
| pnodeFnc->SetHasHomeObj(); |
| pnodeFnc->SetIsAccessor(); |
| |
| if (buildAST) |
| { |
| return CreateBinNode(nop, pnodeName, pnodeFnc); |
| } |
| else |
| { |
| return nullptr; |
| } |
| } |
| |
| /*************************************************************************** |
| Parse a list of object members. e.g. { x:foo, 'y me':bar } |
| ***************************************************************************/ |
| template<bool buildAST> |
| ParseNodePtr Parser::ParseMemberList(LPCOLESTR pNameHint, uint32* pNameHintLength, tokens declarationType) |
| { |
| ParseNodeBin * pnodeArg = nullptr; |
| ParseNodePtr pnodeEllipsis = nullptr; |
| ParseNodePtr pnodeName = nullptr; |
| ParseNodePtr pnodeList = nullptr; |
| ParseNodePtr *lastNodeRef = nullptr; |
| LPCOLESTR pFullNameHint = nullptr; // A calculated full name |
| uint32 fullNameHintLength = pNameHintLength ? *pNameHintLength : 0; |
| uint32 shortNameOffset = 0; |
| bool isProtoDeclared = false; |
| bool seenRest = false; |
| |
| // we get declaration tkLCurly - when the possible object pattern found under the expression. |
| bool isObjectPattern = (declarationType == tkVAR || declarationType == tkLET || declarationType == tkCONST || declarationType == tkLCurly) && IsES6DestructuringEnabled(); |
| |
| // Check for an empty list |
| if (tkRCurly == m_token.tk) |
| { |
| return nullptr; |
| } |
| |
| ArenaAllocator tempAllocator(_u("MemberNames"), m_nodeAllocator.GetPageAllocator(), Parser::OutOfMemory); |
| |
| bool hasDeferredInitError = false; |
| |
| for (;;) |
| { |
| bool isComputedName = false; |
| #if DEBUG |
| if ((m_grfscr & fscrEnforceJSON) && (tkStrCon != m_token.tk || !(this->GetScanner()->IsDoubleQuoteOnLastTkStrCon()))) |
| { |
| Error(ERRsyntax); |
| } |
| #endif |
| bool isAsyncMethod = false; |
| charcount_t ichMin = this->GetScanner()->IchMinTok(); |
| size_t iecpMin = this->GetScanner()->IecpMinTok(); |
| if (m_token.tk == tkID && m_token.GetIdentifier(this->GetHashTbl()) == wellKnownPropertyPids.async && m_scriptContext->GetConfig()->IsES7AsyncAndAwaitEnabled()) |
| { |
| RestorePoint parsedAsync; |
| this->GetScanner()->Capture(&parsedAsync); |
| ichMin = this->GetScanner()->IchMinTok(); |
| iecpMin = this->GetScanner()->IecpMinTok(); |
| |
| this->GetScanner()->ScanForcingPid(); |
| if (m_token.tk == tkLParen || m_token.tk == tkColon || m_token.tk == tkRCurly || this->GetScanner()->FHadNewLine() || m_token.tk == tkComma) |
| { |
| this->GetScanner()->SeekTo(parsedAsync); |
| } |
| else |
| { |
| isAsyncMethod = true; |
| } |
| } |
| |
| bool isGenerator = m_scriptContext->GetConfig()->IsES6GeneratorsEnabled() && |
| m_token.tk == tkStar; |
| ushort fncDeclFlags = fFncNoName | fFncMethod; |
| if (isGenerator) |
| { |
| if (isAsyncMethod) |
| { |
| Error(ERRsyntax); |
| } |
| |
| // Include star character in the function extents |
| ichMin = this->GetScanner()->IchMinTok(); |
| iecpMin = this->GetScanner()->IecpMinTok(); |
| |
| this->GetScanner()->ScanForcingPid(); |
| fncDeclFlags |= fFncGenerator; |
| } |
| |
| IdentPtr pidHint = nullptr; // A name scoped to current expression |
| Token tkHint = m_token; |
| charcount_t idHintIchMin = static_cast<charcount_t>(this->GetScanner()->IecpMinTok()); |
| charcount_t idHintIchLim = static_cast<charcount_t>(this->GetScanner()->IecpLimTok()); |
| bool wrapInBrackets = false; |
| bool seenEllipsis = false; |
| switch (m_token.tk) |
| { |
| default: |
| if (!m_token.IsReservedWord()) |
| { |
| Error(ERRnoMemberIdent); |
| } |
| // allow reserved words |
| wrapInBrackets = true; |
| // fall-through |
| case tkID: |
| pidHint = m_token.GetIdentifier(this->GetHashTbl()); |
| if (buildAST) |
| { |
| pnodeName = CreateStrNode(pidHint); |
| } |
| break; |
| |
| case tkStrCon: |
| if (IsStrictMode() && this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) |
| { |
| Error(ERRES5NoOctal); |
| } |
| wrapInBrackets = true; |
| pidHint = m_token.GetStr(); |
| if (buildAST) |
| { |
| pnodeName = CreateStrNode(pidHint); |
| } |
| break; |
| |
| case tkIntCon: |
| // Object initializers with numeric labels allowed in JS6 |
| if (IsStrictMode() && this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) |
| { |
| Error(ERRES5NoOctal); |
| } |
| |
| pidHint = this->GetScanner()->PidFromLong(m_token.GetLong()); |
| if (buildAST) |
| { |
| pnodeName = CreateStrNode(pidHint); |
| } |
| break; |
| |
| case tkFltCon: |
| if (IsStrictMode() && this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) |
| { |
| Error(ERRES5NoOctal); |
| } |
| |
| pidHint = this->GetScanner()->PidFromDbl(m_token.GetDouble()); |
| if (buildAST) |
| { |
| pnodeName = CreateStrNode(pidHint); |
| } |
| wrapInBrackets = true; |
| break; |
| |
| case tkLBrack: |
| // Computed property name: [expr] : value |
| if (!m_scriptContext->GetConfig()->IsES6ObjectLiteralsEnabled()) |
| { |
| Error(ERRnoMemberIdent); |
| } |
| |
| ParseComputedName<buildAST>(&pnodeName, &pNameHint, &pFullNameHint, &fullNameHintLength, &shortNameOffset); |
| |
| isComputedName = true; |
| break; |
| |
| case tkEllipsis: |
| if (CONFIG_FLAG(ES2018ObjectRestSpread)) |
| { |
| seenEllipsis = true; |
| } |
| else |
| { |
| Error(ERRnoMemberIdent); |
| } |
| break; |
| } |
| |
| if (pFullNameHint == nullptr) |
| { |
| if (CONFIG_FLAG(UseFullName)) |
| { |
| pFullNameHint = AppendNameHints(pNameHint, pidHint, &fullNameHintLength, &shortNameOffset, false, wrapInBrackets); |
| } |
| else |
| { |
| pFullNameHint = pidHint ? pidHint->Psz() : nullptr; |
| fullNameHintLength = pidHint ? pidHint->Cch() : 0; |
| shortNameOffset = 0; |
| } |
| } |
| |
| RestorePoint atPid; |
| |
| // Only move to next token if spread op was not seen |
| if (!seenEllipsis) |
| { |
| this->GetScanner()->Capture(&atPid); |
| this->GetScanner()->ScanForcingPid(); |
| } |
| |
| if (isGenerator && m_token.tk != tkLParen) |
| { |
| Error(ERRnoLparen); |
| } |
| |
| if (tkColon == m_token.tk) |
| { |
| // It is a syntax error if the production of the form __proto__ : <> occurs more than once. From B.3.1 in spec. |
| // Note that previous scan is important because only after that we can determine we have a variable. |
| if (!isComputedName && pidHint == wellKnownPropertyPids.__proto__) |
| { |
| if (isProtoDeclared) |
| { |
| Error(ERRsyntax); |
| } |
| else |
| { |
| isProtoDeclared = true; |
| } |
| } |
| |
| this->GetScanner()->Scan(); |
| ParseNodePtr pnodeExpr = nullptr; |
| if (isObjectPattern) |
| { |
| if (m_token.tk == tkEllipsis) |
| { |
| Error(ERRUnexpectedEllipsis); |
| } |
| |
| RestorePoint atExpression; |
| if (!buildAST && declarationType == tkLCurly && IsPossiblePatternStart()) |
| { |
| this->GetScanner()->Capture(&atExpression); |
| |
| int saveNextBlockId = m_nextBlockId; |
| |
| // It is possible that we might encounter the shorthand init error. Lets find that out. |
| bool savedDeferredInitError = m_hasDeferredShorthandInitError; |
| m_hasDeferredShorthandInitError = false; |
| |
| IdentToken token; |
| BOOL fLikelyPattern = false; |
| |
| // First identify that the current expression is indeed the object/array literal. Otherwise we will just use the ParsrExpr to parse that. |
| |
| ParseTerm<buildAST>(/* fAllowCall */ m_token.tk != tkSUPER, nullptr /*pNameHint*/, nullptr /*pHintLength*/, nullptr /*pShortNameOffset*/, &token, false /*fUnaryOrParen*/, |
| TRUE, nullptr /*pfCanAssign*/, &fLikelyPattern); |
| |
| m_nextBlockId = saveNextBlockId; |
| |
| this->GetScanner()->SeekTo(atExpression); |
| |
| if (fLikelyPattern) |
| { |
| pnodeExpr = ParseDestructuredVarDecl<buildAST>(declarationType, declarationType != tkLCurly, &seenRest/* *hasSeenRest*/, false /*topLevel*/, false /*allowEmptyExpression*/, true /*isObjectPattern*/); |
| if (m_token.tk != tkComma && m_token.tk != tkRCurly) |
| { |
| if (m_token.IsOperator()) |
| { |
| Error(ERRDestructNoOper); |
| } |
| Error(ERRsyntax); |
| } |
| } |
| else |
| { |
| if (m_hasDeferredShorthandInitError) |
| { |
| Error(ERRnoColon); |
| } |
| |
| pnodeExpr = ParseExpr<buildAST>(koplCma, nullptr/*pfCantAssign*/, TRUE/*fAllowIn*/, FALSE/*fAllowEllipsis*/, pFullNameHint, &fullNameHintLength, &shortNameOffset); |
| } |
| |
| m_hasDeferredShorthandInitError = savedDeferredInitError; |
| } |
| else |
| { |
| pnodeExpr = ParseDestructuredVarDecl<buildAST>(declarationType, declarationType != tkLCurly, &seenRest/* *hasSeenRest*/, false /*topLevel*/, false /*allowEmptyExpression*/, true /*isObjectPattern*/); |
| if (m_token.tk != tkComma && m_token.tk != tkRCurly) |
| { |
| if (m_token.IsOperator()) |
| { |
| Error(ERRDestructNoOper); |
| } |
| Error(ERRsyntax); |
| } |
| } |
| } |
| else |
| { |
| pnodeExpr = ParseExpr<buildAST>(koplCma, nullptr/*pfCantAssign*/, TRUE/*fAllowIn*/, FALSE/*fAllowEllipsis*/, pFullNameHint, &fullNameHintLength, &shortNameOffset); |
| |
| if (pnodeExpr && pnodeExpr->nop == knopFncDecl) |
| { |
| ParseNodeFnc* funcNode = pnodeExpr->AsParseNodeFnc(); |
| if (isComputedName) |
| { |
| funcNode->SetHasComputedName(); |
| } |
| funcNode->SetHasHomeObj(); |
| } |
| } |
| #if DEBUG |
| if ((m_grfscr & fscrEnforceJSON) && !IsJSONValid(pnodeExpr)) |
| { |
| Error(ERRsyntax); |
| } |
| #endif |
| if (buildAST) |
| { |
| pnodeArg = CreateBinNode(isObjectPattern ? knopObjectPatternMember : knopMember, pnodeName, pnodeExpr); |
| if (pnodeArg->pnode1->nop == knopStr) |
| { |
| pnodeArg->pnode1->AsParseNodeStr()->pid->PromoteAssignmentState(); |
| } |
| } |
| } |
| else if (m_token.tk == tkLParen && m_scriptContext->GetConfig()->IsES6ObjectLiteralsEnabled()) |
| { |
| if (isObjectPattern) |
| { |
| Error(ERRInvalidAssignmentTarget); |
| } |
| // Shorthand syntax: foo() {} -> foo: function() {} |
| |
| // Rewind to the PID and parse a function expression. |
| this->GetScanner()->SeekTo(atPid); |
| |
| ParseNodeFnc * pnodeFnc = ParseFncDeclNoCheckScope<buildAST>(fncDeclFlags | (isAsyncMethod ? fFncAsync : fFncNoFlgs), SuperRestrictionState::PropertyAllowed, pFullNameHint, |
| /*needsPIDOnRCurlyScan*/ false); |
| |
| if (isAsyncMethod || isGenerator) |
| { |
| pnodeFnc->cbStringMin = iecpMin; |
| } |
| |
| if (isComputedName) |
| { |
| pnodeFnc->SetHasComputedName(); |
| pnodeFnc->cbStringMin = iecpMin; |
| |
| } |
| pnodeFnc->SetHasHomeObj(); |
| |
| if (buildAST) |
| { |
| pnodeArg = CreateBinNode(knopMember, pnodeName, pnodeFnc); |
| } |
| } |
| else if (seenEllipsis) |
| { |
| if (!isObjectPattern) |
| { |
| pnodeEllipsis = ParseExpr<buildAST>(koplCma, nullptr, TRUE, /* fAllowEllipsis */ TRUE); |
| } |
| else |
| { |
| pnodeEllipsis = ParseDestructuredVarDecl<buildAST>(declarationType, declarationType != tkLCurly, &seenRest/* *hasSeenRest*/, false /*topLevel*/, false /*allowEmptyExpression*/, true /*isObjectPattern*/); |
| } |
| if (buildAST) |
| { |
| this->CheckArguments(pnodeEllipsis); |
| } |
| } |
| else if (nullptr != pidHint) //It's either tkID/tkStrCon/tkFloatCon/tkIntCon |
| { |
| Assert(pidHint->Psz() != nullptr); |
| |
| if ((pidHint == wellKnownPropertyPids.get || pidHint == wellKnownPropertyPids.set) && |
| // get/set are only pseudo keywords when they are identifiers (i.e. not strings) |
| tkHint.tk == tkID && NextTokenIsPropertyNameStart()) |
| { |
| if (isObjectPattern) |
| { |
| Error(ERRInvalidAssignmentTarget); |
| } |
| |
| LPCOLESTR pNameGetOrSet = nullptr; |
| OpCode op = pidHint == wellKnownPropertyPids.get ? knopGetMember : knopSetMember; |
| |
| pnodeArg = ParseMemberGetSet<buildAST>(op, &pNameGetOrSet, iecpMin, ichMin); |
| |
| if (CONFIG_FLAG(UseFullName) && buildAST && pnodeArg->pnode2->nop == knopFncDecl) |
| { |
| // displays as "get object.funcname" or "set object.funcname" |
| uint32 getOrSetOffset = 0; |
| LPCOLESTR intermediateHint = AppendNameHints(pNameHint, pNameGetOrSet, &fullNameHintLength, &shortNameOffset); |
| pFullNameHint = AppendNameHints(pidHint, intermediateHint, &fullNameHintLength, &getOrSetOffset, true); |
| shortNameOffset += getOrSetOffset; |
| } |
| } |
| else if ((m_token.tk == tkRCurly || m_token.tk == tkComma || m_token.tk == tkAsg) && m_scriptContext->GetConfig()->IsES6ObjectLiteralsEnabled()) |
| { |
| // Shorthand {foo} -> {foo:foo} syntax. |
| // {foo = <initializer>} supported only when on object pattern rules are being applied |
| if (tkHint.tk != tkID) |
| { |
| Assert(tkHint.IsReservedWord() |
| || tkHint.tk == tkIntCon || tkHint.tk == tkFltCon || tkHint.tk == tkStrCon); |
| // All keywords are banned in non-strict mode. |
| // Future reserved words are banned in strict mode. |
| if (IsStrictMode() || !tkHint.IsFutureReservedWord(true)) |
| { |
| IdentifierExpectedError(tkHint); |
| } |
| } |
| |
| if (buildAST) |
| { |
| CheckArgumentsUse(pidHint, GetCurrentFunctionNode()); |
| } |
| |
| bool couldBeObjectPattern = !isObjectPattern && m_token.tk == tkAsg; |
| // Saving the current state as we may change the isObjectPattern down below. |
| bool oldState = isObjectPattern; |
| |
| if (couldBeObjectPattern) |
| { |
| declarationType = tkLCurly; |
| isObjectPattern = true; |
| |
| // This may be an error but we are deferring for favouring destructuring. |
| hasDeferredInitError = true; |
| } |
| |
| ParseNodePtr pnodeIdent = nullptr; |
| if (isObjectPattern) |
| { |
| this->GetScanner()->SeekTo(atPid); |
| pnodeIdent = ParseDestructuredVarDecl<buildAST>(declarationType, declarationType != tkLCurly, &seenRest/* *hasSeenRest*/, false /*topLevel*/, false /*allowEmptyExpression*/, true /*isObjectPattern*/); |
| |
| if (m_token.tk != tkComma && m_token.tk != tkRCurly) |
| { |
| if (m_token.IsOperator()) |
| { |
| Error(ERRDestructNoOper); |
| } |
| Error(ERRsyntax); |
| } |
| } |
| else |
| { |
| // Add a reference to the hinted name so we can bind it properly. |
| PidRefStack *ref = PushPidRef(pidHint); |
| |
| if (buildAST) |
| { |
| pnodeIdent = CreateNameNode(pidHint, ref, idHintIchMin, idHintIchLim); |
| } |
| } |
| |
| if (buildAST) |
| { |
| pnodeArg = CreateBinNode(isObjectPattern && !couldBeObjectPattern ? knopObjectPatternMember : knopMemberShort, pnodeName, pnodeIdent); |
| } |
| |
| isObjectPattern = oldState; |
| } |
| else |
| { |
| Error(ERRnoColon); |
| } |
| } |
| else |
| { |
| Error(ERRnoColon); |
| } |
| |
| if (buildAST) |
| { |
| if (seenEllipsis) |
| { |
| Assert(pnodeEllipsis != nullptr); |
| AddToNodeListEscapedUse(&pnodeList, &lastNodeRef, pnodeEllipsis); |
| } |
| else |
| { |
| Assert(pnodeArg->pnode2 != nullptr); |
| if (pnodeArg->pnode2->nop == knopFncDecl) |
| { |
| Assert(fullNameHintLength >= shortNameOffset); |
| ParseNodeFnc * pnodeFunc = pnodeArg->pnode2->AsParseNodeFnc(); |
| pnodeFunc->hint = pFullNameHint; |
| pnodeFunc->hintLength = fullNameHintLength; |
| pnodeFunc->hintOffset = shortNameOffset; |
| } |
| AddToNodeListEscapedUse(&pnodeList, &lastNodeRef, pnodeArg); |
| } |
| } |
| pidHint = nullptr; |
| pFullNameHint = nullptr; |
| if (tkComma != m_token.tk) |
| { |
| break; |
| } |
| this->GetScanner()->ScanForcingPid(); |
| if (tkRCurly == m_token.tk) |
| { |
| break; |
| } |
| if (seenRest) // Rest must be in the last position. |
| { |
| Error(ERRDestructRestLast); |
| } |
| } |
| |
| m_hasDeferredShorthandInitError = m_hasDeferredShorthandInitError || hasDeferredInitError; |
| |
| if (buildAST) |
| { |
| Assert(lastNodeRef); |
| Assert(*lastNodeRef); |
| pnodeList->ichLim = (*lastNodeRef)->ichLim; |
| } |
| |
| return pnodeList; |
| } |
| |
| BOOL Parser::WillDeferParse(Js::LocalFunctionId functionId) |
| { |
| if ((m_grfscr & fscrWillDeferFncParse) != 0) |
| { |
| if (m_stoppedDeferredParse) |
| { |
| return false; |
| } |
| if (!PHASE_ENABLED_RAW(DeferParsePhase, m_sourceContextInfo->sourceContextId, functionId)) |
| { |
| return false; |
| } |
| if (PHASE_FORCE_RAW(Js::DeferParsePhase, m_sourceContextInfo->sourceContextId, functionId) |
| #ifdef ENABLE_DEBUG_CONFIG_OPTIONS |
| || Js::Configuration::Global.flags.IsEnabled(Js::ForceUndoDeferFlag) |
| #endif |
| ) |
| { |
| return true; |
| } |
| #if ENABLE_PROFILE_INFO |
| #ifndef DISABLE_DYNAMIC_PROFILE_DEFER_PARSE |
| if (m_sourceContextInfo->sourceDynamicProfileManager != nullptr) |
| { |
| Js::ExecutionFlags flags = m_sourceContextInfo->sourceDynamicProfileManager->IsFunctionExecuted(functionId); |
| return flags != Js::ExecutionFlags_Executed; |
| } |
| #endif |
| #endif |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // |
| // Call this in ParseFncDecl only to check (and reset) if ParseFncDecl is re-parsing a deferred |
| // function body. If a deferred function is called and being re-parsed, it shouldn't be deferred again. |
| // |
| BOOL Parser::IsDeferredFnc() |
| { |
| if (m_grfscr & fscrDeferredFnc) |
| { |
| m_grfscr &= ~fscrDeferredFnc; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| template<bool buildAST> |
| ParseNode * Parser::ParseFncDeclCheckScope(ushort flags, bool fAllowIn) |
| { |
| ParseNodeBlock * pnodeFncBlockScope = nullptr; |
| ParseNodePtr *ppnodeScopeSave = nullptr; |
| ParseNodePtr *ppnodeExprScopeSave = nullptr; |
| bool fDeclaration = flags & fFncDeclaration; |
| bool noStmtContext = false; |
| if (fDeclaration) |
| { |
| noStmtContext = m_pstmtCur->GetNop() != knopBlock; |
| |
| if (noStmtContext) |
| { |
| // We have a function declaration like "if (a) function f() {}". We didn't see |
| // a block scope on the way in, so we need to pretend we did. Note that this is a syntax error |
| // in strict mode. |
| if (!this->FncDeclAllowedWithoutContext(flags)) |
| { |
| Error(ERRsyntax); |
| } |
| pnodeFncBlockScope = StartParseBlock<buildAST>(PnodeBlockType::Regular, ScopeType_Block); |
| if (buildAST) |
| { |
| PushFuncBlockScope(pnodeFncBlockScope, &ppnodeScopeSave, &ppnodeExprScopeSave); |
| } |
| } |
| } |
| |
| ParseNodeFnc * pnodeFnc = ParseFncDeclInternal<buildAST>(flags, nullptr, /* needsPIDOnRCurlyScan */ false, /* fUnaryOrParen */ false, noStmtContext, SuperRestrictionState::Disallowed, fAllowIn); |
| |
| if (pnodeFncBlockScope) |
| { |
| Assert(pnodeFncBlockScope->pnodeStmt == nullptr); |
| pnodeFncBlockScope->pnodeStmt = pnodeFnc; |
| if (buildAST) |
| { |
| PopFuncBlockScope(ppnodeScopeSave, ppnodeExprScopeSave); |
| } |
| FinishParseBlock(pnodeFncBlockScope); |
| return pnodeFncBlockScope; |
| } |
| |
| return pnodeFnc; |
| |
| } |
| |
| template<bool buildAST> |
| ParseNodeFnc * Parser::ParseFncDeclNoCheckScope(ushort flags, SuperRestrictionState::State superRestrictionState, LPCOLESTR pNameHint, const bool needsPIDOnRCurlyScan, bool fUnaryOrParen, bool fAllowIn) |
| { |
| Assert((flags & fFncDeclaration) == 0); |
| return ParseFncDeclInternal<buildAST>(flags, pNameHint, needsPIDOnRCurlyScan, fUnaryOrParen, /* noStmtContext */ false, superRestrictionState, fAllowIn); |
| } |
| |
| template<bool buildAST> |
| ParseNodeFnc * Parser::ParseFncDeclInternal(ushort flags, LPCOLESTR pNameHint, const bool needsPIDOnRCurlyScan, bool fUnaryOrParen, bool noStmtContext, SuperRestrictionState::State superRestrictionState, bool fAllowIn) |
| { |
| ParseNodeFnc * pnodeFnc = nullptr; |
| ParseNodePtr *ppnodeVarSave = nullptr; |
| |
| bool fDeclaration = flags & fFncDeclaration; |
| bool fModule = (flags & fFncModule) != 0; |
| bool fLambda = (flags & fFncLambda) != 0; |
| charcount_t ichMin = this->GetScanner()->IchMinTok(); |
| bool wasInDeferredNestedFunc = false; |
| |
| uint tryCatchOrFinallyDepthSave = this->m_tryCatchOrFinallyDepth; |
| this->m_tryCatchOrFinallyDepth = 0; |
| |
| if (this->m_arrayDepth) |
| { |
| this->m_funcInArrayDepth++; // Count function depth within array literal |
| } |
| |
| // Update the count of functions nested in the current parent. |
| Assert(m_pnestedCount || !buildAST); |
| uint *pnestedCountSave = m_pnestedCount; |
| if (buildAST || m_pnestedCount) |
| { |
| (*m_pnestedCount)++; |
| } |
| |
| uint scopeCountNoAstSave = m_scopeCountNoAst; |
| m_scopeCountNoAst = 0; |
| |
| // Create the node. |
| pnodeFnc = CreateAllowDeferNodeForOpT<knopFncDecl>(); |
| pnodeFnc->SetDeclaration(fDeclaration); |
| |
| pnodeFnc->nestedFuncEscapes = false; |
| pnodeFnc->cbMin = this->GetScanner()->IecpMinTok(); |
| pnodeFnc->cbStringMin = pnodeFnc->cbMin; |
| pnodeFnc->functionId = (*m_nextFunctionId)++; |
| pnodeFnc->superRestrictionState = superRestrictionState; |
| |
| |
| // Push new parser state with this new function node |
| AppendFunctionToScopeList(fDeclaration, pnodeFnc); |
| |
| // Start the argument list. |
| ppnodeVarSave = m_ppnodeVar; |
| |
| if (buildAST) |
| { |
| pnodeFnc->lineNumber = this->GetScanner()->LineCur(); |
| pnodeFnc->columnNumber = CalculateFunctionColumnNumber(); |
| pnodeFnc->SetNested(m_currentNodeFunc != nullptr); // If there is a current function, then we're a nested function. |
| pnodeFnc->SetStrictMode(IsStrictMode()); // Inherit current strict mode -- may be overridden by the function itself if it contains a strict mode directive. |
| |
| m_pCurrentAstSize = &pnodeFnc->astSize; |
| } |
| else // if !buildAST |
| { |
| wasInDeferredNestedFunc = m_inDeferredNestedFunc; |
| m_inDeferredNestedFunc = true; |
| } |
| |
| m_pnestedCount = &pnodeFnc->nestedCount; |
| |
| AnalysisAssert(pnodeFnc); |
| pnodeFnc->SetIsAsync((flags & fFncAsync) != 0); |
| pnodeFnc->SetIsLambda(fLambda); |
| pnodeFnc->SetIsMethod((flags & fFncMethod) != 0); |
| pnodeFnc->SetIsClassMember((flags & fFncClassMember) != 0); |
| pnodeFnc->SetIsModule(fModule); |
| pnodeFnc->SetIsClassConstructor((flags & fFncClassConstructor) != 0); |
| pnodeFnc->SetIsBaseClassConstructor((flags & fFncBaseClassConstructor) != 0); |
| pnodeFnc->SetHomeObjLocation(Js::Constants::NoRegister); |
| |
| IdentPtr pFncNamePid = nullptr; |
| bool needScanRCurly = true; |
| ParseFncDeclHelper<buildAST>(pnodeFnc, pNameHint, flags, fUnaryOrParen, noStmtContext, &needScanRCurly, fModule, &pFncNamePid, fAllowIn); |
| AddNestedCapturedNames(pnodeFnc); |
| |
| AnalysisAssert(pnodeFnc); |
| |
| *m_ppnodeVar = nullptr; |
| m_ppnodeVar = ppnodeVarSave; |
| |
| if (m_currentNodeFunc && (pnodeFnc->CallsEval() || pnodeFnc->ChildCallsEval())) |
| { |
| GetCurrentFunctionNode()->SetChildCallsEval(true); |
| } |
| |
| // Lambdas do not have "arguments" and instead capture their parent's |
| // binding of "arguments. To ensure the arguments object of the enclosing |
| // non-lambda function is loaded propagate the UsesArguments flag up to |
| // the parent function |
| if (fLambda && (pnodeFnc->UsesArguments() || pnodeFnc->CallsEval())) |
| { |
| ParseNodeFnc * pnodeFncParent = GetCurrentFunctionNode(); |
| |
| if (pnodeFncParent != nullptr) |
| { |
| pnodeFncParent->SetUsesArguments(); |
| } |
| else |
| { |
| m_UsesArgumentsAtGlobal = true; |
| } |
| } |
| |
| if (needScanRCurly && !fModule) |
| { |
| // Consume the next token now that we're back in the enclosing function (whose strictness may be |
| // different from the function we just finished). |
| #if DBG |
| bool expectedTokenValid = m_token.tk == tkRCurly; |
| AssertMsg(expectedTokenValid, "Invalid token expected for RCurly match"); |
| #endif |
| // The next token may need to have a PID created in !buildAST mode, as we may be parsing a method with a string name. |
| if (needsPIDOnRCurlyScan) |
| { |
| this->GetScanner()->ScanForcingPid(); |
| } |
| else |
| { |
| this->GetScanner()->Scan(); |
| } |
| } |
| |
| m_pnestedCount = pnestedCountSave; |
| Assert(!buildAST || !wasInDeferredNestedFunc); |
| m_inDeferredNestedFunc = wasInDeferredNestedFunc; |
| |
| if (this->m_arrayDepth) |
| { |
| this->m_funcInArrayDepth--; |
| if (this->m_funcInArrayDepth == 0) |
| { |
| // We disable deferred parsing if array literals dominate. |
| // But don't do this if the array literal is dominated by function bodies. |
| if (flags & (fFncMethod | fFncClassMember) && m_token.tk != tkSColon) |
| { |
| // Class member methods have optional separators. We need to check whether we are |
| // getting the IchLim of the correct token. |
| Assert(this->GetScanner()->m_tkPrevious == tkRCurly && needScanRCurly); |
| |
| this->m_funcInArray += this->GetScanner()->IchMinTok() - /*tkRCurly*/ 1 - ichMin; |
| } |
| else |
| { |
| this->m_funcInArray += this->GetScanner()->IchLimTok() - ichMin; |
| } |
| } |
| } |
| |
| m_scopeCountNoAst = scopeCountNoAstSave; |
| |
| if (fDeclaration && !IsStrictMode()) |
| { |
| if (pFncNamePid != nullptr && |
| GetCurrentBlock() && |
| GetCurrentBlock()->blockType == PnodeBlockType::Regular) |
| { |
| // Add a function-scoped VarDecl with the same name as the function for |
| // back compat with pre-ES6 code that declares functions in blocks. The |
| // idea is that the last executed declaration wins at the function scope |
| // level and we accomplish this by having each block scoped function |
| // declaration assign to both the block scoped "let" binding, as well |
| // as the function scoped "var" binding. |
| ParseNodeVar * vardecl = CreateVarDeclNode(pFncNamePid, STVariable, false, nullptr, false); |
| vardecl->isBlockScopeFncDeclVar = true; |
| if (GetCurrentFunctionNode() && vardecl->sym->GetIsFormal()) |
| { |
| GetCurrentFunctionNode()->SetHasAnyWriteToFormals(true); |
| } |
| } |
| } |
| |
| if (buildAST && fDeclaration) |
| { |
| Symbol* funcSym = pnodeFnc->GetFuncSymbol(); |
| if (funcSym->GetIsFormal()) |
| { |
| GetCurrentFunctionNode()->SetHasAnyWriteToFormals(true); |
| } |
| } this->m_tryCatchOrFinallyDepth = tryCatchOrFinallyDepthSave; |
| |
| return pnodeFnc; |
| } |
| |
| bool Parser::FncDeclAllowedWithoutContext(ushort flags) |
| { |
| // Statement context required for strict mode, async functions, and generators. |
| // Note that generators aren't detected yet when this method is called; they're checked elsewhere. |
| return !IsStrictMode() && !(flags & fFncAsync); |
| } |
| |
| uint Parser::CalculateFunctionColumnNumber() |
| { |
| uint columnNumber; |
| |
| charcount_t ichMinTok = this->GetScanner()->IchMinTok(); |
| charcount_t ichMinLine = this->GetScanner()->IchMinLine(); |
| if (ichMinTok >= ichMinLine) |
| { |
| // In scenarios involving defer parse IchMinLine() can be incorrect for the first line after defer parse |
| columnNumber = ichMinTok - ichMinLine; |
| if (m_functionBody != nullptr && m_functionBody->GetRelativeLineNumber() == this->GetScanner()->LineCur()) |
| { |
| // Adjust the column if it falls on the first line, where the re-parse is happening. |
| columnNumber += m_functionBody->GetRelativeColumnNumber(); |
| } |
| } |
| else if (m_currentNodeFunc) |
| { |
| // For the first line after defer parse, compute the column relative to the column number |
| // of the lexically parent function. |
| ULONG offsetFromCurrentFunction = ichMinTok - m_currentNodeFunc->ichMin; |
| columnNumber = m_currentNodeFunc->columnNumber + offsetFromCurrentFunction; |
| } |
| else |
| { |
| // if there is no current function, lets give a default of 0. |
| columnNumber = 0; |
| } |
| |
| return columnNumber; |
| } |
| |
| void Parser::AppendFunctionToScopeList(bool fDeclaration, ParseNodeFnc * pnodeFnc) |
| { |
| if (!fDeclaration && m_ppnodeExprScope) |
| { |
| // We're tracking function expressions separately from declarations in this scope |
| // (e.g., inside a catch scope in standards mode). |
| Assert(*m_ppnodeExprScope == nullptr); |
| *m_ppnodeExprScope = pnodeFnc; |
| m_ppnodeExprScope = &pnodeFnc->pnodeNext; |
| } |
| else |
| { |
| Assert(*m_ppnodeScope == nullptr); |
| *m_ppnodeScope = pnodeFnc; |
| m_ppnodeScope = &pnodeFnc->pnodeNext; |
| } |
| } |
| |
| /*************************************************************************** |
| Parse a function definition. |
| ***************************************************************************/ |
| template<bool buildAST> |
| void Parser::ParseFncDeclHelper(ParseNodeFnc * pnodeFnc, LPCOLESTR pNameHint, ushort flags, bool fUnaryOrParen, bool noStmtContext, bool *pNeedScanRCurly, bool skipFormals, IdentPtr* pFncNamePid, bool fAllowIn) |
| { |
| Assert(pnodeFnc); |
| ParseNodeFnc * pnodeFncParent = GetCurrentFunctionNode(); |
| // is the following correct? When buildAST is false, m_currentNodeDeferredFunc can be nullptr on transition to deferred parse from non-deferred |
| ParseNodeFnc * pnodeFncSave = buildAST ? m_currentNodeFunc : m_currentNodeDeferredFunc; |
| ParseNodeFnc * pnodeFncSaveNonLambda = buildAST ? m_currentNodeNonLambdaFunc : m_currentNodeNonLambdaDeferredFunc; |
| int32* pAstSizeSave = m_pCurrentAstSize; |
| |
| bool fDeclaration = (flags & fFncDeclaration) != 0; |
| bool fLambda = (flags & fFncLambda) != 0; |
| bool fAsync = (flags & fFncAsync) != 0; |
| bool fModule = (flags & fFncModule) != 0; |
| bool fDeferred = false; |
| StmtNest *pstmtSave; |
| bool fFunctionInBlock = false; |
| |
| if (buildAST) |
| { |
| fFunctionInBlock = GetCurrentBlockInfo() != GetCurrentFunctionBlockInfo() && |
| (GetCurrentBlockInfo()->pnodeBlock->scope == nullptr || |
| GetCurrentBlockInfo()->pnodeBlock->scope->GetScopeType() != ScopeType_GlobalEvalBlock); |
| } |
| |
| // Save the position of the scanner in case we need to inspect the name hint later |
| RestorePoint beginNameHint; |
| this->GetScanner()->Capture(&beginNameHint); |
| |
| ParseNodeBlock * pnodeFncExprScope = nullptr; |
| Scope *fncExprScope = nullptr; |
| if (!fDeclaration) |
| { |
| if (!fLambda) |
| { |
| pnodeFncExprScope = StartParseBlock<buildAST>(PnodeBlockType::Function, ScopeType_FuncExpr); |
| fncExprScope = pnodeFncExprScope->scope; |
| } |
| |
| // Function expression: push the new function onto the stack now so that the name (if any) will be |
| // local to the new function. |
| |
| this->UpdateCurrentNodeFunc<buildAST>(pnodeFnc, fLambda); |
| } |
| |
| if (!fLambda && !fModule) |
| { |
| this->ParseFncName<buildAST>(pnodeFnc, flags, pFncNamePid); |
| } |
| |
| if (fDeclaration) |
| { |
| // Declaration statement: push the new function now, after parsing the name, so the name is local to the |
| // enclosing function. |
| |
| this->UpdateCurrentNodeFunc<buildAST>(pnodeFnc, fLambda); |
| } |
| |
| if (noStmtContext && pnodeFnc->IsGenerator()) |
| { |
| // Generator decl not allowed outside stmt context. (We have to wait until we've parsed the '*' to |
| // detect generator.) |
| Error(ERRsyntax, pnodeFnc); |
| } |
| |
| // switch scanner to treat 'yield' as keyword in generator functions |
| // or as an identifier in non-generator functions |
| bool fPreviousYieldIsKeyword = this->GetScanner()->SetYieldIsKeywordRegion(pnodeFnc->IsGenerator()); |
| bool fPreviousAwaitIsKeyword = this->GetScanner()->SetAwaitIsKeywordRegion(fAsync); |
| |
| if (pnodeFnc->IsGenerator()) |
| { |
| CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, Generator, m_scriptContext); |
| } |
| |
| if (fncExprScope && pnodeFnc->pnodeName == nullptr) |
| { |
| FinishParseBlock(pnodeFncExprScope); |
| m_nextBlockId--; |
| Adelete(&m_nodeAllocator, fncExprScope); |
| fncExprScope = nullptr; |
| pnodeFncExprScope = nullptr; |
| } |
| |
| pnodeFnc->scope = fncExprScope; |
| |
| // Start a new statement stack. |
| bool topLevelStmt = |
| buildAST && |
| !fFunctionInBlock && |
| (this->m_pstmtCur == nullptr || this->m_pstmtCur->pnodeStmt->nop == knopBlock); |
| |
| pstmtSave = m_pstmtCur; |
| SetCurrentStatement(nullptr); |
| |
| RestorePoint beginFormals; |
| this->GetScanner()->Capture(&beginFormals); |
| BOOL fWasAlreadyStrictMode = IsStrictMode(); |
| BOOL oldStrictMode = this->m_fUseStrictMode; |
| |
| if (fLambda) |
| { |
| CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, Lambda, m_scriptContext); |
| } |
| |
| uint uCanDeferSave = m_grfscr & fscrCanDeferFncParse; |
| uint uDeferSave = m_grfscr & fscrWillDeferFncParse; |
| if (flags & fFncClassMember) |
| { |
| // Disable deferral on class members or other construct with unusual text bounds |
| // as these are usually trivial, and re-parsing is problematic. |
| // NOTE: It is probably worth supporting these cases for memory and load-time purposes, |
| // especially as they become more and more common. |
| m_grfscr &= ~(fscrCanDeferFncParse | fscrWillDeferFncParse); |
| } |
| |
| bool isTopLevelDeferredFunc = false; |
| |
| #if ENABLE_BACKGROUND_PARSING |
| struct AutoFastScanFlag { |
| bool savedDoingFastScan; |
| AutoFastScanFlag(Parser *parser) : m_parser(parser) { savedDoingFastScan = m_parser->m_doingFastScan; } |
| ~AutoFastScanFlag() { m_parser->m_doingFastScan = savedDoingFastScan; } |
| Parser *m_parser; |
| } flag(this); |
| #endif |
| |
| bool doParallel = false; |
| #if ENABLE_BACKGROUND_PARSING |
| bool parallelJobStarted = false; |
| #endif |
| if (buildAST) |
| { |
| bool isLikelyIIFE = !fDeclaration && fUnaryOrParen; |
| |
| BOOL isDeferredFnc = IsDeferredFnc(); |
| // These are the conditions that prohibit upfront deferral *and* redeferral. |
| isTopLevelDeferredFunc = |
| (m_grfscr & fscrCanDeferFncParse) |
| && !m_InAsmMode |
| // Don't defer a module function wrapper because we need to do export resolution at parse time |
| && !fModule; |
| |
| pnodeFnc->SetCanBeDeferred(isTopLevelDeferredFunc && ParseNodeFnc::CanBeRedeferred(pnodeFnc->fncFlags)); |
| |
| // These are heuristic conditions that prohibit upfront deferral but not redeferral. |
| isTopLevelDeferredFunc = isTopLevelDeferredFunc && !isDeferredFnc && WillDeferParse(pnodeFnc->functionId) && |
| (!isLikelyIIFE || !topLevelStmt || PHASE_FORCE_RAW(Js::DeferParsePhase, m_sourceContextInfo->sourceContextId, pnodeFnc->functionId)); |
| |
| #if ENABLE_BACKGROUND_PARSING |
| if (!fLambda && |
| !isDeferredFnc && |
| !isLikelyIIFE && |
| !this->IsBackgroundParser() && |
| !this->m_doingFastScan && |
| !(pnodeFncSave && m_currDeferredStub) && |
| !(this->m_parseType == ParseType_Deferred && this->m_functionBody && this->m_functionBody->GetScopeInfo() && !isTopLevelDeferredFunc)) |
| { |
| doParallel = DoParallelParse(pnodeFnc); |
| |
| if (doParallel) |
| { |
| BackgroundParser *bgp = m_scriptContext->GetBackgroundParser(); |
| Assert(bgp); |
| if (bgp->HasFailedBackgroundParseItem()) |
| { |
| Error(ERRsyntax); |
| } |
| doParallel = bgp->ParseBackgroundItem(this, pnodeFnc, isTopLevelDeferredFunc); |
| if (doParallel) |
| { |
| parallelJobStarted = true; |
| this->m_hasParallelJob = true; |
| this->m_doingFastScan = true; |
| doParallel = FastScanFormalsAndBody(); |
| if (doParallel) |
| { |
| // Let the foreground thread take care of marking the limit on the function node, |
| // because in some cases this function's caller will want to change that limit, |
| // so we don't want the background thread to try and touch it. |
| pnodeFnc->ichLim = this->GetScanner()->IchLimTok(); |
| pnodeFnc->cbLim = this->GetScanner()->IecpLimTok(); |
| } |
| } |
| } |
| } |
| #endif |
| } |
| |
| if (!doParallel) |
| { |
| #if ENABLE_BACKGROUND_PARSING |
| // We don't want to, or couldn't, let the main thread scan past this function body, so parse |
| // it for real. |
| ParseNodeFnc * pnodeRealFnc = pnodeFnc; |
| if (parallelJobStarted) |
| { |
| // We have to deal with a failure to fast-scan the function (due to syntax error? "/"?) when |
| // a background thread may already have begun to work on the job. Both threads can't be allowed to |
| // operate on the same node. |
| pnodeFnc = CreateDummyFuncNode(fDeclaration); |
| } |
| #endif |
| |
| AnalysisAssert(pnodeFnc); |
| ParseNodeBlock * pnodeBlock = StartParseBlock<buildAST>(PnodeBlockType::Parameter, ScopeType_Parameter); |
| AnalysisAssert(pnodeBlock != nullptr); |
| pnodeFnc->pnodeScopes = pnodeBlock; |
| m_ppnodeVar = &pnodeFnc->pnodeParams; |
| pnodeFnc->pnodeVars = nullptr; |
| ParseNodePtr* varNodesList = &pnodeFnc->pnodeVars; |
| ParseNodeVar * argNode = nullptr; |
| |
| if (!fModule && !fLambda) |
| { |
| ParseNodePtr *const ppnodeVarSave = m_ppnodeVar; |
| m_ppnodeVar = &pnodeFnc->pnodeVars; |
| |
| // Create the built-in arguments symbol |
| argNode = this->AddArgumentsNodeToVars(pnodeFnc); |
| |
| // Save the updated var list |
| varNodesList = m_ppnodeVar; |
| m_ppnodeVar = ppnodeVarSave; |
| } |
| |
| ParseNodePtr *ppnodeScopeSave = nullptr; |
| ParseNodePtr *ppnodeExprScopeSave = nullptr; |
| |
| ppnodeScopeSave = m_ppnodeScope; |
| if (pnodeBlock) |
| { |
| // This synthetic block scope will contain all the nested scopes. |
| m_ppnodeScope = &pnodeBlock->pnodeScopes; |
| pnodeBlock->pnodeStmt = pnodeFnc; |
| } |
| |
| // Keep nested function declarations and expressions in the same list at function scope. |
| // (Indicate this by nulling out the current function expressions list.) |
| ppnodeExprScopeSave = m_ppnodeExprScope; |
| m_ppnodeExprScope = nullptr; |
| |
| uint parenExprDepthSave = m_funcParenExprDepth; |
| m_funcParenExprDepth = 0; |
| |
| if (!skipFormals) |
| { |
| bool fLambdaParamsSave = m_reparsingLambdaParams; |
| if (fLambda) |
| { |
| m_reparsingLambdaParams = true; |
| } |
| |
| uint savedStubCount = m_currDeferredStubCount; |
| DeferredFunctionStub* savedStub = m_currDeferredStub; |
| ShiftCurrDeferredStubToChildFunction(pnodeFnc, pnodeFncParent); |
| |
| this->ParseFncFormals<buildAST>(pnodeFnc, pnodeFncParent, flags, isTopLevelDeferredFunc); |
| |
| m_currDeferredStub = savedStub; |
| m_currDeferredStubCount = savedStubCount; |
| |
| m_reparsingLambdaParams = fLambdaParamsSave; |
| } |
| |
| // Create function body scope |
| ParseNodeBlock * pnodeInnerBlock = StartParseBlock<buildAST>(PnodeBlockType::Function, ScopeType_FunctionBody); |
| // Set the parameter block's child to the function body block. |
| // The pnodeFnc->pnodeScopes list is constructed in such a way that it includes all the scopes in this list. |
| // For example if the param scope has one function and body scope has one function then the list will look like below, |
| // param scope block -> function decl from param scope -> body socpe block -> function decl from body scope. |
| *m_ppnodeScope = pnodeInnerBlock; |
| pnodeFnc->pnodeBodyScope = pnodeInnerBlock; |
| |
| // This synthetic block scope will contain all the nested scopes. |
| m_ppnodeScope = &pnodeInnerBlock->pnodeScopes; |
| pnodeInnerBlock->pnodeStmt = pnodeFnc; |
| |
| // DEFER: Begin deferral here (after names are parsed and name nodes created). |
| // Create no more AST nodes until we're done. |
| |
| // Try to defer this func if all these are true: |
| // 0. We are not already in deferred parsing (i.e. buildAST is true) |
| // 1. We are not re-parsing a deferred func which is being invoked. |
| // 2. Dynamic profile suggests this func can be deferred (and deferred parse is on). |
| // 3. This func is top level or defer nested func is on. |
| // 4. Optionally, the function is non-nested and not in eval, or the deferral decision was based on cached profile info, |
| // or the function is sufficiently long. (I.e., don't defer little nested functions unless we're |
| // confident they'll never be executed, because un-deferring nested functions is more expensive.) |
| // NOTE: I'm disabling #4 by default, because we've found other ways to reduce the cost of un-deferral, |
| // and we don't want to create function bodies aggressively for little functions. |
| |
| // We will also temporarily defer all asm.js functions, except for the asm.js |
| // module itself, which we will never defer |
| bool strictModeTurnedOn = false; |
| |
| if (isTopLevelDeferredFunc && |
| !(this->m_grfscr & fscrEvalCode) && |
| pnodeFnc->IsNested() && |
| #ifndef DISABLE_DYNAMIC_PROFILE_DEFER_PARSE |
| m_sourceContextInfo->sourceDynamicProfileManager == nullptr && |
| #endif |
| PHASE_ON_RAW(Js::ScanAheadPhase, m_sourceContextInfo->sourceContextId, pnodeFnc->functionId) && |
| ( |
| !PHASE_FORCE_RAW(Js::DeferParsePhase, m_sourceContextInfo->sourceContextId, pnodeFnc->functionId) || |
| PHASE_FORCE_RAW(Js::ScanAheadPhase, m_sourceContextInfo->sourceContextId, pnodeFnc->functionId) |
| )) |
| { |
| // Try to scan ahead to the end of the function. If we get there before we've scanned a minimum |
| // number of tokens, don't bother deferring, because it's too small. |
| if (this->ScanAheadToFunctionEnd(CONFIG_FLAG(MinDeferredFuncTokenCount))) |
| { |
| isTopLevelDeferredFunc = false; |
| } |
| } |
| |
| Scope* paramScope = pnodeFnc->pnodeScopes ? pnodeFnc->pnodeScopes->scope : nullptr; |
| if (paramScope != nullptr) |
| { |
| if (CONFIG_FLAG(ForceSplitScope)) |
| { |
| pnodeFnc->ResetBodyAndParamScopeMerged(); |
| } |
| else if (pnodeFnc->HasNonSimpleParameterList() && pnodeFnc->IsBodyAndParamScopeMerged()) |
| { |
| paramScope->ForEachSymbolUntil([this, paramScope, pnodeFnc](Symbol* sym) { |
| if (sym->GetPid()->GetTopRef()->GetFuncScopeId() > pnodeFnc->functionId) |
| { |
| // One of the symbol has non local reference. Mark the param scope as we can't merge it with body scope. |
| pnodeFnc->ResetBodyAndParamScopeMerged(); |
| return true; |
| } |
| return false; |
| }); |
| |
| if (pnodeFnc->IsBodyAndParamScopeMerged() && !fDeclaration && pnodeFnc->pnodeName != nullptr) |
| { |
| Assert(pnodeFnc->pnodeName->nop == knopVarDecl); |
| Symbol* funcSym = pnodeFnc->pnodeName->sym; |
| if (funcSym->GetPid()->GetTopRef()->GetFuncScopeId() > pnodeFnc->functionId) |
| { |
| // This is a function expression with name captured in the param scope. In non-eval, non-split cases the function |
| // name symbol is added to the body scope to make it accessible in the body. But if there is a function or var |
| // declaration with the same name in the body then adding to the body will fail. So in this case we have to add |
| // the name symbol to the param scope by splitting it. |
| pnodeFnc->ResetBodyAndParamScopeMerged(); |
| } |
| } |
| } |
| } |
| |
| // If the param scope is merged with the body scope we want to use the param scope symbols in the body scope. |
| // So add a pid ref for the body using the param scope symbol. Note that in this case the same symbol will occur twice |
| // in the same pid ref stack. |
| if (paramScope != nullptr && pnodeFnc->IsBodyAndParamScopeMerged()) |
| { |
| paramScope->ForEachSymbol([this](Symbol* paramSym) |
| { |
| PidRefStack* ref = PushPidRef(paramSym->GetPid()); |
| ref->SetSym(paramSym); |
| }); |
| } |
| |
| AssertMsg(m_funcParenExprDepth == 0, "Paren exprs should have been resolved by the time we finish function formals"); |
| |
| if (fLambda) |
| { |
| #ifdef ASMJS_PLAT |
| if (m_InAsmMode && (isTopLevelDeferredFunc && m_deferAsmJs)) |
| { |
| // asm.js doesn't support lambda functions |
| Js::AsmJSCompiler::OutputError(m_scriptContext, _u("Lambda functions are not supported.")); |
| Js::AsmJSCompiler::OutputError(m_scriptContext, _u("Asm.js compilation failed.")); |
| throw Js::AsmJsParseException(); |
| } |
| #endif |
| } |
| |
| if (m_token.tk == tkRParen) |
| { |
| this->GetScanner()->Scan(); |
| } |
| |
| if (fLambda) |
| { |
| BOOL hadNewLine = this->GetScanner()->FHadNewLine(); |
| |
| // it can be the case we do not have a fat arrow here if there is a valid expression on the left hand side |
| // of the fat arrow, but that expression does not parse as a parameter list. E.g. |
| // a.x => { } |
| // Therefore check for it and error if not found. |
| ChkCurTok(tkDArrow, ERRnoDArrow); |
| |
| // Newline character between arrow parameters and fat arrow is a syntax error but we want to check for |
| // this after verifying there was a => token. Otherwise we would throw the wrong error. |
| if (hadNewLine) |
| { |
| Error(ERRValidIfFollowedBy, _u("Lambda parameter list"), _u("'=>' on the same line")); |
| } |
| } |
| |
| if (isTopLevelDeferredFunc || (m_InAsmMode && m_deferAsmJs)) |
| { |
| fDeferred = true; |
| |
| this->ParseTopLevelDeferredFunc(pnodeFnc, pnodeFncSave, pNameHint, fLambda, pNeedScanRCurly, fAllowIn); |
| } |
| else |
| { |
| AnalysisAssert(pnodeFnc); |
| |
| // Shouldn't be any temps in the arg list. |
| Assert(*m_ppnodeVar == nullptr); |
| |
| // Start the var list. |
| m_ppnodeVar = varNodesList; |
| |
| if (!pnodeFnc->IsBodyAndParamScopeMerged()) |
| { |
| OUTPUT_TRACE_DEBUGONLY(Js::ParsePhase, _u("The param and body scope of the function %s cannot be merged\n"), pnodeFnc->pnodeName ? pnodeFnc->pnodeName->pid->Psz() : _u("Anonymous function")); |
| } |
| |
| // Keep nested function declarations and expressions in the same list at function scope. |
| // (Indicate this by nulling out the current function expressions list.) |
| m_ppnodeExprScope = nullptr; |
| |
| if (buildAST) |
| { |
| if (m_token.tk != tkLCurly && fLambda) |
| { |
| *pNeedScanRCurly = false; |
| } |
| uint savedStubCount = m_currDeferredStubCount; |
| DeferredFunctionStub* savedStub = m_currDeferredStub; |
| ShiftCurrDeferredStubToChildFunction(pnodeFnc, pnodeFncSave); |
| |
| this->FinishFncDecl(pnodeFnc, pNameHint, fLambda, skipFormals, fAllowIn); |
| |
| m_currDeferredStub = savedStub; |
| m_currDeferredStubCount = savedStubCount; |
| } |
| else |
| { |
| this->ParseNestedDeferredFunc(pnodeFnc, fLambda, pNeedScanRCurly, &strictModeTurnedOn, fAllowIn); |
| } |
| } |
| |
| // Restore the paren count for any outer spread/rest error checking. |
| m_funcParenExprDepth = parenExprDepthSave; |
| |
| if (pnodeInnerBlock) |
| { |
| FinishParseBlock(pnodeInnerBlock, *pNeedScanRCurly); |
| } |
| |
| if (!fModule && (m_token.tk == tkLCurly || !fLambda)) |
| { |
| UpdateArgumentsNode(pnodeFnc, argNode); |
| } |
| |
| CreateSpecialSymbolDeclarations(pnodeFnc); |
| |
| // Restore the lists of scopes that contain function expressions. |
| |
| Assert(m_ppnodeExprScope == nullptr || *m_ppnodeExprScope == nullptr); |
| m_ppnodeExprScope = ppnodeExprScopeSave; |
| |
| Assert(m_ppnodeScope); |
| Assert(nullptr == *m_ppnodeScope); |
| m_ppnodeScope = ppnodeScopeSave; |
| |
| if (pnodeBlock) |
| { |
| FinishParseBlock(pnodeBlock, *pNeedScanRCurly); |
| } |
| |
| if (IsStrictMode() || strictModeTurnedOn) |
| { |
| this->m_fUseStrictMode = TRUE; // Now we know this function is in strict mode |
| |
| if (!fWasAlreadyStrictMode) |
| { |
| // If this function turned on strict mode then we didn't check the formal |
| // parameters or function name hint for future reserved word usage. So do that now. |
| RestorePoint afterFnc; |
| this->GetScanner()->Capture(&afterFnc); |
| |
| if (pnodeFnc->pnodeName != nullptr) |
| { |
| // Rewind to the function name hint and check if the token is a reserved word. |
| this->GetScanner()->SeekTo(beginNameHint); |
| this->GetScanner()->Scan(); |
| if (pnodeFnc->IsGenerator()) |
| { |
| Assert(m_token.tk == tkStar); |
| Assert(m_scriptContext->GetConfig()->IsES6GeneratorsEnabled()); |
| Assert(!(flags & fFncClassMember)); |
| this->GetScanner()->Scan(); |
| } |
| if (m_token.IsReservedWord()) |
| { |
| IdentifierExpectedError(m_token); |
| } |
| CheckStrictModeEvalArgumentsUsage(m_token.GetIdentifier(this->GetHashTbl())); |
| } |
| |
| // Fast forward to formal parameter list, check for future reserved words, |
| // then restore scanner as it was. |
| this->GetScanner()->SeekToForcingPid(beginFormals); |
| CheckStrictFormalParameters(); |
| this->GetScanner()->SeekTo(afterFnc); |
| } |
| |
| if (buildAST) |
| { |
| if (pnodeFnc->pnodeName != nullptr) |
| { |
| Assert(pnodeFnc->pnodeName->nop == knopVarDecl); |
| CheckStrictModeEvalArgumentsUsage(pnodeFnc->pnodeName->pid, pnodeFnc->pnodeName); |
| } |
| } |
| |
| this->m_fUseStrictMode = oldStrictMode; |
| CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, StrictModeFunction, m_scriptContext); |
| } |
| |
| ProcessCapturedNames(pnodeFnc); |
| |
| if (fDeferred) |
| { |
| AnalysisAssert(pnodeFnc); |
| pnodeFnc->pnodeVars = nullptr; |
| } |
| |
| #if ENABLE_BACKGROUND_PARSING |
| if (parallelJobStarted) |
| { |
| pnodeFnc = pnodeRealFnc; |
| m_currentNodeFunc = pnodeRealFnc; |
| |
| // Let the foreground thread take care of marking the limit on the function node, |
| // because in some cases this function's caller will want to change that limit, |
| // so we don't want the background thread to try and touch it. |
| pnodeFnc->ichLim = this->GetScanner()->IchLimTok(); |
| pnodeFnc->cbLim = this->GetScanner()->IecpLimTok(); |
| } |
| #endif |
| } |
| |
| // after parsing asm.js module, we want to reset asm.js state before continuing |
| AnalysisAssert(pnodeFnc); |
| if (pnodeFnc->GetAsmjsMode()) |
| { |
| m_InAsmMode = false; |
| } |
| |
| // Restore the statement stack. |
| Assert(nullptr == m_pstmtCur); |
| SetCurrentStatement(pstmtSave); |
| |
| if (pnodeFncExprScope) |
| { |
| FinishParseFncExprScope(pnodeFnc, pnodeFncExprScope); |
| } |
| m_grfscr |= uCanDeferSave; |
| if (!m_stoppedDeferredParse) |
| { |
| m_grfscr |= uDeferSave; |
| } |
| |
| this->GetScanner()->SetYieldIsKeywordRegion(fPreviousYieldIsKeyword); |
| this->GetScanner()->SetAwaitIsKeywordRegion(fPreviousAwaitIsKeyword); |
| |
| // Restore the current function. |
| if (buildAST) |
| { |
| Assert(pnodeFnc == m_currentNodeFunc); |
| |
| m_currentNodeFunc = pnodeFncSave; |
| m_pCurrentAstSize = pAstSizeSave; |
| |
| if (!fLambda) |
| { |
| Assert(pnodeFnc == m_currentNodeNonLambdaFunc); |
| m_currentNodeNonLambdaFunc = pnodeFncSaveNonLambda; |
| } |
| } |
| else |
| { |
| Assert(pnodeFnc == m_currentNodeDeferredFunc); |
| if (!fLambda) |
| { |
| Assert(pnodeFnc == m_currentNodeNonLambdaDeferredFunc); |
| m_currentNodeNonLambdaDeferredFunc = pnodeFncSaveNonLambda; |
| } |
| m_currentNodeDeferredFunc = pnodeFncSave; |
| } |
| |
| if (m_currentNodeFunc && pnodeFnc->HasWithStmt()) |
| { |
| GetCurrentFunctionNode()->SetHasWithStmt(true); |
| } |
| } |
| |
| template<bool buildAST> |
| void Parser::UpdateCurrentNodeFunc(ParseNodeFnc * pnodeFnc, bool fLambda) |
| { |
| if (buildAST) |
| { |
| // Make this the current function and start its sub-function list. |
| m_currentNodeFunc = pnodeFnc; |
| |
| Assert(m_currentNodeDeferredFunc == nullptr); |
| |
| if (!fLambda) |
| { |
| m_currentNodeNonLambdaFunc = pnodeFnc; |
| } |
| } |
| else // if !buildAST |
| { |
| AnalysisAssert(pnodeFnc); |
| |
| if (!fLambda) |
| { |
| m_currentNodeNonLambdaDeferredFunc = pnodeFnc; |
| } |
| |
| m_currentNodeDeferredFunc = pnodeFnc; |
| } |
| } |
| |
| void Parser::ParseTopLevelDeferredFunc(ParseNodeFnc * pnodeFnc, ParseNodeFnc * pnodeFncParent, LPCOLESTR pNameHint, bool fLambda, bool *pNeedScanRCurly, bool fAllowIn) |
| { |
| // Parse a function body that is a transition point from building AST to doing fast syntax check. |
| |
| pnodeFnc->pnodeVars = nullptr; |
| pnodeFnc->pnodeBody = nullptr; |
| |
| this->m_deferringAST = TRUE; |
| |
| // Put the scanner into "no hashing" mode. |
| BYTE deferFlags = this->GetScanner()->SetDeferredParse(TRUE); |
| |
| if (!fLambda) |
| { |
| ChkCurTok(tkLCurly, ERRnoLcurly); |
| } |
| else |
| { |
| // Lambda may consist of a single expression instead of a block |
| if (this->GetScanner()->m_ptoken->tk == tkLCurly) |
| { |
| this->GetScanner()->Scan(); |
| } |
| else |
| { |
| *pNeedScanRCurly = false; |
| } |
| } |
| |
| ParseNodePtr *ppnodeVarSave = m_ppnodeVar; |
| |
| m_ppnodeVar = &pnodeFnc->pnodeVars; |
| |
| // Don't try and skip scanning nested deferred lambdas which have only a single expression in the body. |
| // Their more-complicated text extents won't match the deferred-stub and the single expression should be fast to scan, anyway. |
| if (fLambda && !*pNeedScanRCurly) |
| { |
| ParseExpressionLambdaBody<false>(pnodeFnc, fAllowIn); |
| } |
| else if (pnodeFncParent != nullptr && m_currDeferredStub != nullptr) |
| { |
| // We've already parsed this function body for syntax errors on the initial parse of the script. |
| // We have information that allows us to skip it, so do so. |
| |
| Assert(pnodeFncParent->nestedCount != 0); |
| DeferredFunctionStub *stub = m_currDeferredStub + (pnodeFncParent->nestedCount - 1); |
| |
| Assert(pnodeFnc->ichMin == stub->ichMin |
| || (stub->fncFlags & kFunctionIsAsync) == kFunctionIsAsync |
| || ((stub->fncFlags & kFunctionIsMethod) == kFunctionIsMethod && ( |
| (stub->fncFlags & kFunctionIsAccessor) == kFunctionIsAccessor |
| || (stub->fncFlags & kFunctionIsGenerator) == kFunctionIsGenerator |
| || (stub->fncFlags & kFunctionHasComputedName) == kFunctionHasComputedName |
| ))); |
| if (stub->fncFlags & kFunctionCallsEval) |
| { |
| this->MarkEvalCaller(); |
| } |
| |
| PHASE_PRINT_TRACE1( |
| Js::SkipNestedDeferredPhase, |
| _u("Skipping nested deferred function %d. %s: %d...%d\n"), |
| pnodeFnc->functionId, GetFunctionName(pnodeFnc, pNameHint), pnodeFnc->ichMin, stub->restorePoint.m_ichMinTok); |
| |
| this->GetScanner()->SeekTo(stub->restorePoint, m_nextFunctionId); |
| |
| // If we already incremented m_nextFunctionId when we saw some functions in the parameter scope |
| // (in default argument assignment, for example), we want to remove the count of those so the |
| // function ids following the one we are skipping right now are correct. |
| *m_nextFunctionId -= pnodeFnc->nestedCount; |
| |
| for (uint i = 0; i < stub->capturedNameCount; i++) |
| { |
| int stringId = stub->capturedNameSerializedIds[i]; |
| uint32 stringLength = 0; |
| LPCWSTR stringVal = Js::ByteCodeSerializer::DeserializeString(stub, stringId, stringLength); |
| |
| OUTPUT_TRACE_DEBUGONLY(Js::SkipNestedDeferredPhase, _u("\tPushing a reference to '%s'\n"), stringVal); |
| |
| IdentPtr pid = this->GetHashTbl()->PidHashNameLen(stringVal, stringLength); |
| PushPidRef(pid); |
| } |
| |
| pnodeFnc->nestedCount = stub->nestedCount; |
| pnodeFnc->deferredStub = stub->deferredStubs; |
| pnodeFnc->fncFlags = (FncFlags)(pnodeFnc->fncFlags | stub->fncFlags); |
| } |
| else |
| { |
| ParseStmtList<false>(nullptr, nullptr, SM_DeferredParse, true /* isSourceElementList */); |
| } |
| |
| if (!fLambda || *pNeedScanRCurly) |
| { |
| pnodeFnc->ichLim = this->GetScanner()->IchLimTok(); |
| pnodeFnc->cbLim = this->GetScanner()->IecpLimTok(); |
| } |
| |
| m_ppnodeVar = ppnodeVarSave; |
| |
| // Restore the scanner's default hashing mode. |
| // Do this before we consume the next token. |
| this->GetScanner()->SetDeferredParseFlags(deferFlags); |
| |
| if (*pNeedScanRCurly) |
| { |
| ChkCurTokNoScan(tkRCurly, ERRnoRcurly); |
| } |
| |
| #if DBG |
| pnodeFnc->deferredParseNextFunctionId = *this->m_nextFunctionId; |
| #endif |
| this->m_deferringAST = FALSE; |
| } |
| |
| bool Parser::DoParallelParse(ParseNodeFnc * pnodeFnc) const |
| { |
| #if ENABLE_BACKGROUND_PARSING |
| if (!PHASE_ENABLED_RAW(ParallelParsePhase, m_sourceContextInfo->sourceContextId, pnodeFnc->functionId)) |
| { |
| return false; |
| } |
| |
| BackgroundParser *bgp = m_scriptContext->GetBackgroundParser(); |
| return bgp != nullptr; |
| #else |
| return false; |
| #endif |
| } |
| |
| bool Parser::ScanAheadToFunctionEnd(uint count) |
| { |
| bool found = false; |
| uint curlyDepth = 0; |
| |
| RestorePoint funcStart; |
| this->GetScanner()->Capture(&funcStart); |
| |
| for (uint i = 0; i < count; i++) |
| { |
| switch (m_token.tk) |
| { |
| case tkStrTmplBegin: |
| case tkStrTmplMid: |
| case tkStrTmplEnd: |
| case tkDiv: |
| case tkAsgDiv: |
| case tkScanError: |
| case tkEOF: |
| goto LEnd; |
| |
| case tkLCurly: |
| UInt32Math::Inc(curlyDepth, Parser::OutOfMemory); |
| break; |
| |
| case tkRCurly: |
| if (curlyDepth == 1) |
| { |
| found = true; |
| goto LEnd; |
| } |
| if (curlyDepth == 0) |
| { |
| goto LEnd; |
| } |
| curlyDepth--; |
| break; |
| } |
| |
| this->GetScanner()->ScanAhead(); |
| } |
| |
| LEnd: |
| this->GetScanner()->SeekTo(funcStart); |
| return found; |
| } |
| |
| #if ENABLE_BACKGROUND_PARSING |
| bool Parser::FastScanFormalsAndBody() |
| { |
| // The scanner is currently pointing just past the name of a function. |
| // The idea here is to find the end of the function body as quickly as possible, |
| // by tokenizing and tracking {}'s if possible. |
| // String templates require some extra logic but can be handled. |
| |
| // The real wrinkle is "/" and "/=", which may indicate either a RegExp literal or a division, depending |
| // on the context. |
| // To handle this with minimal work, keep track of the last ";" seen at each {} depth. If we see one of the |
| // difficult tokens, rewind to the last ";" at the current {} depth and parse statements until we pass the |
| // point where we had to rewind. This will process the "/" as required. |
| |
| RestorePoint funcStart; |
| this->GetScanner()->Capture(&funcStart); |
| |
| const int maxRestorePointDepth = 16; |
| struct FastScanRestorePoint |
| { |
| RestorePoint restorePoint; |
| uint parenDepth; |
| Js::LocalFunctionId functionId; |
| int blockId; |
| |
| FastScanRestorePoint() : restorePoint(), parenDepth(0) {}; |
| }; |
| FastScanRestorePoint lastSColonAtCurlyDepth[maxRestorePointDepth]; |
| |
| charcount_t ichStart = this->GetScanner()->IchMinTok(); |
| uint blockIdSave = m_nextBlockId; |
| uint functionIdSave = *m_nextFunctionId; |
| uint curlyDepth = 0; |
| uint strTmplDepth = 0; |
| for (;;) |
| { |
| switch (m_token.tk) |
| { |
| case tkStrTmplBegin: |
| UInt32Math::Inc(strTmplDepth, Parser::OutOfMemory); |
| // Fall through |
| |
| case tkStrTmplMid: |
| case tkLCurly: |
| UInt32Math::Inc(curlyDepth, Parser::OutOfMemory); |
| Int32Math::Inc(m_nextBlockId, &m_nextBlockId); |
| break; |
| |
| case tkStrTmplEnd: |
| // We can assert here, because the scanner will only return this token if we've told it we're |
| // in a string template. |
| Assert(strTmplDepth > 0); |
| strTmplDepth--; |
| break; |
| |
| case tkRCurly: |
| if (curlyDepth == 1) |
| { |
| Assert(strTmplDepth == 0); |
| if (PHASE_TRACE1(Js::ParallelParsePhase)) |
| { |
| Output::Print(_u("Finished fast seek: %d. %s -- %d...%d\n"), |
| m_currentNodeFunc->functionId, |
| GetFunctionName(m_currentNodeFunc, m_currentNodeFunc->hint), |
| ichStart, this->GetScanner()->IchLimTok()); |
| } |
| return true; |
| } |
| if (curlyDepth < maxRestorePointDepth) |
| { |
| lastSColonAtCurlyDepth[curlyDepth].restorePoint.m_ichMinTok = (uint)-1; |
| } |
| curlyDepth--; |
| if (strTmplDepth > 0) |
| { |
| this->GetScanner()->SetScanState(Scanner_t::ScanState::ScanStateStringTemplateMiddleOrEnd); |
| } |
| break; |
| |
| case tkSColon: |
| // Track the location of the ";" (if it's outside parens, as we don't, for instance, want |
| // to track the ";"'s in a for-loop header. If we find it's important to rewind within a paren |
| // expression, we can do something more sophisticated.) |
| if (curlyDepth < maxRestorePointDepth && lastSColonAtCurlyDepth[curlyDepth].parenDepth == 0) |
| { |
| this->GetScanner()->Capture(&lastSColonAtCurlyDepth[curlyDepth].restorePoint); |
| lastSColonAtCurlyDepth[curlyDepth].functionId = *this->m_nextFunctionId; |
| lastSColonAtCurlyDepth[curlyDepth].blockId = m_nextBlockId; |
| } |
| break; |
| |
| case tkLParen: |
| if (curlyDepth < maxRestorePointDepth) |
| { |
| UInt32Math::Inc(lastSColonAtCurlyDepth[curlyDepth].parenDepth); |
| } |
| break; |
| |
| case tkRParen: |
| if (curlyDepth < maxRestorePointDepth) |
| { |
| Assert(lastSColonAtCurlyDepth[curlyDepth].parenDepth != 0); |
| lastSColonAtCurlyDepth[curlyDepth].parenDepth--; |
| } |
| break; |
| |
| case tkID: |
| { |
| charcount_t tokLength = this->GetScanner()->IchLimTok() - this->GetScanner()->IchMinTok(); |
| // Detect the function and class keywords so we can track function ID's. |
| // (In fast mode, the scanner doesn't distinguish keywords and doesn't point the token |
| // to a PID.) |
| // Detect try/catch/for to increment block count for them. |
| switch (tokLength) |
| { |
| case 3: |
| if (!memcmp(this->GetScanner()->PchMinTok(), "try", 3) || !memcmp(this->GetScanner()->PchMinTok(), "for", 3)) |
| { |
| Int32Math::Inc(m_nextBlockId, &m_nextBlockId); |
| } |
| break; |
| case 5: |
| if (!memcmp(this->GetScanner()->PchMinTok(), "catch", 5)) |
| { |
| Int32Math::Inc(m_nextBlockId, &m_nextBlockId); |
| } |
| else if (!memcmp(this->GetScanner()->PchMinTok(), "class", 5)) |
| { |
| Int32Math::Inc(m_nextBlockId, &m_nextBlockId); |
| Int32Math::Inc(*this->m_nextFunctionId, (int*)this->m_nextFunctionId); |
| } |
| break; |
| case 8: |
| if (!memcmp(this->GetScanner()->PchMinTok(), "function", 8)) |
| { |
| // Account for the possible func expr scope or dummy block for missing {}'s around a declaration |
| Int32Math::Inc(m_nextBlockId, &m_nextBlockId); |
| Int32Math::Inc(*this->m_nextFunctionId, (int*)this->m_nextFunctionId); |
| } |
| break; |
| } |
| break; |
| } |
| |
| case tkDArrow: |
| Int32Math::Inc(m_nextBlockId, &m_nextBlockId); |
| Int32Math::Inc(*this->m_nextFunctionId, (int*)this->m_nextFunctionId); |
| break; |
| |
| case tkDiv: |
| case tkAsgDiv: |
| { |
| int opl; |
| OpCode nop; |
| tokens tkPrev = this->GetScanner()->m_tkPrevious; |
| if ((this->GetHashTbl()->TokIsBinop(tkPrev, &opl, &nop) && nop != knopNone) || |
| (this->GetHashTbl()->TokIsUnop(tkPrev, &opl, &nop) && |
| nop != knopNone && |
| tkPrev != tkInc && |
| tkPrev != tkDec) || |
| tkPrev == tkColon || |
| tkPrev == tkLParen || |
| tkPrev == tkLBrack || |
| tkPrev == tkRETURN) |
| { |
| // Previous token indicates that we're starting an expression here and can't have a |
| // binary operator now. |
| // Assume this is a RegExp. |
| ParseRegExp<false>(); |
| break; |
| } |
| uint tempCurlyDepth = curlyDepth < maxRestorePointDepth ? curlyDepth : maxRestorePointDepth - 1; |
| for (; tempCurlyDepth != (uint)-1; tempCurlyDepth--) |
| { |
| // We don't know whether we've got a RegExp or a divide. Rewind to the last safe ";" |
| // if we can and parse statements until we pass this point. |
| if (lastSColonAtCurlyDepth[tempCurlyDepth].restorePoint.m_ichMinTok != -1) |
| { |
| break; |
| } |
| } |
| if (tempCurlyDepth != (uint)-1) |
| { |
| ParseNodeFnc * pnodeFncSave = m_currentNodeFunc; |
| int32 *pastSizeSave = m_pCurrentAstSize; |
| uint *pnestedCountSave = m_pnestedCount; |
| ParseNodePtr *ppnodeScopeSave = m_ppnodeScope; |
| ParseNodePtr *ppnodeExprScopeSave = m_ppnodeExprScope; |
| |
| ParseNodeFnc * pnodeFnc = CreateDummyFuncNode(true); |
| |
| charcount_t ichStop = this->GetScanner()->IchLimTok(); |
| curlyDepth = tempCurlyDepth; |
| this->GetScanner()->SeekTo(lastSColonAtCurlyDepth[tempCurlyDepth].restorePoint); |
| m_nextBlockId = lastSColonAtCurlyDepth[tempCurlyDepth].blockId; |
| *this->m_nextFunctionId = lastSColonAtCurlyDepth[tempCurlyDepth].functionId; |
| |
| ParseNodeBlock * pnodeBlock = StartParseBlock<true>(PnodeBlockType::Function, ScopeType_FunctionBody); |
| pnodeFnc->pnodeScopes = pnodeBlock; |
| m_ppnodeScope = &pnodeBlock->pnodeScopes; |
| m_ppnodeExprScope = nullptr; |
| this->GetScanner()->Scan(); |
| do |
| { |
| ParseStatement<false>(); |
| } while (this->GetScanner()->IchMinTok() < ichStop); |
| |
| FinishParseBlock(pnodeBlock); |
| |
| m_currentNodeFunc = pnodeFncSave; |
| m_pCurrentAstSize = pastSizeSave; |
| m_pnestedCount = pnestedCountSave; |
| m_ppnodeScope = ppnodeScopeSave; |
| m_ppnodeExprScope = ppnodeExprScopeSave; |
| |
| // We've already consumed the first token of the next statement, so just continue |
| // without a further scan. |
| continue; |
| } |
| } |
| |
| // fall through to rewind to function start |
| case tkScanError: |
| case tkEOF: |
| // Unexpected token. |
| if (PHASE_TRACE1(Js::ParallelParsePhase)) |
| { |
| Output::Print(_u("Failed fast seek: %d. %s -- %d...%d\n"), |
| m_currentNodeFunc->functionId, |
| GetFunctionName(m_currentNodeFunc, m_currentNodeFunc->hint), |
| ichStart, this->GetScanner()->IchLimTok()); |
| } |
| m_nextBlockId = blockIdSave; |
| *m_nextFunctionId = functionIdSave; |
| this->GetScanner()->SeekTo(funcStart); |
| return false; |
| } |
| |
| this->GetScanner()->ScanNoKeywords(); |
| } |
| } |
| #endif |
| |
| ParseNodeFnc * Parser::CreateDummyFuncNode(bool fDeclaration) |
| { |
| // Create a dummy node and make it look like the current function declaration. |
| // Do this in situations where we want to parse statements without impacting |
| // the state of the "real" AST. |
| |
| ParseNodeFnc * pnodeFnc = CreateAllowDeferNodeForOpT<knopFncDecl>(); |
| pnodeFnc->SetDeclaration(fDeclaration); |
| |
| pnodeFnc->SetNested(m_currentNodeFunc != nullptr); // If there is a current function, then we're a nested function. |
| pnodeFnc->SetStrictMode(IsStrictMode()); // Inherit current strict mode -- may be overridden by the function itself if it contains a strict mode directive. |
| |
| m_pCurrentAstSize = &pnodeFnc->astSize; |
| m_currentNodeFunc = pnodeFnc; |
| m_pnestedCount = &pnodeFnc->nestedCount; |
| |
| return pnodeFnc; |
| } |
| |
| void Parser::ParseNestedDeferredFunc(ParseNodeFnc * pnodeFnc, bool fLambda, bool *pNeedScanRCurly, bool *pStrictModeTurnedOn, bool fAllowIn) |
| { |
| // Parse a function nested inside another deferred function. |
| |
| size_t lengthBeforeBody = this->GetSourceLength(); |
| |
| if (m_token.tk != tkLCurly && fLambda) |
| { |
| ParseExpressionLambdaBody<false>(pnodeFnc, fAllowIn); |
| *pNeedScanRCurly = false; |
| } |
| else |
| { |
| ChkCurTok(tkLCurly, ERRnoLcurly); |
| |
| bool* detectStrictModeOn = IsStrictMode() ? nullptr : pStrictModeTurnedOn; |
| m_ppnodeVar = &m_currentNodeDeferredFunc->pnodeVars; |
| |
| ParseStmtList<false>(nullptr, nullptr, SM_DeferredParse, true /* isSourceElementList */, detectStrictModeOn); |
| |
| ChkCurTokNoScan(tkRCurly, ERRnoRcurly); |
| |
| pnodeFnc->ichLim = this->GetScanner()->IchLimTok(); |
| pnodeFnc->cbLim = this->GetScanner()->IecpLimTok(); |
| } |
| |
| if (*pStrictModeTurnedOn) |
| { |
| pnodeFnc->SetStrictMode(true); |
| } |
| |
| if (!PHASE_OFF1(Js::SkipNestedDeferredPhase)) |
| { |
| // Record the end of the function and the function ID increment that happens inside the function. |
| // Byte code gen will use this to build stub information to allow us to skip this function when the |
| // enclosing function is fully parsed. |
| RestorePoint *restorePoint = Anew(&m_nodeAllocator, RestorePoint); |
| this->GetScanner()->Capture(restorePoint, |
| *m_nextFunctionId - pnodeFnc->functionId - 1, |
| lengthBeforeBody - this->GetSourceLength()); |
| pnodeFnc->pRestorePoint = restorePoint; |
| } |
| } |
| |
| template<bool buildAST> |
| void Parser::ParseFncName(ParseNodeFnc * pnodeFnc, ushort flags, IdentPtr* pFncNamePid) |
| { |
| Assert(pnodeFnc); |
| BOOL fDeclaration = flags & fFncDeclaration; |
| BOOL fIsAsync = flags & fFncAsync; |
| |
| this->GetScanner()->Scan(); |
| |
| // If generators are enabled then we are in a recent enough version |
| // that deferred parsing will create a parse node for pnodeFnc and |
| // it is safe to assume it is not null. |
| if (flags & fFncGenerator) |
| { |
| Assert(m_scriptContext->GetConfig()->IsES6GeneratorsEnabled()); |
| pnodeFnc->SetIsGenerator(); |
| } |
| else if (m_scriptContext->GetConfig()->IsES6GeneratorsEnabled() && |
| m_token.tk == tkStar && |
| !(flags & fFncClassMember)) |
| { |
| if (!fDeclaration) |
| { |
| bool fPreviousYieldIsKeyword = this->GetScanner()->SetYieldIsKeywordRegion(!fDeclaration); |
| this->GetScanner()->Scan(); |
| this->GetScanner()->SetYieldIsKeywordRegion(fPreviousYieldIsKeyword); |
| } |
| else |
| { |
| this->GetScanner()->Scan(); |
| } |
| |
| pnodeFnc->SetIsGenerator(); |
| } |
| |
| if (fIsAsync) |
| { |
| if (pnodeFnc->IsGenerator()) |
| { |
| Error(ERRsyntax); |
| } |
| pnodeFnc->SetIsAsync(); |
| } |
| |
| pnodeFnc->pnodeName = nullptr; |
| |
| if ((m_token.tk != tkID || flags & fFncNoName) |
| && (IsStrictMode() || fDeclaration |
| || pnodeFnc->IsGenerator() || pnodeFnc->IsAsync() |
| || (m_token.tk != tkYIELD && m_token.tk != tkAWAIT))) // Function expressions can have the name yield/await even inside generator/async functions |
| { |
| if (fDeclaration || |
| m_token.IsReservedWord()) // For example: var x = (function break(){}); |
| { |
| IdentifierExpectedError(m_token); |
| } |
| return; |
| } |
| |
| Assert(m_token.tk == tkID || (m_token.tk == tkYIELD && !fDeclaration) || (m_token.tk == tkAWAIT && !fDeclaration)); |
| |
| if (IsStrictMode()) |
| { |
| CheckStrictModeEvalArgumentsUsage(m_token.GetIdentifier(this->GetHashTbl())); |
| } |
| |
| |
| IdentPtr pidBase = m_token.GetIdentifier(this->GetHashTbl()); |
| pnodeFnc->pnodeName = CreateDeclNode(knopVarDecl, pidBase, STFunction); |
| pnodeFnc->pid = pnodeFnc->pnodeName->pid; |
| |
| if (pFncNamePid != nullptr) |
| { |
| *pFncNamePid = pidBase; |
| } |
| |
| this->GetScanner()->Scan(); |
| } |
| |
| void Parser::ValidateFormals() |
| { |
| ParseFncFormals<false>(this->GetCurrentFunctionNode(), nullptr, fFncNoFlgs); |
| // Eat the tkRParen. The ParseFncDeclHelper caller expects to see it. |
| this->GetScanner()->Scan(); |
| } |
| |
| void Parser::ValidateSourceElementList() |
| { |
| ParseStmtList<false>(nullptr, nullptr, SM_NotUsed, true); |
| } |
| |
| void Parser::UpdateOrCheckForDuplicateInFormals(IdentPtr pid, SList<IdentPtr> *formals) |
| { |
| bool isStrictMode = IsStrictMode(); |
| if (isStrictMode) |
| { |
| CheckStrictModeEvalArgumentsUsage(pid); |
| } |
| |
| if (formals->Has(pid)) |
| { |
| if (isStrictMode) |
| { |
| Error(ERRES5ArgSame); |
| } |
| else |
| { |
| Error(ERRFormalSame); |
| } |
| } |
| else |
| { |
| formals->Prepend(pid); |
| } |
| } |
| |
| template<bool buildAST> |
| void Parser::ParseFncFormals(ParseNodeFnc * pnodeFnc, ParseNodeFnc * pnodeParentFnc, ushort flags, bool isTopLevelDeferredFunc) |
| { |
| bool fLambda = (flags & fFncLambda) != 0; |
| bool fMethod = (flags & fFncMethod) != 0; |
| bool fNoArg = (flags & fFncNoArg) != 0; |
| bool fOneArg = (flags & fFncOneArg) != 0; |
| bool fAsync = (flags & fFncAsync) != 0; |
| |
| bool fPreviousYieldIsKeyword = false; |
| bool fPreviousAwaitIsKeyword = false; |
| |
| if (fLambda) |
| { |
| fPreviousYieldIsKeyword = this->GetScanner()->SetYieldIsKeywordRegion(pnodeParentFnc != nullptr && pnodeParentFnc->IsGenerator()); |
| fPreviousAwaitIsKeyword = this->GetScanner()->SetAwaitIsKeywordRegion(fAsync || (pnodeParentFnc != nullptr && pnodeParentFnc->IsAsync())); |
| } |
| |
| Assert(!fNoArg || !fOneArg); // fNoArg and fOneArg can never be true at the same time. |
| |
| // strictFormals corresponds to the StrictFormalParameters grammar production |
| // in the ES spec which just means duplicate names are not allowed |
| bool fStrictFormals = IsStrictMode() || fLambda || fMethod; |
| |
| // When detecting duplicated formals pids are needed so force PID creation (unless the function should take 0 or 1 arg). |
| bool forcePid = fStrictFormals && !fNoArg && !fOneArg; |
| AutoTempForcePid autoForcePid(this->GetScanner(), forcePid); |
| |
| // Lambda's allow single formal specified by a single binding identifier without parentheses, special case it. |
| if (fLambda && m_token.tk == tkID) |
| { |
| IdentPtr pid = m_token.GetIdentifier(this->GetHashTbl()); |
| |
| CreateVarDeclNode(pid, STFormal, false, nullptr, false); |
| CheckPidIsValid(pid); |
| |
| this->GetScanner()->Scan(); |
| |
| if (m_token.tk != tkDArrow) |
| { |
| Error(ERRsyntax, this->GetScanner()->IchMinTok(), this->GetScanner()->IchLimTok()); |
| } |
| |
| if (fLambda) |
| { |
| this->GetScanner()->SetYieldIsKeywordRegion(fPreviousYieldIsKeyword); |
| this->GetScanner()->SetAwaitIsKeywordRegion(fPreviousAwaitIsKeyword); |
| } |
| |
| return; |
| } |
| else if (fLambda && m_token.tk == tkAWAIT) |
| { |
| // async await => {} |
| IdentifierExpectedError(m_token); |
| } |
| |
| // Otherwise, must have a parameter list within parens. |
| ChkCurTok(tkLParen, ERRnoLparen); |
| |
| // Now parse the list of arguments, if present |
| if (m_token.tk == tkRParen) |
| { |
| if (fOneArg) |
| { |
| Error(ERRSetterMustHaveOneParameter); |
| } |
| } |
| else |
| { |
| if (fNoArg) |
| { |
| Error(ERRGetterMustHaveNoParameters); |
| } |
| SList<IdentPtr> formals(&m_nodeAllocator); |
| ParseNodeVar * pnodeT = nullptr; |
| bool seenRestParameter = false; |
| bool isNonSimpleParameterList = false; |
| for (Js::ArgSlot argPos = 0; ; ++argPos) |
| { |
| bool isBindingPattern = false; |
| if (m_scriptContext->GetConfig()->IsES6RestEnabled() && m_token.tk == tkEllipsis) |
| { |
| // Possible rest parameter |
| this->GetScanner()->Scan(); |
| seenRestParameter = true; |
| } |
| if (m_token.tk != tkID) |
| { |
| if (IsES6DestructuringEnabled() && IsPossiblePatternStart()) |
| { |
| // Mark that the function has a non simple parameter list before parsing the pattern since the pattern can have function definitions. |
| this->GetCurrentFunctionNode()->SetHasNonSimpleParameterList(); |
| this->GetCurrentFunctionNode()->SetHasDestructuredParams(); |
| |
| ParseNodePtr *const ppnodeVarSave = m_ppnodeVar; |
| m_ppnodeVar = &pnodeFnc->pnodeVars; |
| |
| ParseNodePtr * ppNodeLex = m_currentBlockInfo->m_ppnodeLex; |
| Assert(ppNodeLex != nullptr); |
| |
| ParseNodeParamPattern * paramPattern = nullptr; |
| ParseNode * pnodePattern = nullptr; |
| if (isTopLevelDeferredFunc) |
| { |
| pnodePattern = ParseDestructuredLiteral<false>(tkLET, true /*isDecl*/, false /*topLevel*/); |
| } |
| else |
| { |
| pnodePattern = ParseDestructuredLiteral<buildAST>(tkLET, true /*isDecl*/, false /*topLevel*/); |
| } |
| |
| // Instead of passing the STFormal all the way on many methods, it seems it is better to change the symbol type afterward. |
| for (ParseNodePtr lexNode = *ppNodeLex; lexNode != nullptr; lexNode = lexNode->AsParseNodeVar()->pnodeNext) |
| { |
| Assert(lexNode->IsVarLetOrConst()); |
| UpdateOrCheckForDuplicateInFormals(lexNode->AsParseNodeVar()->pid, &formals); |
| lexNode->AsParseNodeVar()->sym->SetSymbolType(STFormal); |
| if (lexNode->AsParseNodeVar()->pid == wellKnownPropertyPids.arguments) |
| { |
| GetCurrentFunctionNode()->grfpn |= PNodeFlags::fpnArguments_overriddenInParam; |
| } |
| } |
| |
| m_ppnodeVar = ppnodeVarSave; |
| |
| if (buildAST) |
| { |
| if (isTopLevelDeferredFunc) |
| { |
| Assert(pnodePattern == nullptr); |
| // Create a dummy pattern node as we need the node to be considered for the param count |
| paramPattern = CreateDummyParamPatternNode(this->GetScanner()->IchMinTok()); |
| } |
| else |
| { |
| Assert(pnodePattern); |
| paramPattern = CreateParamPatternNode(pnodePattern); |
| } |
| // Linking the current formal parameter (which is pattern parameter) with other formals. |
| *m_ppnodeVar = paramPattern; |
| paramPattern->pnodeNext = nullptr; |
| m_ppnodeVar = ¶mPattern->pnodeNext; |
| } |
| |
| isBindingPattern = true; |
| isNonSimpleParameterList = true; |
| } |
| else |
| { |
| IdentifierExpectedError(m_token); |
| } |
| } |
| |
| if (!isBindingPattern) |
| { |
| IdentPtr pid = m_token.GetIdentifier(this->GetHashTbl()); |
| LPCOLESTR pNameHint = pid->Psz(); |
| uint32 nameHintLength = pid->Cch(); |
| uint32 nameHintOffset = 0; |
| |
| if (seenRestParameter) |
| { |
| this->GetCurrentFunctionNode()->SetHasNonSimpleParameterList(); |
| if (flags & fFncOneArg) |
| { |
| // The parameter of a setter cannot be a rest parameter. |
| Error(ERRUnexpectedEllipsis); |
| } |
| pnodeT = CreateDeclNode(knopVarDecl, pid, STFormal, false); |
| pnodeT->sym->SetIsNonSimpleParameter(true); |
| if (buildAST) |
| { |
| // When only validating formals, we won't have a function node. |
| pnodeFnc->pnodeRest = pnodeT; |
| if (!isNonSimpleParameterList) |
| { |
| // This is the first non-simple parameter we've seen. We need to go back |
| // and set the Symbols of all previous parameters. |
| MapFormalsWithoutRest(m_currentNodeFunc, [&](ParseNodePtr pnodeArg) { pnodeArg->AsParseNodeVar()->sym->SetIsNonSimpleParameter(true); }); |
| } |
| } |
| |
| isNonSimpleParameterList = true; |
| } |
| else |
| { |
| pnodeT = CreateVarDeclNode(pid, STFormal, false, nullptr, false); |
| if (isNonSimpleParameterList) |
| { |
| pnodeT->sym->SetIsNonSimpleParameter(true); |
| } |
| } |
| |
| if (buildAST && pid == wellKnownPropertyPids.arguments) |
| { |
| // This formal parameter overrides the built-in 'arguments' object |
| m_currentNodeFunc->grfpn |= PNodeFlags::fpnArguments_overriddenInParam; |
| } |
| |
| if (fStrictFormals) |
| { |
| UpdateOrCheckForDuplicateInFormals(pid, &formals); |
| } |
| |
| this->GetScanner()->Scan(); |
| |
| if (seenRestParameter && m_token.tk != tkRParen && m_token.tk != tkAsg) |
| { |
| Error(ERRRestLastArg); |
| } |
| |
| if (m_token.tk == tkAsg && m_scriptContext->GetConfig()->IsES6DefaultArgsEnabled()) |
| { |
| if (seenRestParameter && m_scriptContext->GetConfig()->IsES6RestEnabled()) |
| { |
| Error(ERRRestWithDefault); |
| } |
| |
| // In defer parse mode we have to flag the function node to indicate that it has default arguments |
| // so that it will be considered for any syntax error scenario. |
| // Also mark it before parsing the expression as it may contain functions. |
| ParseNodeFnc * currentFncNode = GetCurrentFunctionNode(); |
| if (!currentFncNode->HasDefaultArguments()) |
| { |
| currentFncNode->SetHasDefaultArguments(); |
| currentFncNode->SetHasNonSimpleParameterList(); |
| currentFncNode->firstDefaultArg = argPos; |
| } |
| |
| this->GetScanner()->Scan(); |
| |
| ParseNodePtr pnodeInit; |
| if (isTopLevelDeferredFunc) |
| { |
| // Defer default expressions if the function will be deferred, since we know they can't be evaluated |
| // until the function is fully compiled, and generating code for a function nested inside a deferred function |
| // creates inconsistencies. |
| pnodeInit = ParseExpr<false>(koplCma, nullptr, TRUE, FALSE, pNameHint, &nameHintLength, &nameHintOffset); |
| } |
| else |
| { |
| pnodeInit = ParseExpr<buildAST>(koplCma, nullptr, TRUE, FALSE, pNameHint, &nameHintLength, &nameHintOffset); |
| } |
| |
| if (buildAST && pnodeInit && pnodeInit->nop == knopFncDecl) |
| { |
| Assert(nameHintLength >= nameHintOffset); |
| ParseNodeFnc * pnodeFncInit = pnodeInit->AsParseNodeFnc(); |
| pnodeFncInit->hint = pNameHint; |
| pnodeFncInit->hintLength = nameHintLength; |
| pnodeFncInit->hintOffset = nameHintOffset; |
| } |
| |
| AnalysisAssert(pnodeT); |
| pnodeT->sym->SetIsNonSimpleParameter(true); |
| if (!isNonSimpleParameterList) |
| { |
| if (buildAST) |
| { |
| // This is the first non-simple parameter we've seen. We need to go back |
| // and set the Symbols of all previous parameters. |
| MapFormalsWithoutRest(m_currentNodeFunc, [&](ParseNodePtr pnodeArg) { pnodeArg->AsParseNodeVar()->sym->SetIsNonSimpleParameter(true); }); |
| } |
| |
| // There may be previous parameters that need to be checked for duplicates. |
| isNonSimpleParameterList = true; |
| } |
| |
| if (buildAST) |
| { |
| if (!m_currentNodeFunc->HasDefaultArguments()) |
| { |
| CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, DefaultArgFunction, m_scriptContext); |
| } |
| pnodeT->pnodeInit = pnodeInit; |
| pnodeT->ichLim = this->GetScanner()->IchLimTok(); |
| } |
| } |
| } |
| |
| if (isNonSimpleParameterList && m_currentScope->GetHasDuplicateFormals()) |
| { |
| Error(ERRFormalSame); |
| } |
| |
| if (flags & fFncOneArg) |
| { |
| if (m_token.tk != tkRParen) |
| { |
| Error(ERRSetterMustHaveOneParameter); |
| } |
| break; //enforce only one arg |
| } |
| |
| if (m_token.tk != tkComma) |
| { |
| break; |
| } |
| |
| this->GetScanner()->Scan(); |
| |
| if (m_token.tk == tkRParen && m_scriptContext->GetConfig()->IsES7TrailingCommaEnabled()) |
| { |
| break; |
| } |
| } |
| |
| if (seenRestParameter) |
| { |
| CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, Rest, m_scriptContext); |
| } |
| |
| if (m_token.tk != tkRParen) |
| { |
| Error(ERRnoRparen); |
| } |
| |
| if (this->GetCurrentFunctionNode()->CallsEval() || this->GetCurrentFunctionNode()->ChildCallsEval()) |
| { |
| Assert(pnodeFnc->HasNonSimpleParameterList()); |
| pnodeFnc->ResetBodyAndParamScopeMerged(); |
| } |
| } |
| Assert(m_token.tk == tkRParen); |
| |
| if (fLambda) |
| { |
| this->GetScanner()->SetYieldIsKeywordRegion(fPreviousYieldIsKeyword); |
| this->GetScanner()->SetAwaitIsKeywordRegion(fPreviousAwaitIsKeyword); |
| } |
| } |
| |
| template<bool buildAST> |
| ParseNodePtr Parser::GenerateModuleFunctionWrapper() |
| { |
| ParseNodePtr pnodeFnc = ParseFncDeclNoCheckScope<buildAST>(fFncModule, SuperRestrictionState::Disallowed, nullptr, /* needsPIDOnRCurlyScan */ false, /* fUnaryOrParen */ true); |
| ParseNodePtr callNode = CreateCallNode(knopCall, pnodeFnc, nullptr); |
| |
| return callNode; |
| } |
| |
| template<bool buildAST> |
| ParseNodeFnc * Parser::GenerateEmptyConstructor(bool extends) |
| { |
| ParseNodeFnc * pnodeFnc; |
| |
| // Create the node. |
| pnodeFnc = CreateAllowDeferNodeForOpT<knopFncDecl>(); |
| pnodeFnc->SetNested(NULL != m_currentNodeFunc); |
| pnodeFnc->SetStrictMode(); |
| pnodeFnc->SetDeclaration(TRUE); |
| pnodeFnc->SetIsMethod(TRUE); |
| pnodeFnc->SetIsClassMember(TRUE); |
| pnodeFnc->SetIsClassConstructor(TRUE); |
| pnodeFnc->SetIsBaseClassConstructor(!extends); |
| pnodeFnc->SetHasNonThisStmt(); |
| pnodeFnc->SetIsGeneratedDefault(TRUE); |
| pnodeFnc->SetHasComputedName(); |
| pnodeFnc->SetHasHomeObj(); |
| pnodeFnc->SetHomeObjLocation(Js::Constants::NoRegister); |
| |
| pnodeFnc->ichLim = this->GetScanner()->IchLimTok(); |
| pnodeFnc->ichMin = this->GetScanner()->IchMinTok(); |
| pnodeFnc->cbLim = this->GetScanner()->IecpLimTok(); |
| pnodeFnc->cbMin = this->GetScanner()->IecpMinTok(); |
| pnodeFnc->cbStringMin = pnodeFnc->cbMin; |
| pnodeFnc->lineNumber = this->GetScanner()->LineCur(); |
| |
| pnodeFnc->functionId = (*m_nextFunctionId); |
| |
| // In order to (re-)defer the default constructor, we need to, for instance, track |
| // deferred class expression the way we track function expression, since we lose the part of the source |
| // that tells us which we have. |
| Assert(!pnodeFnc->canBeDeferred); |
| |
| #ifdef DBG |
| pnodeFnc->deferredParseNextFunctionId = *(this->m_nextFunctionId); |
| #endif |
| |
| AppendFunctionToScopeList(true, pnodeFnc); |
| |
| if (m_nextFunctionId) |
| { |
| (*m_nextFunctionId)++; |
| } |
| |
| // Update the count of functions nested in the current parent. |
| if (m_pnestedCount) |
| { |
| (*m_pnestedCount)++; |
| } |
| |
| if (this->GetScanner()->IchMinTok() >= this->GetScanner()->IchMinLine()) |
| { |
| // In scenarios involving defer parse IchMinLine() can be incorrect for the first line after defer parse |
| pnodeFnc->columnNumber = this->GetScanner()->IchMinTok() - this->GetScanner()->IchMinLine(); |
| } |
| else if (m_currentNodeFunc) |
| { |
| // For the first line after defer parse, compute the column relative to the column number |
| // of the lexically parent function. |
| ULONG offsetFromCurrentFunction = this->GetScanner()->IchMinTok() - m_currentNodeFunc->ichMin; |
| pnodeFnc->columnNumber = m_currentNodeFunc->columnNumber + offsetFromCurrentFunction; |
| } |
| else |
| { |
| // if there is no current function, lets give a default of 0. |
| pnodeFnc->columnNumber = 0; |
| } |
| |
| int32 * pAstSizeSave = m_pCurrentAstSize; |
| m_pCurrentAstSize = &(pnodeFnc->astSize); |
| |
| // Make this the current function. |
| ParseNodeFnc * pnodeFncSave = m_currentNodeFunc; |
| m_currentNodeFunc = pnodeFnc; |
| |
| ParseNodeName * argsId = nullptr; |
| ParseNodePtr *lastNodeRef = nullptr; |
| ParseNodeBlock * pnodeBlock = StartParseBlock<buildAST>(PnodeBlockType::Parameter, ScopeType_Parameter); |
| |
| if (buildAST && extends) |
| { |
| // constructor(...args) { super(...args); } |
| // ^^^^^^^ |
| ParseNodePtr *const ppnodeVarSave = m_ppnodeVar; |
| m_ppnodeVar = &pnodeFnc->pnodeVars; |
| |
| IdentPtr pidargs = this->GetHashTbl()->PidHashNameLen(_u("args"), sizeof("args") - 1); |
| ParseNodeVar * pnodeT = CreateVarDeclNode(pidargs, STFormal); |
| pnodeT->sym->SetIsNonSimpleParameter(true); |
| pnodeFnc->pnodeRest = pnodeT; |
| PidRefStack *ref = this->PushPidRef(pidargs); |
| |
| argsId = CreateNameNode(pidargs, ref, pnodeFnc->ichMin, pnodeFnc->ichLim); |
| m_ppnodeVar = ppnodeVarSave; |
| } |
| |
| ParseNodeBlock * pnodeInnerBlock = StartParseBlock<buildAST>(PnodeBlockType::Function, ScopeType_FunctionBody); |
| pnodeBlock->pnodeScopes = pnodeInnerBlock; |
| pnodeFnc->pnodeBodyScope = pnodeInnerBlock; |
| pnodeFnc->pnodeScopes = pnodeBlock; |
| |
| if (buildAST) |
| { |
| if (extends) |
| { |
| // constructor(...args) { super(...args); } |
| // ^^^^^^^^^^^^^^^ |
| Assert(argsId); |
| ParseNodeUni * spreadArg = CreateUniNode(knopEllipsis, argsId, pnodeFnc->ichMin, pnodeFnc->ichLim); |
| ParseNodeSpecialName * superRef = ReferenceSpecialName(wellKnownPropertyPids._superConstructor, pnodeFnc->ichMin, pnodeFnc->ichLim, true); |
| pnodeFnc->SetHasSuperReference(TRUE); |
| ParseNodeSuperCall * callNode = CreateSuperCallNode(superRef, spreadArg); |
| |
| callNode->pnodeThis = ReferenceSpecialName(wellKnownPropertyPids._this, pnodeFnc->ichMin, pnodeFnc->ichLim, true); |
| callNode->pnodeNewTarget = ReferenceSpecialName(wellKnownPropertyPids._newTarget, pnodeFnc->ichMin, pnodeFnc->ichLim, true); |
| callNode->spreadArgCount = 1; |
| AddToNodeList(&pnodeFnc->pnodeBody, &lastNodeRef, callNode); |
| } |
| |
| AddToNodeList(&pnodeFnc->pnodeBody, &lastNodeRef, CreateNodeForOpT<knopEndCode>()); |
| } |
| |
| FinishParseBlock(pnodeInnerBlock); |
| |
| CreateSpecialSymbolDeclarations(pnodeFnc); |
| |
| FinishParseBlock(pnodeBlock); |
| |
| m_currentNodeFunc = pnodeFncSave; |
| m_pCurrentAstSize = pAstSizeSave; |
| |
| return pnodeFnc; |
| } |
| |
| template<bool buildAST> |
| void Parser::ParseExpressionLambdaBody(ParseNodeFnc * pnodeLambda, bool fAllowIn) |
| { |
| ParseNodePtr *lastNodeRef = nullptr; |
| |
| // The lambda body is a single expression, the result of which is the return value. |
| ParseNodeReturn * pnodeRet = nullptr; |
| |
| if (buildAST) |
| { |
| pnodeRet = CreateNodeForOpT<knopReturn>(); |
| pnodeRet->grfpn |= PNodeFlags::fpnSyntheticNode; |
| pnodeLambda->pnodeScopes->pnodeStmt = pnodeRet; |
| } |
| |
| IdentToken token; |
| charcount_t lastRParen = 0; |
| |
| // We need to disable deferred parse mode in the scanner because the lambda body doesn't end with a right paren. |
| // The scanner needs to create a pid in the case of a string constant token immediately following the lambda body expression. |
| // Otherwise, we'll save null for the string constant pid which will AV during ByteCode generation. |
| BYTE fScanDeferredFlagsSave = this->GetScanner()->SetDeferredParse(FALSE); |
| ParseNodePtr result = ParseExpr<buildAST>(koplAsg, nullptr, fAllowIn, FALSE, nullptr, nullptr, nullptr, &token, false, nullptr, &lastRParen); |
| this->GetScanner()->SetDeferredParseFlags(fScanDeferredFlagsSave); |
| |
| this->MarkEscapingRef(result, &token); |
| |
| if (buildAST) |
| { |
| pnodeRet->pnodeExpr = result; |
| |
| pnodeRet->ichMin = pnodeRet->pnodeExpr->ichMin; |
| pnodeRet->ichLim = pnodeRet->pnodeExpr->ichLim; |
| |
| // Pushing a statement node with PushStmt<>() normally does this initialization |
| // but do it here manually since we know there is no outer statement node. |
| pnodeRet->grfnop = 0; |
| pnodeRet->pnodeOuter = nullptr; |
| |
| pnodeLambda->ichLim = max(pnodeRet->ichLim, lastRParen); |
| pnodeLambda->cbLim = this->GetScanner()->IecpLimTokPrevious(); |
| pnodeLambda->pnodeScopes->ichLim = pnodeRet->ichLim; |
| |
| pnodeLambda->pnodeBody = nullptr; |
| AddToNodeList(&pnodeLambda->pnodeBody, &lastNodeRef, pnodeRet); |
| |
| // Append an EndCode node. |
| ParseNodePtr end = CreateNodeForOpT<knopEndCode>(pnodeRet->ichLim); |
| end->ichLim = end->ichMin; // make end code zero width at the immediate end of lambda body |
| AddToNodeList(&pnodeLambda->pnodeBody, &lastNodeRef, end); |
| |
| // Lambda's do not have arguments binding |
| pnodeLambda->SetHasReferenceableBuiltInArguments(false); |
| } |
| else |
| { |
| pnodeLambda->ichLim = max(this->GetScanner()->IchLimTokPrevious(), lastRParen); |
| pnodeLambda->cbLim = this->GetScanner()->IecpLimTokPrevious(); |
| } |
| } |
| |
| void Parser::CheckStrictFormalParameters() |
| { |
| if (m_token.tk == tkID) |
| { |
| // single parameter arrow function case |
| IdentPtr pid = m_token.GetIdentifier(this->GetHashTbl()); |
| CheckStrictModeEvalArgumentsUsage(pid); |
| return; |
| } |
| |
| Assert(m_token.tk == tkLParen); |
| this->GetScanner()->ScanForcingPid(); |
| |
| if (m_token.tk != tkRParen) |
| { |
| SList<IdentPtr> formals(&m_nodeAllocator); |
| for (;;) |
| { |
| if (m_token.tk != tkID) |
| { |
| IdentifierExpectedError(m_token); |
| } |
| |
| IdentPtr pid = m_token.GetIdentifier(this->GetHashTbl()); |
| CheckStrictModeEvalArgumentsUsage(pid); |
| if (formals.Has(pid)) |
| { |
| Error(ERRES5ArgSame, this->GetScanner()->IchMinTok(), this->GetScanner()->IchLimTok()); |
| } |
| else |
| { |
| formals.Prepend(pid); |
| } |
| |
| this->GetScanner()->Scan(); |
| |
| if (m_token.tk == tkAsg && m_scriptContext->GetConfig()->IsES6DefaultArgsEnabled()) |
| { |
| this->GetScanner()->Scan(); |
| // We can avoid building the AST since we are just checking the default expression. |
| ParseNodePtr pnodeInit = ParseExpr<false>(koplCma); |
| Assert(pnodeInit == nullptr); |
| } |
| |
| if (m_token.tk != tkComma) |
| { |
| break; |
| } |
| this->GetScanner()->ScanForcingPid(); |
| |
| if (m_token.tk == tkRParen && m_scriptContext->GetConfig()->IsES7TrailingCommaEnabled()) |
| { |
| break; |
| } |
| } |
| } |
| Assert(m_token.tk == tkRParen); |
| } |
| |
| void Parser::FinishFncNode(ParseNodeFnc * pnodeFnc, bool fAllowIn) |
| { |
| AnalysisAssert(pnodeFnc); |
| |
| // Finish the AST for a function that was deferred earlier, but which we decided |
| // to finish after the fact. |
| // We assume that the name(s) and arg(s) have already got parse nodes, so |
| // we just have to do the function body. |
| |
| // Save the current next function Id, and resume from the old one. |
| Js::LocalFunctionId * nextFunctionIdSave = m_nextFunctionId; |
| Js::LocalFunctionId tempNextFunctionId = pnodeFnc->functionId + 1; |
| this->m_nextFunctionId = &tempNextFunctionId; |
| |
| ParseNodeFnc * pnodeFncSave = m_currentNodeFunc; |
| uint *pnestedCountSave = m_pnestedCount; |
| int32* pAstSizeSave = m_pCurrentAstSize; |
| |
| m_currentNodeFunc = pnodeFnc; |
| m_pCurrentAstSize = &(pnodeFnc->astSize); |
| |
| pnodeFnc->nestedCount = 0; |
| m_pnestedCount = &pnodeFnc->nestedCount; |
| |
| bool fLambda = pnodeFnc->IsLambda(); |
| bool fMethod = pnodeFnc->IsMethod(); |
| |
| // Cue up the parser to the start of the function body. |
| if (pnodeFnc->pnodeName) |
| { |
| // Skip the name(s). |
| this->GetScanner()->SetCurrentCharacter(pnodeFnc->pnodeName->ichLim, pnodeFnc->lineNumber); |
| } |
| else |
| { |
| this->GetScanner()->SetCurrentCharacter(pnodeFnc->ichMin, pnodeFnc->lineNumber); |
| |
| if (fMethod) |
| { |
| // Method. Skip identifier name, computed property name, "async", "get", "set", and '*' or '(' characters. |
| for (;;) |
| { |
| this->GetScanner()->Scan(); |
| // '[' character indicates a computed property name for this method. We should consume it. |
| if (m_token.tk == tkLBrack) |
| { |
| // We don't care what the name expr is. |
| this->GetScanner()->Scan(); |
| ParseExpr<false>(); |
| Assert(m_token.tk == tkRBrack); |
| continue; |
| } |
| // Quit scanning ahead when we reach a '(' character which opens the arg list. |
| if (m_token.tk == tkLParen) |
| { |
| break; |
| } |
| } |
| } |
| else if (pnodeFnc->IsAccessor()) |
| { |
| // Getter/setter. The node text starts with the name, so eat that. |
| this->GetScanner()->ScanNoKeywords(); |
| } |
| else if (!fLambda) |
| { |
| // Anonymous function. Skip "async", "function", and '(' or '*' characters. |
| for (;;) |
| { |
| this->GetScanner()->Scan(); |
| if (m_token.GetIdentifier(this->GetHashTbl()) == wellKnownPropertyPids.async) |
| { |
| Assert(pnodeFnc->IsAsync()); |
| continue; |
| } |
| // Quit scanning ahead when we reach a 'function' keyword which precedes the arg list. |
| if (m_token.tk == tkFUNCTION) |
| { |
| break; |
| } |
| Assert(m_token.tk == tkLParen || m_token.tk == tkStar); |
| } |
| } |
| } |
| |
| // switch scanner to treat 'yield' as keyword in generator functions |
| // or as an identifier in non-generator functions |
| bool fPreviousYieldIsKeyword = this->GetScanner()->SetYieldIsKeywordRegion(pnodeFnc && pnodeFnc->IsGenerator()); |
| bool fPreviousAwaitIsKeyword = this->GetScanner()->SetAwaitIsKeywordRegion(pnodeFnc && pnodeFnc->IsAsync()); |
| |
| // Skip the arg list. |
| if (!fMethod) |
| { |
| // If this is a method, we've already advanced to the '(' token. |
| this->GetScanner()->Scan(); |
| } |
| if (m_token.tk == tkStar) |
| { |
| Assert(pnodeFnc->IsGenerator()); |
| this->GetScanner()->ScanNoKeywords(); |
| } |
| if (fLambda && m_token.tk == tkID && m_token.GetIdentifier(this->GetHashTbl()) == wellKnownPropertyPids.async) |
| { |
| Assert(pnodeFnc->IsAsync()); |
| this->GetScanner()->ScanNoKeywords(); |
| } |
| Assert(m_token.tk == tkLParen || (fLambda && m_token.tk == tkID)); |
| this->GetScanner()->ScanNoKeywords(); |
| |
| if (m_token.tk != tkRParen && m_token.tk != tkDArrow) |
| { |
| for (;;) |
| { |
| if (m_token.tk == tkEllipsis) |
| { |
| this->GetScanner()->ScanNoKeywords(); |
| } |
| |
| if (m_token.tk == tkID) |
| { |
| this->GetScanner()->ScanNoKeywords(); |
| |
| if (m_token.tk == tkAsg) |
| { |
| // Eat the default expression |
| this->GetScanner()->Scan(); |
| ParseExpr<false>(koplCma); |
| } |
| } |
| else if (IsPossiblePatternStart()) |
| { |
| ParseDestructuredLiteralWithScopeSave(tkLET, false/*isDecl*/, false /*topLevel*/); |
| } |
| else |
| { |
| AssertMsg(false, "Unexpected identifier prefix while fast-scanning formals"); |
| } |
| |
| if (m_token.tk != tkComma) |
| { |
| break; |
| } |
| this->GetScanner()->ScanNoKeywords(); |
| |
| if (m_token.tk == tkRParen && m_scriptContext->GetConfig()->IsES7TrailingCommaEnabled()) |
| { |
| break; |
| } |
| } |
| } |
| |
| if (m_token.tk == tkRParen) |
| { |
| this->GetScanner()->Scan(); |
| } |
| if (fLambda && m_token.tk == tkDArrow) |
| { |
| this->GetScanner()->Scan(); |
| } |
| |
| // Finish the function body. |
| { |
| // Note that in IE8- modes, surrounding parentheses are considered part of function body. e.g. "( function x(){} )". |
| // We lose that context here since we start from middle of function body. So save and restore source range info. |
| const charcount_t ichLim = pnodeFnc->ichLim; |
| const size_t cbLim = pnodeFnc->cbLim; |
| |
| this->FinishFncDecl(pnodeFnc, NULL, fLambda, /* skipCurlyBraces */ false, fAllowIn); |
| |
| #if DBG |
| // The pnode extent may not match the original extent. |
| // We expect this to happen only when there are trailing ")"'s. |
| // Consume them and make sure that's all we've got. |
| if (pnodeFnc->ichLim != ichLim) |
| { |
| Assert(pnodeFnc->ichLim < ichLim); |
| this->GetScanner()->SetCurrentCharacter(pnodeFnc->ichLim); |
| while (this->GetScanner()->IchLimTok() != ichLim) |
| { |
| this->GetScanner()->ScanNoKeywords(); |
| Assert(m_token.tk == tkRParen); |
| } |
| } |
| #endif |
| pnodeFnc->ichLim = ichLim; |
| pnodeFnc->cbLim = cbLim; |
| } |
| |
| m_currentNodeFunc = pnodeFncSave; |
| m_pCurrentAstSize = pAstSizeSave; |
| m_pnestedCount = pnestedCountSave; |
| Assert(m_pnestedCount); |
| |
| Assert(tempNextFunctionId == pnodeFnc->deferredParseNextFunctionId); |
| this->m_nextFunctionId = nextFunctionIdSave; |
| |
| this->GetScanner()->SetYieldIsKeywordRegion(fPreviousYieldIsKeyword); |
| this->GetScanner()->SetAwaitIsKeywordRegion(fPreviousAwaitIsKeyword); |
| } |
| |
| void Parser::FinishFncDecl(ParseNodeFnc * pnodeFnc, LPCOLESTR pNameHint, bool fLambda, bool skipCurlyBraces, bool fAllowIn) |
| { |
| LPCOLESTR name = NULL; |
| JS_ETW(int32 startAstSize = *m_pCurrentAstSize); |
| if (IS_JS_ETW(EventEnabledJSCRIPT_PARSE_METHOD_START()) || PHASE_TRACE1(Js::DeferParsePhase)) |
| { |
| name = GetFunctionName(pnodeFnc, pNameHint); |
| m_functionBody = NULL; // for nested functions we do not want to get the name of the top deferred function return name; |
| JS_ETW(EventWriteJSCRIPT_PARSE_METHOD_START(m_sourceContextInfo->dwHostSourceContext, GetScriptContext(), pnodeFnc->functionId, 0, m_parseType, name)); |
| OUTPUT_TRACE(Js::DeferParsePhase, _u("Parsing function (%s) : %s (%d)\n"), GetParseType(), name, pnodeFnc->functionId); |
| } |
| |
| JS_ETW_INTERNAL(EventWriteJSCRIPT_PARSE_FUNC(GetScriptContext(), pnodeFnc->functionId, /*Undefer*/FALSE)); |
| |
| |
| // Do the work of creating an AST for a function body. |
| // This is common to the un-deferred case and the case in which we un-defer late in the game. |
| |
| Assert(pnodeFnc->nop == knopFncDecl); |
| |
| if (fLambda && m_token.tk != tkLCurly) |
| { |
| ParseExpressionLambdaBody<true>(pnodeFnc, fAllowIn); |
| } |
| else |
| { |
| if (!skipCurlyBraces) |
| { |
| ChkCurTok(tkLCurly, ERRnoLcurly); |
| } |
| |
| ParseNodePtr * lastNodeRef = nullptr; |
| ParseStmtList<true>(&pnodeFnc->pnodeBody, &lastNodeRef, SM_OnFunctionCode, true /* isSourceElementList */); |
| // Append an EndCode node. |
| AddToNodeList(&pnodeFnc->pnodeBody, &lastNodeRef, CreateNodeForOpT<knopEndCode>()); |
| |
| if (!skipCurlyBraces) |
| { |
| ChkCurTokNoScan(tkRCurly, ERRnoRcurly); |
| } |
| |
| pnodeFnc->ichLim = this->GetScanner()->IchLimTok(); |
| pnodeFnc->cbLim = this->GetScanner()->IecpLimTok(); |
| } |
| |
| #ifdef ENABLE_JS_ETW |
| int32 astSize = *m_pCurrentAstSize - startAstSize; |
| EventWriteJSCRIPT_PARSE_METHOD_STOP(m_sourceContextInfo->dwHostSourceContext, GetScriptContext(), pnodeFnc->functionId, astSize, m_parseType, name); |
| #endif |
| } |
| |
| ParseNodeVar * Parser::CreateSpecialVarDeclNode(ParseNodeFnc * pnodeFnc, IdentPtr pid) |
| { |
| ParseNodeVar * pnode = InsertVarAtBeginning(pnodeFnc, pid); |
| |
| pnode->grfpn |= fpnSpecialSymbol; |
| // special symbol must not be global |
| pnode->sym->SetIsGlobal(false); |
| |
| return pnode; |
| } |
| |
| ParseNodeVar * Parser::InsertVarAtBeginning(ParseNodeFnc * pnodeFnc, IdentPtr pid) |
| { |
| ParseNodeVar * pnode = nullptr; |
| |
| if (m_ppnodeVar == &pnodeFnc->pnodeVars) |
| { |
| pnode = CreateVarDeclNode(pid, STVariable, true, pnodeFnc); |
| } |
| else |
| { |
| ParseNodePtr * const ppnodeVarSave = m_ppnodeVar; |
| m_ppnodeVar = &pnodeFnc->pnodeVars; |
| pnode = CreateVarDeclNode(pid, STVariable, true, pnodeFnc); |
| m_ppnodeVar = ppnodeVarSave; |
| } |
| |
| Assert(pnode); |
| return pnode; |
| } |
| |
| ParseNodeVar * Parser::AddArgumentsNodeToVars(ParseNodeFnc * pnodeFnc) |
| { |
| Assert(!GetCurrentFunctionNode()->IsLambda()); |
| |
| ParseNodeVar * argNode = InsertVarAtBeginning(pnodeFnc, wellKnownPropertyPids.arguments); |
| |
| argNode->grfpn |= PNodeFlags::fpnArguments; // Flag this as the built-in arguments node |
| |
| return argNode; |
| } |
| |
| void Parser::UpdateArgumentsNode(ParseNodeFnc * pnodeFnc, ParseNodeVar * argNode) |
| { |
| if ((pnodeFnc->grfpn & PNodeFlags::fpnArguments_overriddenInParam) || pnodeFnc->IsLambda()) |
| { |
| // There is a parameter named arguments. So we don't have to create the built-in arguments. |
| pnodeFnc->SetHasReferenceableBuiltInArguments(false); |
| } |
| else if ((pnodeFnc->grfpn & PNodeFlags::fpnArguments_overriddenByDecl) && pnodeFnc->IsBodyAndParamScopeMerged()) |
| { |
| // In non-split scope case there is a var or function definition named arguments in the body |
| pnodeFnc->SetHasReferenceableBuiltInArguments(false); |
| } |
| else |
| { |
| pnodeFnc->SetHasReferenceableBuiltInArguments(true); |
| Assert(argNode); |
| } |
| |
| if (argNode != nullptr && !argNode->sym->IsArguments()) |
| { |
| // A duplicate definition has updated the declaration node. Need to reset it back. |
| argNode->grfpn |= PNodeFlags::fpnArguments; |
| argNode->sym->SetDecl(argNode); |
| } |
| } |
| |
| LPCOLESTR Parser::GetFunctionName(ParseNodeFnc * pnodeFnc, LPCOLESTR pNameHint) |
| { |
| LPCOLESTR name = nullptr; |
| if (pnodeFnc->pnodeName != nullptr) |
| { |
| Assert(pnodeFnc->pnodeName->nop == knopVarDecl); |
| name = pnodeFnc->pnodeName->pid->Psz(); |
| } |
| if (name == nullptr && pNameHint != nullptr) |
| { |
| name = pNameHint; |
| } |
| if (name == nullptr && (pnodeFnc->IsLambda() || |
| (!pnodeFnc->IsDeclaration() && !pnodeFnc->IsMethod()))) |
| { |
| name = Js::Constants::AnonymousFunction; |
| } |
| if (name == nullptr && m_functionBody != nullptr) |
| { |
| name = m_functionBody->GetExternalDisplayName(); |
| } |
| else if (name == nullptr) |
| { |
| name = Js::Constants::AnonymousFunction; |
| } |
| return name; |
| } |
| |
| IdentPtr Parser::ParseClassPropertyName(IdentPtr * pidHint) |
| { |
| if (m_token.tk == tkID || m_token.tk == tkStrCon || m_token.IsReservedWord()) |
| { |
| IdentPtr pid; |
| if (m_token.tk == tkStrCon) |
| { |
| if (this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) |
| { |
| Error(ERRES5NoOctal); |
| } |
| |
| pid = m_token.GetStr(); |
| } |
| else |
| { |
| pid = m_token.GetIdentifier(this->GetHashTbl()); |
| } |
| *pidHint = pid; |
| return pid; |
| } |
| else if (m_token.tk == tkIntCon) |
| { |
| if (this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) |
| { |
| Error(ERRES5NoOctal); |
| } |
| |
| return this->GetScanner()->PidFromLong(m_token.GetLong()); |
| } |
| else if (m_token.tk == tkFltCon) |
| { |
| if (this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) |
| { |
| Error(ERRES5NoOctal); |
| } |
| |
| return this->GetScanner()->PidFromDbl(m_token.GetDouble()); |
| } |
| |
| Error(ERRnoMemberIdent); |
| } |
| |
| LPCOLESTR Parser::ConstructFinalHintNode(IdentPtr pClassName, IdentPtr pMemberName, IdentPtr pGetSet, bool isStatic, uint32* nameLength, uint32* pShortNameOffset, bool isComputedName, LPCOLESTR pMemberNameHint) |
| { |
| if ((pMemberName == nullptr && !isComputedName) || |
| (pMemberNameHint == nullptr && isComputedName) || |
| !CONFIG_FLAG(UseFullName)) |
| { |
| return nullptr; |
| } |
| |
| LPCOLESTR pFinalName = isComputedName ? pMemberNameHint : pMemberName->Psz(); |
| uint32 fullNameHintLength = (uint32)wcslen(pFinalName); |
| uint32 shortNameOffset = 0; |
| if (!isStatic) |
| { |
| // Add prototype. |
| pFinalName = AppendNameHints(wellKnownPropertyPids.prototype, pFinalName, &fullNameHintLength, &shortNameOffset); |
| } |
| |
| if (pClassName) |
| { |
| uint32 classNameOffset = 0; |
| pFinalName = AppendNameHints(pClassName, pFinalName, &fullNameHintLength, &classNameOffset); |
| shortNameOffset += classNameOffset; |
| } |
| |
| if (pGetSet) |
| { |
| // displays as get/set prototype.funcname |
| uint32 getSetOffset = 0; |
| pFinalName = AppendNameHints(pGetSet, pFinalName, &fullNameHintLength, &getSetOffset, true); |
| shortNameOffset += getSetOffset; |
| } |
| |
| *nameLength = fullNameHintLength; |
| *pShortNameOffset = shortNameOffset; |
| |
| return pFinalName; |
| } |
| |
| template<bool buildAST> |
| ParseNodeClass * Parser::ParseClassDecl(BOOL isDeclaration, LPCOLESTR pNameHint, uint32 *pHintLength, uint32 *pShortNameOffset) |
| { |
| bool hasConstructor = false; |
| bool hasExtends = false; |
| IdentPtr name = nullptr; |
| ParseNodeVar * pnodeName = nullptr; |
| ParseNodeFnc * pnodeConstructor = nullptr; |
| ParseNodePtr pnodeExtends = nullptr; |
| ParseNodePtr pnodeMembers = nullptr; |
| ParseNodePtr *lastMemberNodeRef = nullptr; |
| ParseNodePtr pnodeStaticMembers = nullptr; |
| ParseNodePtr *lastStaticMemberNodeRef = nullptr; |
| uint32 nameHintLength = pHintLength ? *pHintLength : 0; |
| uint32 nameHintOffset = pShortNameOffset ? *pShortNameOffset : 0; |
| |
| ArenaAllocator tempAllocator(_u("ClassMemberNames"), m_nodeAllocator.GetPageAllocator(), Parser::OutOfMemory); |
| |
| size_t cbMinConstructor = 0; |
| ParseNodeClass * pnodeClass = nullptr; |
| if (buildAST) |
| { |
| pnodeClass = CreateNodeForOpT<knopClassDecl>(); |
| |
| CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, Class, m_scriptContext); |
| |
| cbMinConstructor = this->GetScanner()->IecpMinTok(); |
| } |
| |
| this->GetScanner()->Scan(); |
| if (m_token.tk == tkID) |
| { |
| name = m_token.GetIdentifier(this->GetHashTbl()); |
| this->GetScanner()->Scan(); |
| } |
| else if (isDeclaration) |
| { |
| IdentifierExpectedError(m_token); |
| } |
| |
| if (isDeclaration && name == wellKnownPropertyPids.arguments && GetCurrentBlockInfo()->pnodeBlock->blockType == Function) |
| { |
| GetCurrentFunctionNode()->grfpn |= PNodeFlags::fpnArguments_overriddenByDecl; |
| } |
| |
| BOOL strictSave = m_fUseStrictMode; |
| m_fUseStrictMode = TRUE; |
| |
| ParseNodeVar * pnodeDeclName = nullptr; |
| if (isDeclaration) |
| { |
| pnodeDeclName = CreateBlockScopedDeclNode(name, knopLetDecl); |
| } |
| |
| ParseNodePtr *ppnodeScopeSave = nullptr; |
| ParseNodePtr *ppnodeExprScopeSave = nullptr; |
| |
| ParseNodeBlock * pnodeBlock = StartParseBlock<buildAST>(PnodeBlockType::Regular, ScopeType_Block); |
| if (buildAST) |
| { |
| PushFuncBlockScope(pnodeBlock, &ppnodeScopeSave, &ppnodeExprScopeSave); |
| pnodeClass->pnodeBlock = pnodeBlock; |
| } |
| |
| if (name) |
| { |
| pnodeName = CreateBlockScopedDeclNode(name, knopConstDecl); |
| } |
| |
| if (m_token.tk == tkEXTENDS) |
| { |
| this->GetScanner()->Scan(); |
| pnodeExtends = ParseTerm<buildAST>(); |
| hasExtends = true; |
| } |
| |
| if (m_token.tk != tkLCurly) |
| { |
| Error(ERRnoLcurly); |
| } |
| |
| OUTPUT_TRACE_DEBUGONLY(Js::ES6VerboseFlag, _u("Parsing class (%s) : %s\n"), GetParseType(), name ? name->Psz() : _u("anonymous class")); |
| |
| RestorePoint beginClass; |
| this->GetScanner()->Capture(&beginClass); |
| |
| this->GetScanner()->ScanForcingPid(); |
| |
| IdentPtr pClassNamePid = pnodeName ? pnodeName->pid : nullptr; |
| |
| for (;;) |
| { |
| if (m_token.tk == tkSColon) |
| { |
| this->GetScanner()->ScanForcingPid(); |
| continue; |
| } |
| if (m_token.tk == tkRCurly) |
| { |
| break; |
| } |
| |
| bool isStatic = false; |
| if (m_token.tk == tkSTATIC) |
| { |
| // 'static' can be used as an IdentifierName here, even in strict mode code. We need to see the next token before we know |
| // if this is being used as a keyword. This is similar to the way we treat 'let' in some cases. |
| // See https://tc39.github.io/ecma262/#sec-keywords for more info. |
| |
| RestorePoint beginStatic; |
| this->GetScanner()->Capture(&beginStatic); |
| |
| this->GetScanner()->ScanForcingPid(); |
| |
| if (m_token.tk == tkLParen) |
| { |
| this->GetScanner()->SeekTo(beginStatic); |
| } |
| else |
| { |
| isStatic = true; |
| } |
| } |
| |
| ushort fncDeclFlags = fFncNoName | fFncMethod | fFncClassMember; |
| charcount_t ichMin = this->GetScanner()->IchMinTok(); |
| size_t iecpMin = this->GetScanner()->IecpMinTok(); |
| ParseNodePtr pnodeMemberName = nullptr; |
| IdentPtr pidHint = nullptr; |
| IdentPtr memberPid = nullptr; |
| LPCOLESTR pMemberNameHint = nullptr; |
| uint32 memberNameHintLength = 0; |
| uint32 memberNameOffset = 0; |
| bool isComputedName = false; |
| bool isAsyncMethod = false; |
| |
| if (m_token.tk == tkID && m_token.GetIdentifier(this->GetHashTbl()) == wellKnownPropertyPids.async && m_scriptContext->GetConfig()->IsES7AsyncAndAwaitEnabled()) |
| { |
| RestorePoint parsedAsync; |
| this->GetScanner()->Capture(&parsedAsync); |
| ichMin = this->GetScanner()->IchMinTok(); |
| iecpMin = this->GetScanner()->IecpMinTok(); |
| |
| this->GetScanner()->Scan(); |
| if (m_token.tk == tkLParen || this->GetScanner()->FHadNewLine()) |
| { |
| this->GetScanner()->SeekTo(parsedAsync); |
| } |
| else |
| { |
| isAsyncMethod = true; |
| } |
| } |
| |
| bool isGenerator = m_scriptContext->GetConfig()->IsES6GeneratorsEnabled() && |
| m_token.tk == tkStar; |
| if (isGenerator) |
| { |
| fncDeclFlags |= fFncGenerator; |
| this->GetScanner()->ScanForcingPid(); |
| } |
| |
| |
| if (m_token.tk == tkLBrack && m_scriptContext->GetConfig()->IsES6ObjectLiteralsEnabled()) |
| { |
| // Computed member name: [expr] () { } |
| LPCOLESTR emptyHint = nullptr; |
| ParseComputedName<buildAST>(&pnodeMemberName, &emptyHint, &pMemberNameHint, &memberNameHintLength, &memberNameOffset); |
| isComputedName = true; |
| } |
| else // not computed name |
| { |
| memberPid = this->ParseClassPropertyName(&pidHint); |
| if (pidHint) |
| { |
| pMemberNameHint = pidHint->Psz(); |
| memberNameHintLength = pidHint->Cch(); |
| } |
| } |
| |
| if (buildAST && memberPid) |
| { |
| pnodeMemberName = CreateStrNode(memberPid); |
| } |
| |
| if (!isStatic && memberPid == wellKnownPropertyPids.constructor) |
| { |
| if (hasConstructor || isAsyncMethod) |
| { |
| Error(ERRsyntax); |
| } |
| hasConstructor = true; |
| LPCOLESTR pConstructorName = nullptr; |
| uint32 constructorNameLength = 0; |
| uint32 constructorShortNameHintOffset = 0; |
| if (pnodeName && pnodeName->pid) |
| { |
| pConstructorName = pnodeName->pid->Psz(); |
| constructorNameLength = pnodeName->pid->Cch(); |
| } |
| else |
| { |
| pConstructorName = pNameHint; |
| constructorNameLength = nameHintLength; |
| constructorShortNameHintOffset = nameHintOffset; |
| } |
| |
| { |
| SuperRestrictionState::State state = hasExtends ? SuperRestrictionState::CallAndPropertyAllowed : SuperRestrictionState::PropertyAllowed; |
| |
| // Add the class constructor flag and base class constructor flag if pnodeExtends is nullptr |
| fncDeclFlags |= fFncClassConstructor | (hasExtends ? kFunctionNone : fFncBaseClassConstructor); |
| pnodeConstructor = ParseFncDeclNoCheckScope<buildAST>(fncDeclFlags, state, pConstructorName, /* needsPIDOnRCurlyScan */ true); |
| } |
| |
| if (pnodeConstructor->IsGenerator()) |
| { |
| Error(ERRConstructorCannotBeGenerator); |
| } |
| |
| Assert(constructorNameLength >= constructorShortNameHintOffset); |
| // The constructor function will get the same name as class. |
| pnodeConstructor->hint = pConstructorName; |
| pnodeConstructor->hintLength = constructorNameLength; |
| pnodeConstructor->hintOffset = constructorShortNameHintOffset; |
| pnodeConstructor->pid = pnodeName && pnodeName->pid ? pnodeName->pid : wellKnownPropertyPids.constructor; |
| pnodeConstructor->SetHasNonThisStmt(); |
| pnodeConstructor->SetHasComputedName(); |
| pnodeConstructor->SetHasHomeObj(); |
| } |
| else |
| { |
| ParseNodePtr pnodeMember = nullptr; |
| |
| bool isMemberNamedGetOrSet = false; |
| RestorePoint beginMethodName; |
| this->GetScanner()->Capture(&beginMethodName); |
| if (memberPid == wellKnownPropertyPids.get || memberPid == wellKnownPropertyPids.set) |
| { |
| this->GetScanner()->ScanForcingPid(); |
| } |
| if (m_token.tk == tkLParen) |
| { |
| this->GetScanner()->SeekTo(beginMethodName); |
| isMemberNamedGetOrSet = true; |
| } |
| |
| if ((memberPid == wellKnownPropertyPids.get || memberPid == wellKnownPropertyPids.set) && !isMemberNamedGetOrSet) |
| { |
| bool isGetter = (memberPid == wellKnownPropertyPids.get); |
| |
| if (m_token.tk == tkLBrack && m_scriptContext->GetConfig()->IsES6ObjectLiteralsEnabled()) |
| { |
| // Computed get/set member name: get|set [expr] () { } |
| LPCOLESTR emptyHint = nullptr; |
| ParseComputedName<buildAST>(&pnodeMemberName, &emptyHint, &pMemberNameHint, &memberNameHintLength, &memberNameOffset); |
| isComputedName = true; |
| } |
| else // not computed name |
| { |
| memberPid = this->ParseClassPropertyName(&pidHint); |
| } |
| |
| if ((isStatic ? (memberPid == wellKnownPropertyPids.prototype) : (memberPid == wellKnownPropertyPids.constructor)) || isAsyncMethod) |
| { |
| Error(ERRsyntax); |
| } |
| if (buildAST && memberPid && !isComputedName) |
| { |
| pnodeMemberName = CreateStrNode(memberPid); |
| } |
| |
| ParseNodeFnc * pnodeFnc = nullptr; |
| { |
| pnodeFnc = ParseFncDeclNoCheckScope<buildAST>(fncDeclFlags | (isGetter ? fFncNoArg : fFncOneArg), |
| SuperRestrictionState::PropertyAllowed, pidHint ? pidHint->Psz() : nullptr, /* needsPIDOnRCurlyScan */ true); |
| } |
| |
| pnodeFnc->SetIsStaticMember(isStatic); |
| if (isComputedName) |
| { |
| pnodeFnc->SetHasComputedName(); |
| } |
| pnodeFnc->SetHasHomeObj(); |
| |
| if (buildAST) |
| { |
| pnodeFnc->SetIsAccessor(); |
| pnodeMember = CreateBinNode(isGetter ? knopGetMember : knopSetMember, pnodeMemberName, pnodeFnc); |
| pMemberNameHint = ConstructFinalHintNode(pClassNamePid, pidHint, |
| isGetter ? wellKnownPropertyPids.get : wellKnownPropertyPids.set, isStatic, |
| &memberNameHintLength, &memberNameOffset, isComputedName, pMemberNameHint); |
| } |
| } |
| else |
| { |
| if (isStatic && (memberPid == wellKnownPropertyPids.prototype)) |
| { |
| Error(ERRsyntax); |
| } |
| |
| ParseNodeFnc * pnodeFnc = nullptr; |
| { |
| if (isAsyncMethod) |
| { |
| fncDeclFlags |= fFncAsync; |
| } |
| pnodeFnc = ParseFncDeclNoCheckScope<buildAST>(fncDeclFlags, SuperRestrictionState::PropertyAllowed, pidHint ? pidHint->Psz() : nullptr, /* needsPIDOnRCurlyScan */ true); |
| if (isAsyncMethod) |
| { |
| pnodeFnc->cbMin = iecpMin; |
| pnodeFnc->ichMin = ichMin; |
| } |
| |
| if (isAsyncMethod || isGenerator || isComputedName) |
| { |
| pnodeFnc->cbStringMin = iecpMin; |
| } |
| } |
| pnodeFnc->SetIsStaticMember(isStatic); |
| if (isComputedName) |
| { |
| pnodeFnc->SetHasComputedName(); |
| } |
| pnodeFnc->SetHasHomeObj(); |
| |
| if (buildAST) |
| { |
| pnodeMember = CreateBinNode(knopMember, pnodeMemberName, pnodeFnc); |
| pMemberNameHint = ConstructFinalHintNode(pClassNamePid, pidHint, nullptr /*pgetset*/, isStatic, &memberNameHintLength, &memberNameOffset, isComputedName, pMemberNameHint); |
| } |
| } |
| |
| if (buildAST) |
| { |
| Assert(memberNameHintLength >= memberNameOffset); |
| pnodeMember->AsParseNodeBin()->pnode2->AsParseNodeFnc()->hint = pMemberNameHint; // Fully qualified name |
| pnodeMember->AsParseNodeBin()->pnode2->AsParseNodeFnc()->hintLength = memberNameHintLength; |
| pnodeMember->AsParseNodeBin()->pnode2->AsParseNodeFnc()->hintOffset = memberNameOffset; |
| pnodeMember->AsParseNodeBin()->pnode2->AsParseNodeFnc()->pid = memberPid; // Short name |
| |
| AddToNodeList(isStatic ? &pnodeStaticMembers : &pnodeMembers, isStatic ? &lastStaticMemberNodeRef : &lastMemberNodeRef, pnodeMember); |
| } |
| } |
| } |
| |
| size_t cbLimConstructor = 0; |
| if (buildAST) |
| { |
| pnodeClass->ichLim = this->GetScanner()->IchLimTok(); |
| cbLimConstructor = this->GetScanner()->IecpLimTok(); |
| } |
| |
| if (!hasConstructor) |
| { |
| OUTPUT_TRACE_DEBUGONLY(Js::ES6VerboseFlag, _u("Generating constructor (%s) : %s\n"), GetParseType(), name ? name->Psz() : _u("anonymous class")); |
| |
| RestorePoint endClass; |
| this->GetScanner()->Capture(&endClass); |
| this->GetScanner()->SeekTo(beginClass); |
| |
| pnodeConstructor = GenerateEmptyConstructor<buildAST>(pnodeExtends != nullptr); |
| |
| if (buildAST) |
| { |
| if (pClassNamePid) |
| { |
| pnodeConstructor->hint = pClassNamePid->Psz(); |
| pnodeConstructor->hintLength = pClassNamePid->Cch(); |
| pnodeConstructor->hintOffset = 0; |
| } |
| else |
| { |
| Assert(nameHintLength >= nameHintOffset); |
| pnodeConstructor->hint = pNameHint; |
| pnodeConstructor->hintLength = nameHintLength; |
| pnodeConstructor->hintOffset = nameHintOffset; |
| } |
| pnodeConstructor->pid = pClassNamePid; |
| } |
| |
| this->GetScanner()->SeekTo(endClass); |
| } |
| |
| if (buildAST) |
| { |
| pnodeConstructor->cbMin = cbMinConstructor; |
| pnodeConstructor->cbStringMin = cbMinConstructor; |
| pnodeConstructor->cbLim = cbLimConstructor; |
| pnodeConstructor->ichMin = pnodeClass->ichMin; |
| pnodeConstructor->ichLim = pnodeClass->ichLim; |
| |
| PopFuncBlockScope(ppnodeScopeSave, ppnodeExprScopeSave); |
| |
| pnodeClass->pnodeDeclName = pnodeDeclName; |
| pnodeClass->pnodeName = pnodeName; |
| pnodeClass->pnodeConstructor = pnodeConstructor; |
| pnodeClass->pnodeExtends = pnodeExtends; |
| pnodeClass->pnodeMembers = pnodeMembers; |
| pnodeClass->pnodeStaticMembers = pnodeStaticMembers; |
| pnodeClass->isDefaultModuleExport = false; |
| } |
| FinishParseBlock(pnodeBlock); |
| |
| m_fUseStrictMode = strictSave; |
| |
| this->GetScanner()->Scan(); |
| |
| return pnodeClass; |
| } |
| |
| template<bool buildAST> |
| ParseNodePtr Parser::ParseStringTemplateDecl(ParseNodePtr pnodeTagFnc) |
| { |
| ParseNodePtr pnodeStringLiterals = nullptr; |
| ParseNodePtr* lastStringLiteralNodeRef = nullptr; |
| ParseNodePtr pnodeRawStringLiterals = nullptr; |
| ParseNodePtr* lastRawStringLiteralNodeRef = nullptr; |
| ParseNodePtr pnodeSubstitutionExpressions = nullptr; |
| ParseNodePtr* lastSubstitutionExpressionNodeRef = nullptr; |
| ParseNodePtr pnodeTagFncArgs = nullptr; |
| ParseNodePtr* lastTagFncArgNodeRef = nullptr; |
| ParseNodeStr * stringLiteral = nullptr; |
| ParseNodeStr * stringLiteralRaw = nullptr; |
| ParseNodeStrTemplate * pnodeStringTemplate = nullptr; |
| ParseNode * pnodeReturn = nullptr; |
| bool templateClosed = false; |
| const bool isTagged = pnodeTagFnc != nullptr; |
| uint16 stringConstantCount = 0; |
| charcount_t ichMin = 0; |
| |
| Assert(m_token.tk == tkStrTmplBasic || m_token.tk == tkStrTmplBegin); |
| |
| if (buildAST) |
| { |
| pnodeReturn = pnodeStringTemplate = CreateNodeForOpT<knopStrTemplate>(); |
| pnodeStringTemplate->countStringLiterals = 0; |
| pnodeStringTemplate->isTaggedTemplate = isTagged ? TRUE : FALSE; |
| |
| // If this is a tagged string template, we need to start building the arg list for the call |
| if (isTagged) |
| { |
| ichMin = pnodeTagFnc->ichMin; |
| AddToNodeListEscapedUse(&pnodeTagFncArgs, &lastTagFncArgNodeRef, pnodeStringTemplate); |
| } |
| |
| } |
| CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, StringTemplates, m_scriptContext); |
| |
| OUTPUT_TRACE_DEBUGONLY( |
| Js::StringTemplateParsePhase, |
| _u("Starting to parse a string template (%s)...\n\tis tagged = %s\n"), |
| GetParseType(), |
| isTagged ? _u("true") : _u("false (Raw and cooked strings will not differ!)")); |
| |
| // String template grammar |
| // `...` Simple string template |
| // `...${ String template beginning |
| // }...${ String template middle |
| // }...` String template end |
| while (!templateClosed) |
| { |
| // First, extract the string constant part - we always have one |
| if (IsStrictMode() && this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) |
| { |
| Error(ERRES5NoOctal); |
| } |
| |
| // We are not able to pass more than a ushort worth of arguments to the tag |
| // so use that as a logical limit on the number of string constant pieces. |
| if (stringConstantCount >= Js::Constants::MaxAllowedArgs) |
| { |
| Error(ERRTooManyArgs); |
| } |
| |
| // Keep track of the string literal count (must be the same for raw strings) |
| // We use this in code gen so we don't need to count the string literals list |
| stringConstantCount++; |
| |
| // If we are not creating parse nodes, there is no need to create strings |
| if (buildAST) |
| { |
| stringLiteral = CreateStrNode(m_token.GetStr()); |
| |
| AddToNodeList(&pnodeStringLiterals, &lastStringLiteralNodeRef, stringLiteral); |
| |
| // We only need to collect a raw string when we are going to pass the string template to a tag |
| if (isTagged) |
| { |
| // Make the scanner create a PID for the raw string constant for the preceding scan |
| IdentPtr pid = this->GetScanner()->GetSecondaryBufferAsPid(); |
| |
| stringLiteralRaw = CreateStrNode(pid); |
| |
| // Should have gotten a raw string literal above |
| AddToNodeList(&pnodeRawStringLiterals, &lastRawStringLiteralNodeRef, stringLiteralRaw); |
| } |
| else |
| { |
| #if DBG |
| // Assign the raw string for debug tracing below |
| stringLiteralRaw = stringLiteral; |
| #endif |
| } |
| |
| OUTPUT_TRACE_DEBUGONLY( |
| Js::StringTemplateParsePhase, |
| _u("Parsed string constant: \n\tcooked = \"%s\" \n\traw = \"%s\" \n\tdiffer = %d\n"), |
| stringLiteral->pid->Psz(), |
| stringLiteralRaw->pid->Psz(), |
| stringLiteral->pid->Psz() == stringLiteralRaw->pid->Psz() ? 0 : 1); |
| } |
| |
| switch (m_token.tk) |
| { |
| case tkStrTmplEnd: |
| case tkStrTmplBasic: |
| // We do not need to parse an expression for either the end or basic string template tokens |
| templateClosed = true; |
| break; |
| case tkStrTmplBegin: |
| case tkStrTmplMid: |
| { |
| // In the middle or begin string template token case, we need to parse an expression next |
| this->GetScanner()->Scan(); |
| |
| // Parse the contents of the curly braces as an expression |
| ParseNodePtr expression = ParseExpr<buildAST>(0); |
| |
| // After parsing expression, scan should leave us with an RCurly token. |
| // Use the NoScan version so we do not automatically perform a scan - we need to |
| // set the scan state before next scan but we don't want to set that state if |
| // the token is not as expected since we'll error in that case. |
| ChkCurTokNoScan(tkRCurly, ERRnoRcurly); |
| |
| // Notify the scanner that it should scan for a middle or end string template token |
| this->GetScanner()->SetScanState(Scanner_t::ScanState::ScanStateStringTemplateMiddleOrEnd); |
| this->GetScanner()->Scan(); |
| |
| if (buildAST) |
| { |
| // If we are going to call the tag function, add this expression into the list of args |
| if (isTagged) |
| { |
| AddToNodeListEscapedUse(&pnodeTagFncArgs, &lastTagFncArgNodeRef, expression); |
| } |
| else |
| { |
| // Otherwise add it to the substitution expression list |
| // TODO: Store the arguments and substitution expressions in a single list? |
| AddToNodeList(&pnodeSubstitutionExpressions, &lastSubstitutionExpressionNodeRef, expression); |
| } |
| } |
| |
| if (!(m_token.tk == tkStrTmplMid || m_token.tk == tkStrTmplEnd)) |
| { |
| // Scan with ScanState ScanStateStringTemplateMiddleOrEnd should only return |
| // tkStrTmpMid/End unless it is EOF or tkScanError |
| Assert(m_token.tk == tkEOF || m_token.tk == tkScanError); |
| Error(ERRsyntax); |
| } |
| |
| OUTPUT_TRACE_DEBUGONLY(Js::StringTemplateParsePhase, _u("Parsed expression\n")); |
| } |
| break; |
| default: |
| Assert(false); |
| break; |
| } |
| } |
| |
| if (buildAST) |
| { |
| pnodeStringTemplate->pnodeStringLiterals = pnodeStringLiterals; |
| pnodeStringTemplate->pnodeStringRawLiterals = pnodeRawStringLiterals; |
| pnodeStringTemplate->pnodeSubstitutionExpressions = pnodeSubstitutionExpressions; |
| pnodeStringTemplate->countStringLiterals = stringConstantCount; |
| |
| // We should still have the last string literal. |
| // Use the char offset of the end of that constant as the end of the string template. |
| pnodeStringTemplate->ichLim = stringLiteral->ichLim; |
| |
| // If this is a tagged template, we now have the argument list and can construct a call node |
| if (isTagged) |
| { |
| // Return the call node here and let the byte code generator Emit the string template automagically |
| ParseNodeCall * pnodeCall; |
| pnodeReturn = pnodeCall = CreateCallNode(knopCall, pnodeTagFnc, pnodeTagFncArgs, ichMin, pnodeStringTemplate->ichLim); |
| |
| // We need to set the arg count explicitly |
| pnodeCall->argCount = stringConstantCount; |
| pnodeCall->hasDestructuring = m_hasDestructuringPattern; |
| } |
| } |
| |
| this->GetScanner()->Scan(); |
| |
| return pnodeReturn; |
| } |
| |
| LPCOLESTR Parser::FormatPropertyString(LPCOLESTR propertyString, ParseNodePtr pNode, uint32 *fullNameHintLength, uint32 *pShortNameOffset) |
| { |
| // propertyString could be null, such as 'this.foo' = |
| // propertyString could be empty, found in pattern as in (-1)[""][(x = z)] |
| |
| OpCode op = pNode->nop; |
| LPCOLESTR rightNode = nullptr; |
| if (propertyString == nullptr) |
| { |
| propertyString = _u(""); |
| } |
| |
| if (op != knopInt && op != knopFlt && op != knopName && op != knopStr) |
| { |
| rightNode = _u(""); |
| } |
| else if (op == knopStr) |
| { |
| return AppendNameHints(propertyString, pNode->AsParseNodeStr()->pid, fullNameHintLength, pShortNameOffset, false, true/*add brackets*/); |
| } |
| else if (op == knopFlt) |
| { |
| rightNode = this->GetScanner()->StringFromDbl(pNode->AsParseNodeFloat()->dbl); |
| } |
| else |
| { |
| rightNode = op == knopInt ? this->GetScanner()->StringFromLong(pNode->AsParseNodeInt()->lw) |
| : pNode->AsParseNodeName()->pid->Psz(); |
| } |
| |
| return AppendNameHints(propertyString, rightNode, fullNameHintLength, pShortNameOffset, false, true/*add brackets*/); |
| } |
| |
| LPCOLESTR Parser::ConstructNameHint(ParseNodeBin * pNode, uint32* fullNameHintLength, uint32 *pShortNameOffset) |
| { |
| Assert(pNode != nullptr); |
| Assert(pNode->nop == knopDot || pNode->nop == knopIndex); |
| |
| // This method recursively visits nodes in the AST and could cause an SOE crash for long knopDot chains. |
| // Although this method could be made non-recursive, Emit (ByteCodeEmitter.cpp) hits a stack probe |
| // for shorter chains than which cause SOE here, so add a stack probe to throw SOE rather than crash on SOE. |
| // Because of that correspondence, use Js::Constants::MinStackByteCodeVisitor (which is used in Emit) |
| // for the stack probe here. See OS#14711878. |
| PROBE_STACK_NO_DISPOSE(this->m_scriptContext, Js::Constants::MinStackByteCodeVisitor); |
| |
| LPCOLESTR leftNode = nullptr; |
| if (pNode->pnode1->nop == knopDot || pNode->pnode1->nop == knopIndex) |
| { |
| leftNode = ConstructNameHint(pNode->pnode1->AsParseNodeBin(), fullNameHintLength, pShortNameOffset); |
| } |
| else if (pNode->pnode1->nop == knopName && !pNode->pnode1->AsParseNodeName()->IsSpecialName()) |
| { |
| // We need to skip special names like 'this' because those shouldn't be appended to the |
| // name hint in the debugger stack trace. |
| // function ctor() { |
| // this.func = function() { |
| // // If we break here, the stack should say we are in 'func' and not 'this.func' |
| // } |
| // } |
| |
| IdentPtr pid = pNode->pnode1->AsParseNodeName()->pid; |
| leftNode = pid->Psz(); |
| *fullNameHintLength = pid->Cch(); |
| *pShortNameOffset = 0; |
| } |
| |
| if (pNode->nop == knopIndex) |
| { |
| return FormatPropertyString( |
| leftNode ? leftNode : Js::Constants::AnonymousFunction, // e.g. f()[0] = function () {} |
| pNode->pnode2, fullNameHintLength, pShortNameOffset); |
| } |
| |
| Assert(pNode->pnode2->nop == knopDot || pNode->pnode2->nop == knopName); |
| |
| LPCOLESTR rightNode = nullptr; |
| bool wrapWithBrackets = false; |
| if (pNode->pnode2->nop == knopDot) |
| { |
| rightNode = ConstructNameHint(pNode->pnode2->AsParseNodeBin(), fullNameHintLength, pShortNameOffset); |
| } |
| else |
| { |
| rightNode = pNode->pnode2->AsParseNodeName()->pid->Psz(); |
| wrapWithBrackets = PNodeFlags::fpnIndexOperator == (pNode->grfpn & PNodeFlags::fpnIndexOperator); |
| } |
| Assert(rightNode != nullptr); |
| return AppendNameHints(leftNode, rightNode, fullNameHintLength, pShortNameOffset, false, wrapWithBrackets); |
| } |
| |
| LPCOLESTR Parser::AppendNameHints(LPCOLESTR leftStr, uint32 leftLen, LPCOLESTR rightStr, uint32 rightLen, uint32 *pNameLength, uint32 *pShortNameOffset, bool ignoreAddDotWithSpace, bool wrapInBrackets) |
| { |
| Assert(rightStr != nullptr); |
| Assert(leftLen != 0 || wrapInBrackets); |
| Assert(rightLen != 0 || wrapInBrackets); |
| |
| bool ignoreDot = rightStr[0] == _u('[') && !wrapInBrackets;//if we wrap in brackets it can be a string literal which can have brackets at the first char |
| uint32 totalLength = leftLen + rightLen + ((ignoreDot) ? 1 : 2); // 1 (for dot or [) + 1 (for null termination) |
| |
| if (wrapInBrackets) |
| { |
| totalLength++; //1 for ']'; |
| } |
| WCHAR * finalName = AllocateStringOfLength(totalLength); |
| |
| if (leftStr != nullptr && leftLen != 0) |
| { |
| wcscpy_s(finalName, leftLen + 1, leftStr); |
| } |
| |
| if (ignoreAddDotWithSpace) |
| { |
| finalName[leftLen++] = (OLECHAR)_u(' '); |
| } |
| // mutually exclusive from ignoreAddDotWithSpace which is used for getters/setters |
| |
| else if (wrapInBrackets) |
| { |
| finalName[leftLen++] = (OLECHAR)_u('['); |
| finalName[totalLength - 2] = (OLECHAR)_u(']'); |
| } |
| else if (!ignoreDot) |
| { |
| finalName[leftLen++] = (OLECHAR)_u('.'); |
| } |
| //ignore case falls through |
| js_wmemcpy_s(finalName + leftLen, rightLen, rightStr, rightLen); |
| finalName[totalLength - 1] = (OLECHAR)_u('\0'); |
| |
| if (pNameLength != nullptr) |
| { |
| *pNameLength = totalLength - 1; |
| } |
| if (pShortNameOffset != nullptr) |
| { |
| *pShortNameOffset = leftLen; |
| } |
| |
| return finalName; |
| } |
| |
| WCHAR * Parser::AllocateStringOfLength(ULONG length) |
| { |
| Assert(length > 0); |
| ULONG totalBytes; |
| if (ULongMult(length, sizeof(OLECHAR), &totalBytes) != S_OK) |
| { |
| Error(ERRnoMemory); |
| } |
| WCHAR* finalName = (WCHAR*)this->GetHashTbl()->GetAllocator()->Alloc(totalBytes); |
| if (finalName == nullptr) |
| { |
| Error(ERRnoMemory); |
| } |
| return finalName; |
| } |
| |
| LPCOLESTR Parser::AppendNameHints(IdentPtr left, IdentPtr right, uint32 *pNameLength, uint32 *pShortNameOffset, bool ignoreAddDotWithSpace, bool wrapInBrackets) |
| { |
| if (pShortNameOffset != nullptr) |
| { |
| *pShortNameOffset = 0; |
| } |
| |
| if (left == nullptr && !wrapInBrackets) |
| { |
| if (right) |
| { |
| *pNameLength = right->Cch(); |
| return right->Psz(); |
| } |
| return nullptr; |
| } |
| |
| uint32 leftLen = 0; |
| LPCOLESTR leftStr = _u(""); |
| |
| if (left != nullptr) // if wrapInBrackets is true |
| { |
| leftStr = left->Psz(); |
| leftLen = left->Cch(); |
| } |
| |
| if (right == nullptr) |
| { |
| *pNameLength = leftLen; |
| return left->Psz(); |
| } |
| uint32 rightLen = right->Cch(); |
| |
| return AppendNameHints(leftStr, leftLen, right->Psz(), rightLen, pNameLength, pShortNameOffset, ignoreAddDotWithSpace, wrapInBrackets); |
| } |
| |
| LPCOLESTR Parser::AppendNameHints(IdentPtr left, LPCOLESTR right, uint32 *pNameLength, uint32 *pShortNameOffset, bool ignoreAddDotWithSpace, bool wrapInBrackets) |
| { |
| uint32 rightLen = (right == nullptr) ? 0 : (uint32)wcslen(right); |
| |
| if (pShortNameOffset != nullptr) |
| { |
| *pShortNameOffset = 0; |
| } |
| |
| Assert(rightLen <= ULONG_MAX); // name hints should not exceed ULONG_MAX characters |
| |
| if (left == nullptr && !wrapInBrackets) |
| { |
| *pNameLength = rightLen; |
| return right; |
| } |
| |
| LPCOLESTR leftStr = _u(""); |
| uint32 leftLen = 0; |
| |
| if (left != nullptr) // if wrapInBrackets is true |
| { |
| leftStr = left->Psz(); |
| leftLen = left->Cch(); |
| } |
| |
| if (rightLen == 0 && !wrapInBrackets) |
| { |
| *pNameLength = leftLen; |
| return left->Psz(); |
| } |
| |
| return AppendNameHints(leftStr, leftLen, right, rightLen, pNameLength, pShortNameOffset, ignoreAddDotWithSpace, wrapInBrackets); |
| } |
| |
| LPCOLESTR Parser::AppendNameHints(LPCOLESTR left, IdentPtr right, uint32 *pNameLength, uint32 *pShortNameOffset, bool ignoreAddDotWithSpace, bool wrapInBrackets) |
| { |
| uint32 leftLen = (left == nullptr) ? 0 : (uint32)wcslen(left); |
| |
| if (pShortNameOffset != nullptr) |
| { |
| *pShortNameOffset = 0; |
| } |
| |
| Assert(leftLen <= ULONG_MAX); // name hints should not exceed ULONG_MAX characters |
| |
| if (left == nullptr || (leftLen == 0 && !wrapInBrackets)) |
| { |
| if (right != nullptr) |
| { |
| *pNameLength = right->Cch(); |
| return right->Psz(); |
| } |
| return nullptr; |
| } |
| |
| if (right == nullptr) |
| { |
| *pNameLength = leftLen; |
| return left; |
| } |
| uint32 rightLen = right->Cch(); |
| |
| return AppendNameHints(left, leftLen, right->Psz(), rightLen, pNameLength, pShortNameOffset, ignoreAddDotWithSpace, wrapInBrackets); |
| } |
| |
| |
| LPCOLESTR Parser::AppendNameHints(LPCOLESTR left, LPCOLESTR right, uint32 *pNameLength, uint32 *pShortNameOffset, bool ignoreAddDotWithSpace, bool wrapInBrackets) |
| { |
| uint32 leftLen = (left == nullptr) ? 0 : (uint32)wcslen(left); |
| uint32 rightLen = (right == nullptr) ? 0 : (uint32)wcslen(right); |
| if (pShortNameOffset != nullptr) |
| { |
| *pShortNameOffset = 0; |
| } |
| Assert(rightLen <= ULONG_MAX && leftLen <= ULONG_MAX); // name hints should not exceed ULONG_MAX characters |
| |
| if (leftLen == 0 && !wrapInBrackets) |
| { |
| *pNameLength = right ? rightLen : 0; |
| return right; |
| } |
| |
| if (rightLen == 0 && !wrapInBrackets) |
| { |
| *pNameLength = leftLen; |
| return left; |
| } |
| |
| return AppendNameHints(left, leftLen, right, rightLen, pNameLength, pShortNameOffset, ignoreAddDotWithSpace, wrapInBrackets); |
| } |
| |
| /** |
| * Emits a spread error if there is no ambiguity, or marks defers the error for |
| * when we can determine if it is a rest error or a spread error. |
| * |
| * The ambiguity arises when we are parsing a lambda parameter list but we have |
| * not seen the => token. At this point, we are either in a parenthesized |
| * expression or a parameter list, and cannot issue an error until the matching |
| * RParen has been scanned. |
| * |
| * The actual emission of the error happens in ParseExpr, when we first know if |
| * the expression is a lambda parameter list or not. |
| * |
| */ |
| void Parser::DeferOrEmitPotentialSpreadError(ParseNodePtr pnodeT) |
| { |
| if (m_funcParenExprDepth > 0) |
| { |
| if (m_token.tk == tkRParen) |
| { |
| if (!m_deferEllipsisError) |
| { |
| // Capture only the first error instance. Because a lambda will cause a reparse in a formals context, we can assume |
| // that this will be a spread error. Nested paren exprs will have their own error instance. |
| this->GetScanner()->Capture(&m_deferEllipsisErrorLoc); |
| m_deferEllipsisError = true; |
| } |
| } |
| else |
| { |
| Error(ERRUnexpectedEllipsis); |
| } |
| } |
| else |
| { |
| Error(ERRInvalidSpreadUse); |
| } |
| } |
| |
| bool Parser::IsTerminateToken(bool fAllowIn) |
| { |
| return (m_token.tk == tkRCurly || |
| m_token.tk == tkRBrack || |
| m_token.tk == tkRParen || |
| m_token.tk == tkSColon || |
| m_token.tk == tkColon || |
| m_token.tk == tkComma || |
| m_token.tk == tkLimKwd || |
| (m_token.tk == tkIN && fAllowIn) || |
| this->GetScanner()->FHadNewLine()); |
| } |
| |
| /*************************************************************************** |
| Parse an optional sub expression returning null if there was no expression. |
| Checks for no expression by looking for a token that can follow an |
| Expression grammar production. |
| ***************************************************************************/ |
| template<bool buildAST> |
| bool Parser::ParseOptionalExpr(ParseNodePtr* pnode, bool fUnaryOrParen, int oplMin, BOOL *pfCanAssign, BOOL fAllowIn, BOOL fAllowEllipsis, _Inout_opt_ IdentToken* pToken) |
| { |
| *pnode = nullptr; |
| if (IsTerminateToken(!fAllowIn)) |
| { |
| return false; |
| } |
| |
| IdentToken token; |
| ParseNodePtr pnodeT = ParseExpr<buildAST>(oplMin, pfCanAssign, fAllowIn, fAllowEllipsis, nullptr /*pNameHint*/, nullptr /*pHintLength*/, nullptr /*pShortNameOffset*/, &token, fUnaryOrParen); |
| // Detect nested function escapes of the pattern "return function(){...}" or "yield function(){...}". |
| // Doing so in the parser allows us to disable stack-nested-functions in common cases where an escape |
| // is not detected at byte code gen time because of deferred parsing. |
| this->MarkEscapingRef(pnodeT, &token); |
| if (pToken) |
| { |
| *pToken = token; |
| } |
| *pnode = pnodeT; |
| return true; |
| } |
| |
| /*************************************************************************** |
| Parse a sub expression. |
| 'fAllowIn' indicates if the 'in' operator should be allowed in the initializing |
| expression ( it is not allowed in the context of the first expression in a 'for' loop). |
| ***************************************************************************/ |
| template<bool buildAST> |
| ParseNodePtr Parser::ParseExpr(int oplMin, |
| BOOL *pfCanAssign, |
| BOOL fAllowIn, |
| BOOL fAllowEllipsis, |
| LPCOLESTR pNameHint, |
| uint32 *pHintLength, |
| uint32 *pShortNameOffset, |
| _Inout_opt_ IdentToken* pToken, |
| bool fUnaryOrParen, |
| _Inout_opt_ bool* pfLikelyPattern, |
| _Inout_opt_ charcount_t *plastRParen) |
| { |
| Assert(pToken == nullptr || pToken->tk == tkNone); // Must be empty initially |
| int opl; |
| OpCode nop; |
| charcount_t ichMin; |
| ParseNodePtr pnode = nullptr; |
| ParseNodePtr pnodeT = nullptr; |
| BOOL fCanAssign = TRUE; |
| bool assignmentStmt = false; |
| bool fIsDotOrIndex = false; |
| IdentToken term; |
| RestorePoint termStart; |
| uint32 hintLength = 0; |
| uint32 hintOffset = 0; |
| BOOL fLikelyPattern = FALSE; |
| |
| ParserState parserState; |
| |
| if (pHintLength != nullptr) |
| { |
| hintLength = *pHintLength; |
| } |
| |
| if (pShortNameOffset != nullptr) |
| { |
| hintOffset = *pShortNameOffset; |
| } |
| |
| EnsureStackAvailable(); |
| |
| // Storing the state here as we need to restore this state back when we need to reparse the grammar under lambda syntax. |
| CaptureState(&parserState); |
| |
| this->GetScanner()->Capture(&termStart); |
| |
| bool savedDeferredInitError = m_hasDeferredShorthandInitError; |
| m_hasDeferredShorthandInitError = false; |
| |
| // Is the current token a unary operator? |
| if (this->GetHashTbl()->TokIsUnop(m_token.tk, &opl, &nop) && nop != knopNone) |
| { |
| IdentToken operandToken; |
| ichMin = this->GetScanner()->IchMinTok(); |
| |
| if (nop == knopYield) |
| { |
| if (!this->GetScanner()->YieldIsKeywordRegion() || oplMin > opl) |
| { |
| // The case where 'yield' is scanned as a keyword (tkYIELD) but the scanner |
| // is not treating yield as a keyword (!this->GetScanner()->YieldIsKeywordRegion()) occurs |
| // in strict mode non-generator function contexts. |
| // |
| // That is, 'yield' is a keyword because of strict mode, but YieldExpression |
| // is not a grammar production outside of generator functions. |
| // |
| // Otherwise it is an error for a yield to appear in the context of a higher level |
| // binding operator, be it unary or binary. |
| Error(ERRsyntax); |
| } |
| if (m_currentScope->GetScopeType() == ScopeType_Parameter |
| || (m_currentScope->GetScopeType() == ScopeType_Block && m_currentScope->GetEnclosingScope()->GetScopeType() == ScopeType_Parameter)) // Check whether this is a class definition inside param scope |
| { |
| Error(ERRsyntax); |
| } |
| } |
| else if (nop == knopAwait) |
| { |
| if (!this->GetScanner()->AwaitIsKeywordRegion() || |
| m_currentScope->GetScopeType() == ScopeType_Parameter || |
| (m_currentScope->GetScopeType() == ScopeType_Block && m_currentScope->GetEnclosingScope()->GetScopeType() == ScopeType_Parameter)) // Check whether this is a class definition inside param scope |
| { |
| // As with the 'yield' keyword, the case where 'await' is scanned as a keyword (tkAWAIT) |
| // but the scanner is not treating await as a keyword (!this->GetScanner()->AwaitIsKeyword()) |
| // occurs in strict mode non-async function contexts. |
| // |
| // That is, 'await' is a keyword because of strict mode, but AwaitExpression |
| // is not a grammar production outside of async functions. |
| // |
| // Further, await expressions are disallowed within parameter scopes. |
| Error(ERRBadAwait); |
| } |
| } |
| |
| this->GetScanner()->Scan(); |
| |
| if (m_token.tk == tkEllipsis) { |
| // ... cannot have a unary prefix. |
| Error(ERRUnexpectedEllipsis); |
| } |
| |
| if (nop == knopYield && !this->GetScanner()->FHadNewLine() && m_token.tk == tkStar) |
| { |
| this->GetScanner()->Scan(); |
| nop = knopYieldStar; |
| } |
| |
| if (nop == knopYield) |
| { |
| if (!ParseOptionalExpr<buildAST>(&pnodeT, false, opl, NULL, fAllowIn, fAllowEllipsis)) |
| { |
| nop = knopYieldLeaf; |
| if (buildAST) |
| { |
| pnode = CreateNodeForOpT<knopYieldLeaf>(ichMin, this->GetScanner()->IchLimTok()); |
| } |
| } |
| } |
| else if (nop == knopAwait && m_token.tk == tkColon) |
| { |
| Error(ERRAwaitAsLabelInAsync); |
| } |
| else |
| { |
| // Disallow spread after a unary operator. |
| pnodeT = ParseExpr<buildAST>(opl, &fCanAssign, TRUE, FALSE, nullptr /*hint*/, nullptr /*hintLength*/, nullptr /*hintOffset*/, &operandToken, true, nullptr, plastRParen); |
| } |
| |
| if (nop != knopYieldLeaf) |
| { |
| if ((nop == knopIncPre || nop == knopDecPre) && (m_token.tk != tkDArrow)) |
| { |
| if (!fCanAssign && |
| (m_sourceContextInfo |
| ? !PHASE_OFF_RAW(Js::EarlyReferenceErrorsPhase, m_sourceContextInfo->sourceContextId, GetCurrentFunctionNode()->functionId) |
| : !PHASE_OFF1(Js::EarlyReferenceErrorsPhase))) |
| { |
| Error(JSERR_CantAssignTo); |
| } |
| TrackAssignment<buildAST>(pnodeT, &operandToken); |
| if (buildAST) |
| { |
| if (IsStrictMode() && pnodeT->nop == knopName) |
| { |
| CheckStrictModeEvalArgumentsUsage(pnodeT->AsParseNodeName()->pid); |
| } |
| } |
| else |
| { |
| if (IsStrictMode() && operandToken.tk == tkID) |
| { |
| CheckStrictModeEvalArgumentsUsage(operandToken.pid); |
| } |
| } |
| } |
| else if (nop == knopEllipsis) |
| { |
| if (!fAllowEllipsis) |
| { |
| DeferOrEmitPotentialSpreadError(pnodeT); |
| } |
| } |
| else if (m_token.tk == tkExpo) |
| { |
| //Unary operator on the left hand-side of ** is unexpected, except ++, -- or ... |
| Error(ERRInvalidUseofExponentiationOperator); |
| } |
| |
| if (buildAST) |
| { |
| //Do not do the folding for Asm in case of KnopPos as we need this to determine the type |
| if (nop == knopPos && (pnodeT->nop == knopInt || pnodeT->nop == knopFlt) && !this->m_InAsmMode) |
| { |
| // Fold away a unary '+' on a number. |
| pnode = pnodeT; |
| } |
| else if (nop == knopNeg && |
| ((pnodeT->nop == knopInt && pnodeT->AsParseNodeInt()->lw != 0) || |
| (pnodeT->nop == knopFlt && (pnodeT->AsParseNodeFloat()->dbl != 0 || this->m_InAsmMode)) || |
| (pnodeT->nop == knopBigInt))) |
| { |
| // Fold a unary '-' on a number into the value of the number itself. |
| pnode = pnodeT; |
| if (pnode->nop == knopInt) |
| { |
| pnode->AsParseNodeInt()->lw = -pnode->AsParseNodeInt()->lw; |
| } |
| else if (pnode->nop == knopBigInt) |
| { |
| pnode->AsParseNodeBigInt()->isNegative = true; |
| } |
| else |
| { |
| pnode->AsParseNodeFloat()->dbl = -pnode->AsParseNodeFloat()->dbl; |
| } |
| } |
| else |
| { |
| pnode = CreateUniNode(nop, pnodeT); |
| this->CheckArguments(pnode->AsParseNodeUni()->pnode1); |
| } |
| pnode->ichMin = ichMin; |
| } |
| |
| if (nop == knopDelete) |
| { |
| if (IsStrictMode()) |
| { |
| if ((buildAST && pnode->AsParseNodeUni()->pnode1->IsUserIdentifier()) || |
| (!buildAST && operandToken.tk == tkID && !this->IsSpecialName(operandToken.pid))) |
| { |
| Error(ERRInvalidDelete); |
| } |
| } |
| |
| if (buildAST) |
| { |
| ParseNodePtr pnode1 = pnode->AsParseNodeUni()->pnode1; |
| if (m_currentNodeFunc) |
| { |
| if (pnode1->nop == knopDot || pnode1->nop == knopIndex) |
| { |
| // If we delete an arguments property, use the conservative, |
| // heap-allocated arguments object. |
| this->CheckArguments(pnode1->AsParseNodeBin()->pnode1); |
| } |
| } |
| } |
| } |
| } |
| |
| fCanAssign = FALSE; |
| } |
| else |
| { |
| ichMin = this->GetScanner()->IchMinTok(); |
| pnode = ParseTerm<buildAST>(TRUE, pNameHint, &hintLength, &hintOffset, &term, fUnaryOrParen, TRUE, &fCanAssign, IsES6DestructuringEnabled() ? &fLikelyPattern : nullptr, &fIsDotOrIndex, plastRParen); |
| if (pfLikelyPattern != nullptr) |
| { |
| *pfLikelyPattern = !!fLikelyPattern; |
| } |
| |
| if (m_token.tk == tkDArrow |
| // If we have introduced shorthand error above in the ParseExpr, we need to reset if next token is the assignment. |
| || (m_token.tk == tkAsg && oplMin <= koplAsg)) |
| { |
| m_hasDeferredShorthandInitError = false; |
| } |
| |
| if (m_token.tk == tkAsg && oplMin <= koplAsg && fLikelyPattern) |
| { |
| this->GetScanner()->SeekTo(termStart); |
| |
| // As we are reparsing from the beginning of the destructured literal we need to reset the Block IDs as well to make sure the Block IDs |
| // on the pidref stack match. |
| int saveNextBlockId = m_nextBlockId; |
| m_nextBlockId = parserState.m_nextBlockId; |
| |
| ParseDestructuredLiteralWithScopeSave(tkLCurly, false/*isDecl*/, false /*topLevel*/, DIC_ShouldNotParseInitializer); |
| |
| // Restore the Block ID at the end of the reparsing so it matches the one at the end of the first pass. We need to do this |
| // because we don't parse initializers during reparse and there may be additional blocks (e.g. a class declaration) |
| // in the initializers that will cause the next Block ID at the end of the reparsing to be different. |
| m_nextBlockId = saveNextBlockId; |
| |
| if (buildAST) |
| { |
| this->SetHasDestructuringPattern(true); |
| pnode = ConvertToPattern(pnode); |
| } |
| } |
| |
| if (buildAST) |
| { |
| pNameHint = NULL; |
| if (pnode->nop == knopName) |
| { |
| IdentPtr pid = pnode->AsParseNodeName()->pid; |
| pNameHint = pid->Psz(); |
| hintLength = pid->Cch(); |
| hintOffset = 0; |
| } |
| else if (pnode->nop == knopDot || pnode->nop == knopIndex) |
| { |
| if (CONFIG_FLAG(UseFullName)) |
| { |
| pNameHint = ConstructNameHint(pnode->AsParseNodeBin(), &hintLength, &hintOffset); |
| } |
| else |
| { |
| ParseNodePtr pnodeName = pnode; |
| while (pnodeName->nop == knopDot) |
| { |
| pnodeName = pnodeName->AsParseNodeBin()->pnode2; |
| } |
| |
| if (pnodeName->nop == knopName) |
| { |
| IdentPtr pid = pnode->AsParseNodeName()->pid; |
| pNameHint = pid->Psz(); |
| hintLength = pid->Cch(); |
| hintOffset = 0; |
| } |
| } |
| } |
| } |
| |
| // Check for postfix unary operators. |
| if (!this->GetScanner()->FHadNewLine() && |
| (tkInc == m_token.tk || tkDec == m_token.tk)) |
| { |
| if (!fCanAssign && |
| (m_sourceContextInfo |
| ? !PHASE_OFF_RAW(Js::EarlyReferenceErrorsPhase, m_sourceContextInfo->sourceContextId, GetCurrentFunctionNode()->functionId) |
| : !PHASE_OFF1(Js::EarlyReferenceErrorsPhase))) |
| { |
| Error(JSERR_CantAssignTo); |
| } |
| TrackAssignment<buildAST>(pnode, &term); |
| fCanAssign = FALSE; |
| if (buildAST) |
| { |
| if (IsStrictMode() && pnode->nop == knopName) |
| { |
| CheckStrictModeEvalArgumentsUsage(pnode->AsParseNodeName()->pid); |
| } |
| this->CheckArguments(pnode); |
| pnode = CreateUniNode(tkInc == m_token.tk ? knopIncPost : knopDecPost, pnode); |
| pnode->ichLim = this->GetScanner()->IchLimTok(); |
| } |
| else |
| { |
| if (IsStrictMode() && term.tk == tkID) |
| { |
| CheckStrictModeEvalArgumentsUsage(term.pid); |
| } |
| // This expression is not an identifier |
| term.tk = tkNone; |
| } |
| this->GetScanner()->Scan(); |
| } |
| } |
| |
| // Process a sequence of operators and operands. |
| for (;;) |
| { |
| if (!this->GetHashTbl()->TokIsBinop(m_token.tk, &opl, &nop) || nop == knopNone) |
| { |
| break; |
| } |
| if (!fAllowIn && nop == knopIn) |
| { |
| break; |
| } |
| Assert(opl != koplNo); |
| |
| if (opl == koplAsg) |
| { |
| if (m_token.tk != tkDArrow) |
| { |
| // Assignment operator. These are the only right associative |
| // binary operators. We also need to special case the left |
| // operand - it should only be a LeftHandSideExpression. |
| Assert(ParseNode::Grfnop(nop) & fnopAsg || nop == knopFncDecl); |
| TrackAssignment<buildAST>(pnode, &term); |
| if (buildAST) |
| { |
| if (IsStrictMode() && pnode->nop == knopName) |
| { |
| CheckStrictModeEvalArgumentsUsage(pnode->AsParseNodeName()->pid); |
| } |
| |
| // Assignment stmt of the form "this.<id> = <expr>" |
| if (nop == knopAsg |
| && pnode->nop == knopDot |
| && pnode->AsParseNodeBin()->pnode1->nop == knopName |
| && pnode->AsParseNodeBin()->pnode1->AsParseNodeName()->pid == wellKnownPropertyPids._this |
| && pnode->AsParseNodeBin()->pnode2->nop == knopName) |
| { |
| if (pnode->AsParseNodeBin()->pnode2->AsParseNodeName()->pid != wellKnownPropertyPids.__proto__) |
| { |
| assignmentStmt = true; |
| } |
| } |
| } |
| else |
| { |
| if (IsStrictMode() && term.tk == tkID) |
| { |
| CheckStrictModeEvalArgumentsUsage(term.pid); |
| } |
| } |
| } |
| |
| if (opl < oplMin) |
| { |
| break; |
| } |
| if (m_token.tk != tkDArrow && !fCanAssign && |
| (m_sourceContextInfo |
| ? !PHASE_OFF_RAW(Js::EarlyReferenceErrorsPhase, m_sourceContextInfo->sourceContextId, GetCurrentFunctionNode()->functionId) |
| : !PHASE_OFF1(Js::EarlyReferenceErrorsPhase))) |
| { |
| Error(JSERR_CantAssignTo); |
| // No recovery necessary since this is a semantic, not structural, error. |
| } |
| } |
| else if (opl == koplExpo) |
| { |
| // ** operator is right associative |
| if (opl < oplMin) |
| { |
| break; |
| } |
| |
| } |
| else if (opl <= oplMin) |
| { |
| break; |
| } |
| |
| // This expression is not an identifier |
| term.tk = tkNone; |
| |
| // Precedence is high enough. Consume the operator token. |
| this->GetScanner()->Scan(); |
| fCanAssign = !!fLikelyPattern; |
| |
| // Special case the "?:" operator |
| if (nop == knopQmark) |
| { |
| pnodeT = ParseExpr<buildAST>(koplAsg, NULL, fAllowIn); |
| ChkCurTok(tkColon, ERRnoColon); |
| ParseNodePtr pnodeT2 = ParseExpr<buildAST>(koplAsg, NULL, fAllowIn, 0, nullptr, nullptr, nullptr, nullptr, false, nullptr, plastRParen); |
| if (buildAST) |
| { |
| pnode = CreateTriNode(nop, pnode, pnodeT, pnodeT2); |
| this->CheckArguments(pnode->AsParseNodeTri()->pnode2); |
| this->CheckArguments(pnode->AsParseNodeTri()->pnode3); |
| } |
| } |
| else if (nop == knopFncDecl) |
| { |
| ushort flags = fFncLambda; |
| size_t iecpMin = 0; |
| bool isAsyncMethod = false; |
| |
| RestoreStateFrom(&parserState); |
| |
| this->GetScanner()->SeekTo(termStart); |
| if (m_token.tk == tkID && m_token.GetIdentifier(this->GetHashTbl()) == wellKnownPropertyPids.async && m_scriptContext->GetConfig()->IsES7AsyncAndAwaitEnabled()) |
| { |
| ichMin = this->GetScanner()->IchMinTok(); |
| iecpMin = this->GetScanner()->IecpMinTok(); |
| |
| this->GetScanner()->Scan(); |
| if ((m_token.tk == tkID || m_token.tk == tkLParen) && !this->GetScanner()->FHadNewLine()) |
| { |
| flags |= fFncAsync; |
| isAsyncMethod = true; |
| } |
| else |
| { |
| this->GetScanner()->SeekTo(termStart); |
| } |
| } |
| pnode = ParseFncDeclNoCheckScope<buildAST>(flags, SuperRestrictionState::Disallowed, nullptr, /* needsPIDOnRCurlyScan = */false, /* fUnaryOrParen = */ false, fAllowIn); |
| if (isAsyncMethod) |
| { |
| pnode->AsParseNodeFnc()->cbStringMin = iecpMin; |
| } |
| |
| // ArrowFunction/AsyncArrowFunction is part of AssignmentExpression, which should terminate the expression unless followed by a comma |
| if (m_token.tk != tkComma && m_token.tk != tkIN) |
| { |
| if (!(IsTerminateToken(false))) |
| { |
| Error(ERRnoSemic); |
| } |
| break; |
| } |
| } |
| else // a binary operator |
| { |
| if (nop == knopComma && m_token.tk == tkRParen) |
| { |
| // Trailing comma |
| this->GetScanner()->Capture(&m_deferCommaErrorLoc); |
| m_deferCommaError = true; |
| break; |
| } |
| |
| ParseNode* pnode1 = pnode; |
| |
| // Parse the operand, make a new node, and look for more |
| IdentToken token; |
| ParseNode* pnode2 = ParseExpr<buildAST>( |
| opl, nullptr, fAllowIn, FALSE, pNameHint, &hintLength, &hintOffset, &token, false, nullptr, plastRParen); |
| |
| // Detect nested function escapes of the pattern "o.f = function(){...}" or "o[s] = function(){...}". |
| // Doing so in the parser allows us to disable stack-nested-functions in common cases where an escape |
| // is not detected at byte code gen time because of deferred parsing. |
| if (fIsDotOrIndex && nop == knopAsg) |
| { |
| this->MarkEscapingRef(pnodeT, &token); |
| } |
| |
| if (buildAST) |
| { |
| Assert(pnode2 != nullptr); |
| if (pnode2->nop == knopFncDecl) |
| { |
| Assert(hintLength >= hintOffset); |
| pnode2->AsParseNodeFnc()->hint = pNameHint; |
| pnode2->AsParseNodeFnc()->hintLength = hintLength; |
| pnode2->AsParseNodeFnc()->hintOffset = hintOffset; |
| |
| if (pnode1->nop == knopDot) |
| { |
| pnode2->AsParseNodeFnc()->isNameIdentifierRef = false; |
| } |
| else if (pnode1->nop == knopName) |
| { |
| PidRefStack *pidRef = pnode1->AsParseNodeName()->pid->GetTopRef(); |
| pidRef->isFuncAssignment = true; |
| } |
| } |
| else if (pnode2->nop == knopClassDecl && pnode1->nop == knopDot) |
| { |
| Assert(pnode2->AsParseNodeClass()->pnodeConstructor); |
| |
| if (!pnode2->AsParseNodeClass()->pnodeConstructor->pid) |
| { |
| pnode2->AsParseNodeClass()->pnodeConstructor->isNameIdentifierRef = false; |
| } |
| } |
| else if (pnode1->nop == knopName && nop == knopIn) |
| { |
| PidRefStack* pidRef = pnode1->AsParseNodeName()->pid->GetTopRef(); |
| pidRef->SetIsUsedInLdElem(true); |
| } |
| |
| pnode = CreateBinNode(nop, pnode1, pnode2); |
| } |
| pNameHint = nullptr; |
| } |
| } |
| |
| if (buildAST) |
| { |
| if (!assignmentStmt) |
| { |
| // Don't set the flag for following nodes |
| switch (pnode->nop) |
| { |
| case knopName: |
| case knopInt: |
| case knopBigInt: |
| case knopFlt: |
| case knopStr: |
| case knopRegExp: |
| case knopNull: |
| case knopFalse: |
| case knopTrue: |
| break; |
| default: |
| if (m_currentNodeFunc) |
| { |
| m_currentNodeFunc->SetHasNonThisStmt(); |
| } |
| else if (m_currentNodeProg) |
| { |
| m_currentNodeProg->SetHasNonThisStmt(); |
| } |
| } |
| } |
| } |
| |
| m_hasDeferredShorthandInitError = m_hasDeferredShorthandInitError || savedDeferredInitError; |
| |
| if (NULL != pfCanAssign) |
| { |
| *pfCanAssign = fCanAssign; |
| } |
| |
| // Pass back identifier if requested |
| if (pToken && term.tk == tkID) |
| { |
| *pToken = term; |
| } |
| |
| //Track "obj.a" assignment patterns here - Promote the Assignment state for the property's PID. |
| // This includes =, += etc. |
| if (pnode != NULL) |
| { |
| uint nodeType = ParseNode::Grfnop(pnode->nop); |
| if (nodeType & fnopAsg) |
| { |
| if (nodeType & fnopBin) |
| { |
| ParseNodePtr lhs = pnode->AsParseNodeBin()->pnode1; |
| |
| Assert(lhs); |
| if (lhs->nop == knopDot) |
| { |
| ParseNodePtr propertyNode = lhs->AsParseNodeBin()->pnode2; |
| if (propertyNode->nop == knopName) |
| { |
| propertyNode->AsParseNodeName()->pid->PromoteAssignmentState(); |
| } |
| } |
| } |
| else if (nodeType & fnopUni) |
| { |
| // cases like obj.a++, ++obj.a |
| ParseNodePtr lhs = pnode->AsParseNodeUni()->pnode1; |
| if (lhs->nop == knopDot) |
| { |
| ParseNodePtr propertyNode = lhs->AsParseNodeBin()->pnode2; |
| if (propertyNode->nop == knopName) |
| { |
| propertyNode->AsParseNodeName()->pid->PromoteAssignmentState(); |
| } |
| } |
| } |
| } |
| } |
| return pnode; |
| } |
| |
| template<bool buildAST> |
| void Parser::TrackAssignment(ParseNodePtr pnodeT, IdentToken* pToken) |
| { |
| if (buildAST) |
| { |
| Assert(pnodeT != nullptr); |
| if (pnodeT->nop == knopName) |
| { |
| PidRefStack *ref = pnodeT->AsParseNodeName()->pid->GetTopRef(); |
| Assert(ref); |
| ref->isAsg = true; |
| } |
| } |
| else |
| { |
| Assert(pToken != nullptr); |
| if (pToken->tk == tkID) |
| { |
| PidRefStack *ref = pToken->pid->GetTopRef(); |
| Assert(ref); |
| ref->isAsg = true; |
| } |
| } |
| } |
| |
| PidRefStack* Parser::PushPidRef(IdentPtr pid) |
| { |
| ParseNodeFnc* currentFnc = GetCurrentFunctionNode(); |
| |
| if (this->IsCreatingStateCache()) |
| { |
| IdentPtrSet* capturedNames = currentFnc->EnsureCapturedNames(&m_nodeAllocator); |
| capturedNames->AddNew(pid); |
| } |
| |
| if (PHASE_ON1(Js::ParallelParsePhase)) |
| { |
| // NOTE: the phase check is here to protect perf. See OSG 1020424. |
| // In some LS AST-rewrite cases we lose a lot of perf searching the PID ref stack rather |
| // than just pushing on the top. This hasn't shown up as a perf issue in non-LS benchmarks. |
| return pid->FindOrAddPidRef(&m_nodeAllocator, GetCurrentBlock()->blockId, currentFnc->functionId); |
| } |
| |
| Assert(GetCurrentBlock() != nullptr); |
| AssertMsg(pid != nullptr, "PID should be created"); |
| PidRefStack *ref = pid->GetTopRef(m_nextBlockId - 1); |
| int blockId = GetCurrentBlock()->blockId; |
| int funcId = currentFnc->functionId; |
| if (!ref || (ref->GetScopeId() < blockId)) |
| { |
| ref = Anew(&m_nodeAllocator, PidRefStack); |
| if (ref == nullptr) |
| { |
| Error(ERRnoMemory); |
| } |
| pid->PushPidRef(blockId, funcId, ref); |
| } |
| else if (m_reparsingLambdaParams) |
| { |
| // If we're reparsing params, then we may have pid refs left behind from the first pass. Make sure we're |
| // working with the right ref at this point. |
| ref = this->FindOrAddPidRef(pid, blockId, funcId); |
| // Fix up the function ID if we're reparsing lambda parameters. |
| ref->funcId = funcId; |
| } |
| |
| return ref; |
| } |
| |
| PidRefStack* Parser::FindOrAddPidRef(IdentPtr pid, int scopeId, Js::LocalFunctionId funcId) |
| { |
| PidRefStack *ref = pid->FindOrAddPidRef(&m_nodeAllocator, scopeId, funcId); |
| if (ref == NULL) |
| { |
| Error(ERRnoMemory); |
| } |
| return ref; |
| } |
| |
| void Parser::RemovePrevPidRef(IdentPtr pid, PidRefStack *ref) |
| { |
| PidRefStack *prevRef = pid->RemovePrevPidRef(ref); |
| Assert(prevRef); |
| if (prevRef->GetSym() == nullptr) |
| { |
| AllocatorDelete(ArenaAllocator, &m_nodeAllocator, prevRef); |
| } |
| } |
| |
| void Parser::SetPidRefsInScopeDynamic(IdentPtr pid, int blockId) |
| { |
| PidRefStack *ref = pid->GetTopRef(); |
| while (ref && ref->GetScopeId() >= blockId) |
| { |
| ref->SetDynamicBinding(); |
| ref = ref->prev; |
| } |
| } |
| |
| ParseNodeBlock* Parser::GetFunctionBlock() |
| { |
| Assert(m_currentBlockInfo != nullptr); |
| return m_currentBlockInfo->pBlockInfoFunction->pnodeBlock; |
| } |
| |
| |
| ParseNodeBlock* Parser::GetCurrentBlock() |
| { |
| return m_currentBlockInfo != nullptr ? m_currentBlockInfo->pnodeBlock : nullptr; |
| } |
| |
| BlockInfoStack* Parser::GetCurrentBlockInfo() |
| { |
| return m_currentBlockInfo; |
| } |
| |
| BlockInfoStack* Parser::GetCurrentFunctionBlockInfo() |
| { |
| return m_currentBlockInfo->pBlockInfoFunction; |
| } |
| |
| /*************************************************************************** |
| Parse a variable declaration. |
| 'fAllowIn' indicates if the 'in' operator should be allowed in the initializing |
| expression ( it is not allowed in the context of the first expression in a 'for' loop). |
| ***************************************************************************/ |
| template<bool buildAST> |
| ParseNodePtr Parser::ParseVariableDeclaration( |
| tokens declarationType, charcount_t ichMin, |
| BOOL fAllowIn/* = TRUE*/, |
| BOOL* pfForInOk/* = nullptr*/, |
| BOOL singleDefOnly/* = FALSE*/, |
| BOOL allowInit/* = TRUE*/, |
| BOOL isTopVarParse/* = TRUE*/, |
| BOOL isFor/* = FALSE*/, |
| BOOL* nativeForOk /*= nullptr*/) |
| { |
| ParseNodePtr pnodeThis = nullptr; |
| ParseNodePtr pnodeInit; |
| ParseNodePtr pnodeList = nullptr; |
| ParseNodePtr *lastNodeRef = nullptr; |
| LPCOLESTR pNameHint = nullptr; |
| uint32 nameHintLength = 0; |
| uint32 nameHintOffset = 0; |
| Assert(declarationType == tkVAR || declarationType == tkCONST || declarationType == tkLET); |
| |
| for (;;) |
| { |
| if (IsES6DestructuringEnabled() && IsPossiblePatternStart()) |
| { |
| pnodeThis = ParseDestructuredLiteral<buildAST>(declarationType, true, !!isTopVarParse, DIC_None, !!fAllowIn, pfForInOk, nativeForOk); |
| if (pnodeThis != nullptr) |
| { |
| pnodeThis->ichMin = ichMin; |
| pnodeThis->SetIsPatternDeclaration(); |
| } |
| } |
| else |
| { |
| if (m_token.tk != tkID) |
| { |
| IdentifierExpectedError(m_token); |
| } |
| |
| IdentPtr pid = m_token.GetIdentifier(this->GetHashTbl()); |
| Assert(pid); |
| pNameHint = pid->Psz(); |
| nameHintLength = pid->Cch(); |
| nameHintOffset = 0; |
| |
| if (pid == wellKnownPropertyPids.let && (declarationType == tkCONST || declarationType == tkLET)) |
| { |
| Error(ERRLetIDInLexicalDecl, pnodeThis); |
| } |
| |
| if (declarationType == tkVAR) |
| { |
| pnodeThis = CreateVarDeclNode(pid, STVariable); |
| } |
| else if (declarationType == tkCONST) |
| { |
| pnodeThis = CreateBlockScopedDeclNode(pid, knopConstDecl); |
| CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, Const, m_scriptContext); |
| } |
| else |
| { |
| pnodeThis = CreateBlockScopedDeclNode(pid, knopLetDecl); |
| CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, Let, m_scriptContext); |
| } |
| |
| if (pid == wellKnownPropertyPids.arguments) |
| { |
| // This var declaration may change the way an 'arguments' identifier in the function is resolved |
| if (declarationType == tkVAR) |
| { |
| GetCurrentFunctionNode()->grfpn |= PNodeFlags::fpnArguments_varDeclaration; |
| } |
| else |
| { |
| if (GetCurrentBlockInfo()->pnodeBlock->blockType == Function) |
| { |
| // Only override arguments if we are at the function block level. |
| GetCurrentFunctionNode()->grfpn |= PNodeFlags::fpnArguments_overriddenByDecl; |
| } |
| } |
| } |
| |
| if (pnodeThis) |
| { |
| pnodeThis->ichMin = ichMin; |
| } |
| |
| this->GetScanner()->Scan(); |
| |
| if (m_token.tk == tkAsg) |
| { |
| if (!allowInit) |
| { |
| Error(ERRUnexpectedDefault); |
| } |
| if (pfForInOk && (declarationType == tkLET || declarationType == tkCONST || IsStrictMode())) |
| { |
| *pfForInOk = FALSE; |
| } |
| |
| this->GetScanner()->Scan(); |
| pnodeInit = ParseExpr<buildAST>(koplCma, nullptr, fAllowIn, FALSE, pNameHint, &nameHintLength, &nameHintOffset); |
| if (buildAST) |
| { |
| AnalysisAssert(pnodeThis); |
| pnodeThis->AsParseNodeVar()->pnodeInit = pnodeInit; |
| pnodeThis->ichLim = pnodeInit->ichLim; |
| |
| if (pnodeInit->nop == knopFncDecl) |
| { |
| Assert(nameHintLength >= nameHintOffset); |
| pnodeInit->AsParseNodeFnc()->hint = pNameHint; |
| pnodeInit->AsParseNodeFnc()->hintLength = nameHintLength; |
| pnodeInit->AsParseNodeFnc()->hintOffset = nameHintOffset; |
| pnodeThis->AsParseNodeVar()->pid->GetTopRef()->isFuncAssignment = true; |
| } |
| else |
| { |
| this->CheckArguments(pnodeInit); |
| } |
| pNameHint = nullptr; |
| } |
| |
| //Track var a =, let a= , const a = |
| // This is for FixedFields Constant Heuristics |
| if (pnodeThis && pnodeThis->AsParseNodeVar()->pnodeInit != nullptr) |
| { |
| pnodeThis->AsParseNodeVar()->sym->PromoteAssignmentState(); |
| } |
| } |
| else if (declarationType == tkCONST /*pnodeThis->nop == knopConstDecl*/ |
| && !singleDefOnly |
| && !(isFor && TokIsForInOrForOf())) |
| { |
| Error(ERRUninitializedConst); |
| } |
| |
| if (m_currentNodeFunc && pnodeThis && pnodeThis->AsParseNodeVar()->sym->GetIsFormal()) |
| { |
| m_currentNodeFunc->SetHasAnyWriteToFormals(true); |
| } |
| } |
| |
| if (singleDefOnly) |
| { |
| return pnodeThis; |
| } |
| |
| if (buildAST) |
| { |
| AddToNodeListEscapedUse(&pnodeList, &lastNodeRef, pnodeThis); |
| } |
| |
| if (m_token.tk != tkComma) |
| { |
| return pnodeList; |
| } |
| |
| if (pfForInOk) |
| { |
| // don't allow "for (var a, b in c)" |
| *pfForInOk = FALSE; |
| } |
| this->GetScanner()->Scan(); |
| ichMin = this->GetScanner()->IchMinTok(); |
| } |
| } |
| |
| /*************************************************************************** |
| Parse try-catch-finally statement |
| ***************************************************************************/ |
| |
| // The try-catch-finally tree nests the try-catch within a try-finally. |
| // This matches the new runtime implementation. |
| template<bool buildAST> |
| ParseNodeStmt * Parser::ParseTryCatchFinally() |
| { |
| this->m_tryCatchOrFinallyDepth++; |
| |
| ParseNodeTry * pnodeT = ParseTry<buildAST>(); |
| ParseNodeTryCatch * pnodeTC = nullptr; |
| StmtNest stmt; |
| bool hasCatch = false; |
| |
| if (tkCATCH == m_token.tk) |
| { |
| hasCatch = true; |
| if (buildAST) |
| { |
| pnodeTC = CreateNodeForOpT<knopTryCatch>(); |
| pnodeT->pnodeOuter = pnodeTC; |
| pnodeTC->pnodeTry = pnodeT; |
| } |
| PushStmt<buildAST>(&stmt, pnodeTC, knopTryCatch, nullptr); |
| |
| ParseNodeCatch * pnodeCatch = ParseCatch<buildAST>(); |
| if (buildAST) |
| { |
| pnodeTC->pnodeCatch = pnodeCatch; |
| } |
| PopStmt(&stmt); |
| } |
| if (tkFINALLY != m_token.tk) |
| { |
| if (!hasCatch) |
| { |
| Error(ERRnoCatch); |
| } |
| Assert(!buildAST || pnodeTC); |
| this->m_tryCatchOrFinallyDepth--; |
| return pnodeTC; |
| } |
| |
| ParseNodeTryFinally * pnodeTF = nullptr; |
| if (buildAST) |
| { |
| pnodeTF = CreateNodeForOpT<knopTryFinally>(); |
| } |
| PushStmt<buildAST>(&stmt, pnodeTF, knopTryFinally, nullptr); |
| ParseNodeFinally * pnodeFinally = ParseFinally<buildAST>(); |
| if (buildAST) |
| { |
| if (!hasCatch) |
| { |
| pnodeTF->pnodeTry = pnodeT; |
| pnodeT->pnodeOuter = pnodeTF; |
| } |
| else |
| { |
| pnodeTF->pnodeTry = CreateNodeForOpT<knopTry>(); |
| pnodeTF->pnodeTry->pnodeOuter = pnodeTF; |
| pnodeTF->pnodeTry->pnodeBody = pnodeTC; |
| pnodeTC->pnodeOuter = pnodeTF->pnodeTry; |
| } |
| pnodeTF->pnodeFinally = pnodeFinally; |
| } |
| PopStmt(&stmt); |
| this->m_tryCatchOrFinallyDepth--; |
| return pnodeTF; |
| } |
| |
| template<bool buildAST> |
| ParseNodeTry * Parser::ParseTry() |
| { |
| ParseNodeTry * pnode = nullptr; |
| StmtNest stmt; |
| Assert(tkTRY == m_token.tk); |
| if (buildAST) |
| { |
| pnode = CreateNodeForOpT<knopTry>(); |
| } |
| this->GetScanner()->Scan(); |
| if (tkLCurly != m_token.tk) |
| { |
| Error(ERRnoLcurly); |
| } |
| |
| PushStmt<buildAST>(&stmt, pnode, knopTry, nullptr); |
| ParseNodePtr pnodeBody = ParseStatement<buildAST>(); |
| if (buildAST) |
| { |
| pnode->pnodeBody = pnodeBody; |
| if (pnode->pnodeBody) |
| pnode->ichLim = pnode->pnodeBody->ichLim; |
| } |
| PopStmt(&stmt); |
| return pnode; |
| } |
| |
| template<bool buildAST> |
| ParseNodeFinally * Parser::ParseFinally() |
| { |
| ParseNodeFinally * pnode = nullptr; |
| StmtNest stmt; |
| Assert(tkFINALLY == m_token.tk); |
| if (buildAST) |
| { |
| pnode = CreateNodeForOpT<knopFinally>(); |
| } |
| this->GetScanner()->Scan(); |
| if (tkLCurly != m_token.tk) |
| { |
| Error(ERRnoLcurly); |
| } |
| |
| PushStmt<buildAST>(&stmt, pnode, knopFinally, nullptr); |
| ParseNodePtr pnodeBody = ParseStatement<buildAST>(); |
| if (buildAST) |
| { |
| pnode->pnodeBody = pnodeBody; |
| if (!pnode->pnodeBody) |
| // Will only occur due to error correction. |
| pnode->pnodeBody = CreateNodeForOpT<knopEmpty>(); |
| else |
| pnode->ichLim = pnode->pnodeBody->ichLim; |
| } |
| PopStmt(&stmt); |
| |
| return pnode; |
| } |
| |
| template<bool buildAST> |
| ParseNodeCatch * Parser::ParseCatch() |
| { |
| ParseNodePtr *ppnodeExprScopeSave = nullptr; |
| ParseNodeCatch * pnode = nullptr; |
| ParseNodeBlock * pnodeCatchScope = nullptr; |
| StmtNest stmt; |
| IdentPtr pidCatch = nullptr; |
| |
| if (tkCATCH == m_token.tk) |
| { |
| charcount_t ichMin; |
| if (buildAST) |
| { |
| ichMin = this->GetScanner()->IchMinTok(); |
| } |
| this->GetScanner()->Scan(); //catch |
| |
| bool isPattern = false; |
| bool hasParam = false; |
| |
| if (tkLParen == m_token.tk) |
| { |
| hasParam = true; |
| this->GetScanner()->Scan(); //catch( |
| if (tkID != m_token.tk) |
| { |
| isPattern = IsES6DestructuringEnabled() && IsPossiblePatternStart(); |
| if (!isPattern) |
| { |
| IdentifierExpectedError(m_token); |
| } |
| } |
| } |
| |
| if (buildAST) |
| { |
| pnode = CreateNodeForOpT<knopCatch>(ichMin); |
| pnode->pnodeNext = nullptr; |
| PushStmt<buildAST>(&stmt, pnode, knopCatch, nullptr); |
| } |
| |
| pnodeCatchScope = StartParseBlock<buildAST>(PnodeBlockType::Regular, isPattern ? ScopeType_CatchParamPattern : ScopeType_Catch); |
| |
| if (buildAST) |
| { |
| // Add this catch to the current scope list. |
| |
| if (m_ppnodeExprScope) |
| { |
| Assert(*m_ppnodeExprScope == nullptr); |
| *m_ppnodeExprScope = pnode; |
| m_ppnodeExprScope = &pnode->pnodeNext; |
| } |
| else |
| { |
| Assert(m_ppnodeScope); |
| Assert(*m_ppnodeScope == nullptr); |
| *m_ppnodeScope = pnode; |
| m_ppnodeScope = &pnode->pnodeNext; |
| } |
| |
| // Keep a list of function expressions (not declarations) at this scope. |
| |
| ppnodeExprScopeSave = m_ppnodeExprScope; |
| m_ppnodeExprScope = &pnode->pnodeScopes; |
| pnode->pnodeScopes = nullptr; |
| } |
| |
| if (isPattern) |
| { |
| ParseNodePtr pnodePattern = ParseDestructuredLiteral<buildAST>(tkLET, true /*isDecl*/, true /*topLevel*/, DIC_ForceErrorOnInitializer); |
| if (buildAST) |
| { |
| pnode->SetParam(CreateParamPatternNode(pnodePattern)); |
| Scope *scope = pnodeCatchScope->scope; |
| pnode->scope = scope; |
| } |
| } |
| else if (hasParam) |
| { |
| if (IsStrictMode()) |
| { |
| IdentPtr pid = m_token.GetIdentifier(this->GetHashTbl()); |
| if (pid == wellKnownPropertyPids.eval) |
| { |
| Error(ERREvalUsage); |
| } |
| else if (pid == wellKnownPropertyPids.arguments) |
| { |
| Error(ERRArgsUsage); |
| } |
| } |
| |
| pidCatch = m_token.GetIdentifier(this->GetHashTbl()); |
| PidRefStack *ref = this->FindOrAddPidRef(pidCatch, GetCurrentBlock()->blockId, GetCurrentFunctionNode()->functionId); |
| |
| ParseNodeName * pnodeParam = CreateNameNode(pidCatch); |
| pnodeParam->SetSymRef(ref); |
| |
| const char16 *name = reinterpret_cast<const char16*>(pidCatch->Psz()); |
| int nameLength = pidCatch->Cch(); |
| SymbolName const symName(name, nameLength); |
| Symbol *sym = Anew(&m_nodeAllocator, Symbol, symName, pnodeParam, STVariable); |
| if (sym == nullptr) |
| { |
| Error(ERRnoMemory); |
| } |
| sym->SetPid(pidCatch); |
| Assert(ref->GetSym() == nullptr); |
| ref->SetSym(sym); |
| |
| Scope *scope = pnodeCatchScope->scope; |
| scope->AddNewSymbol(sym); |
| |
| if (buildAST) |
| { |
| pnode->SetParam(pnodeParam); |
| pnode->scope = scope; |
| } |
| |
| this->GetScanner()->Scan(); |
| } |
| else |
| { |
| if (buildAST) |
| { |
| pnode->scope = pnodeCatchScope->scope; |
| } |
| } |
| |
| charcount_t ichLim; |
| if (buildAST) |
| { |
| ichLim = this->GetScanner()->IchLimTok(); |
| } |
| |
| if (hasParam) |
| { |
| ChkCurTok(tkRParen, ERRnoRparen); //catch(id[:expr]) |
| } |
| |
| if (tkLCurly != m_token.tk) |
| { |
| Error(ERRnoLcurly); |
| } |
| |
| ParseNodePtr pnodeBody = ParseStatement<buildAST>(); //catch(id[:expr]) {block} |
| if (buildAST) |
| { |
| pnode->pnodeBody = pnodeBody; |
| pnode->ichLim = ichLim; |
| } |
| |
| if (pnodeCatchScope != nullptr) |
| { |
| FinishParseBlock(pnodeCatchScope); |
| } |
| |
| if (pnodeCatchScope->GetCallsEval() || pnodeCatchScope->GetChildCallsEval()) |
| { |
| GetCurrentBlock()->SetChildCallsEval(true); |
| } |
| |
| if (buildAST) |
| { |
| PopStmt(&stmt); |
| |
| // Restore the lists of function expression scopes. |
| |
| Assert(m_ppnodeExprScope); |
| Assert(*m_ppnodeExprScope == nullptr); |
| m_ppnodeExprScope = ppnodeExprScopeSave; |
| } |
| } |
| return pnode; |
| } |
| |
| template<bool buildAST> |
| ParseNodeCase * Parser::ParseCase(ParseNodePtr *ppnodeBody) |
| { |
| ParseNodeCase * pnodeT = nullptr; |
| |
| charcount_t ichMinT = this->GetScanner()->IchMinTok(); |
| this->GetScanner()->Scan(); |
| ParseNodePtr pnodeExpr = ParseExpr<buildAST>(); |
| charcount_t ichLim = this->GetScanner()->IchLimTok(); |
| |
| ChkCurTok(tkColon, ERRnoColon); |
| |
| if (buildAST) |
| { |
| pnodeT = CreateNodeForOpT<knopCase>(ichMinT); |
| pnodeT->pnodeExpr = pnodeExpr; |
| pnodeT->ichLim = ichLim; |
| } |
| ParseStmtList<buildAST>(ppnodeBody); |
| |
| return pnodeT; |
| } |
| |
| /*************************************************************************** |
| Parse a single statement. Digest a trailing semicolon. |
| ***************************************************************************/ |
| template<bool buildAST> |
| ParseNodePtr Parser::ParseStatement() |
| { |
| ParseNodePtr pnode = nullptr; |
| LabelId* pLabelIdList = nullptr; |
| charcount_t ichMin = 0; |
| size_t iecpMin = 0; |
| StmtNest stmt; |
| StmtNest *pstmt; |
| BOOL fForInOrOfOkay; |
| BOOL fCanAssign; |
| IdentPtr pid; |
| uint fnop; |
| bool expressionStmt = false; |
| bool isAsyncMethod = false; |
| bool labelledStatement = false; |
| tokens tok; |
| #if EXCEPTION_RECOVERY |
| ParseNodeTryCatch * pParentTryCatch = nullptr; |
| ParseNodeBlock * pTryBlock = nullptr; |
| ParseNodeTry * pTry = nullptr; |
| ParseNodeBlock * pParentTryCatchBlock = nullptr; |
| |
| StmtNest stmtTryCatchBlock; |
| StmtNest stmtTryCatch; |
| StmtNest stmtTry; |
| StmtNest stmtTryBlock; |
| #endif |
| |
| if (buildAST) |
| { |
| #if EXCEPTION_RECOVERY |
| if (Js::Configuration::Global.flags.SwallowExceptions) |
| { |
| // If we're swallowing exceptions, surround this statement with a try/catch block: |
| // |
| // Before: x.y = 3; |
| // After: try { x.y = 3; } catch(__ehobj) { } |
| // |
| // This is done to force the runtime to recover from exceptions at the most granular |
| // possible point. Recovering from EH dramatically improves coverage of testing via |
| // fault injection. |
| |
| |
| // create and push the try-catch node |
| pParentTryCatchBlock = CreateBlockNode(); |
| PushStmt<buildAST>(&stmtTryCatchBlock, pParentTryCatchBlock, knopBlock, nullptr); |
| pParentTryCatch = CreateNodeForOpT<knopTryCatch>(); |
| PushStmt<buildAST>(&stmtTryCatch, pParentTryCatch, knopTryCatch, nullptr); |
| |
| // create and push a try node |
| pTry = CreateNodeForOpT<knopTry>(); |
| PushStmt<buildAST>(&stmtTry, pTry, knopTry, nullptr); |
| pTryBlock = CreateBlockNode(); |
| PushStmt<buildAST>(&stmtTryBlock, pTryBlock, knopBlock, nullptr); |
| // these nodes will be closed after the statement is parsed. |
| } |
| #endif // EXCEPTION_RECOVERY |
| } |
| |
| EnsureStackAvailable(); |
| |
| LRestart: |
| tok = m_token.tk; |
| |
| switch (tok) |
| { |
| case tkEOF: |
| if (labelledStatement) |
| { |
| Error(ERRLabelFollowedByEOF); |
| } |
| if (buildAST) |
| { |
| pnode = nullptr; |
| } |
| break; |
| |
| case tkFUNCTION: |
| { |
| LFunctionStatement: |
| if (m_grfscr & fscrDeferredFncExpression) |
| { |
| // The top-level deferred function body was defined by a function expression whose parsing was deferred. We are now |
| // parsing it, so unset the flag so that any nested functions are parsed normally. This flag is only applicable the |
| // first time we see it. |
| m_grfscr &= ~fscrDeferredFncExpression; |
| pnode = ParseFncDeclNoCheckScope<buildAST>(isAsyncMethod ? fFncAsync : fFncNoFlgs); |
| } |
| else |
| { |
| pnode = ParseFncDeclCheckScope<buildAST>(fFncDeclaration | (isAsyncMethod ? fFncAsync : fFncNoFlgs)); |
| } |
| |
| Assert(pnode != nullptr); |
| |
| if (labelledStatement) |
| { |
| if (IsStrictMode()) |
| { |
| Error(ERRFunctionAfterLabelInStrict); |
| } |
| // #sec-with-statement-static-semantics-early-errors states that the Statement of |
| // a WithStatement throws a Syntax Error if the Statement is a LabelledFunction. |
| else if (m_pstmtCur && m_pstmtCur->pnodeStmt && m_pstmtCur->GetNop() == knopWith) |
| { |
| Error(ERRStmtOfWithIsLabelledFunc); |
| } |
| |
| ParseNodeFnc* pNodeFnc = nullptr; |
| |
| // pnode can be a knopBlock due to ParseFncDeclCheckScope, which |
| // can return a ParseNodeBlock that contains a ParseNodeFnc. |
| if (pnode->nop == knopBlock) |
| { |
| ParseNodeBlock* pNodeBlock = pnode->AsParseNodeBlock(); |
| if (pNodeBlock->pnodeStmt && pNodeBlock->pnodeStmt->nop == knopFncDecl) |
| { |
| pNodeFnc = pNodeBlock->pnodeStmt->AsParseNodeFnc(); |
| } |
| } |
| if (pNodeFnc == nullptr) |
| { |
| pNodeFnc = pnode->AsParseNodeFnc(); |
| } |
| |
| if (pNodeFnc->IsAsync()) |
| { |
| Error(ERRLabelBeforeAsyncFncDeclaration); |
| } |
| else if (pNodeFnc->IsGenerator()) |
| { |
| Error(ERRLabelBeforeGeneratorDeclaration); |
| } |
| } |
| |
| if (isAsyncMethod) |
| { |
| pnode->AsParseNodeFnc()->cbStringMin = iecpMin; |
| } |
| break; |
| } |
| |
| case tkCLASS: |
| if (labelledStatement) |
| { |
| Error(ERRLabelBeforeClassDeclaration); |
| } |
| else if (m_scriptContext->GetConfig()->IsES6ClassAndExtendsEnabled()) |
| { |
| pnode = ParseClassDecl<buildAST>(TRUE, nullptr, nullptr, nullptr); |
| } |
| else |
| { |
| goto LDefaultToken; |
| } |
| break; |
| |
| case tkID: |
| case tkLET: |
| if (m_token.GetIdentifier(this->GetHashTbl()) == wellKnownPropertyPids.let) |
| { |
| // We see "let" at the start of a statement. This could either be a declaration or an identifier |
| // reference. The next token determines which. |
| RestorePoint parsedLet; |
| this->GetScanner()->Capture(&parsedLet); |
| ichMin = this->GetScanner()->IchMinTok(); |
| |
| this->GetScanner()->Scan(); |
| if (labelledStatement) |
| { |
| if (!this->GetScanner()->FHadNewLine() || m_token.tk == tkLBrack) |
| { |
| // In the case where a label is followed by a let, we want to fail when parsing if there is no new line after let, |
| // otherwise fail at runtime as let will be viewed as undefined. A left bracket after a let signifies a syntax error regardless. |
| Error(ERRLabelBeforeLexicalDeclaration); |
| } |
| } |
| else if (this->NextTokenConfirmsLetDecl()) |
| { |
| pnode = ParseVariableDeclaration<buildAST>(tkLET, ichMin); |
| goto LNeedTerminator; |
| } |
| this->GetScanner()->SeekTo(parsedLet); |
| } |
| else if (m_token.GetIdentifier(this->GetHashTbl()) == wellKnownPropertyPids.async && m_scriptContext->GetConfig()->IsES7AsyncAndAwaitEnabled()) |
| { |
| RestorePoint parsedAsync; |
| this->GetScanner()->Capture(&parsedAsync); |
| ichMin = this->GetScanner()->IchMinTok(); |
| iecpMin = this->GetScanner()->IecpMinTok(); |
| |
| this->GetScanner()->Scan(); |
| if (m_token.tk == tkFUNCTION && !this->GetScanner()->FHadNewLine()) |
| { |
| isAsyncMethod = true; |
| goto LFunctionStatement; |
| } |
| this->GetScanner()->SeekTo(parsedAsync); |
| } |
| goto LDefaultToken; |
| |
| case tkCONST: |
| if (labelledStatement) |
| { |
| Error(ERRLabelBeforeLexicalDeclaration); |
| } |
| ichMin = this->GetScanner()->IchMinTok(); |
| |
| this->GetScanner()->Scan(); |
| pnode = ParseVariableDeclaration<buildAST>(tok, ichMin); |
| goto LNeedTerminator; |
| |
| case tkVAR: |
| ichMin = this->GetScanner()->IchMinTok(); |
| |
| this->GetScanner()->Scan(); |
| pnode = ParseVariableDeclaration<buildAST>(tok, ichMin); |
| goto LNeedTerminator; |
| |
| case tkFOR: |
| { |
| ParseNodeBlock * pnodeBlock = nullptr; |
| ParseNodePtr *ppnodeScopeSave = nullptr; |
| ParseNodePtr *ppnodeExprScopeSave = nullptr; |
| |
| ichMin = this->GetScanner()->IchMinTok(); |
| ChkNxtTok(tkLParen, ERRnoLparen); |
| pnodeBlock = StartParseBlock<buildAST>(PnodeBlockType::Regular, ScopeType_Block); |
| if (buildAST) |
| { |
| PushFuncBlockScope(pnodeBlock, &ppnodeScopeSave, &ppnodeExprScopeSave); |
| } |
| |
| RestorePoint startExprOrIdentifier; |
| fForInOrOfOkay = TRUE; |
| fCanAssign = TRUE; |
| tok = m_token.tk; |
| BOOL nativeForOkay = TRUE; |
| |
| ParseNodePtr pnodeT; |
| switch (tok) |
| { |
| case tkID: |
| if (m_token.GetIdentifier(this->GetHashTbl()) == wellKnownPropertyPids.let) |
| { |
| // We see "let" in the init part of a for loop. This could either be a declaration or an identifier |
| // reference. The next token determines which. |
| RestorePoint parsedLet; |
| this->GetScanner()->Capture(&parsedLet); |
| auto ichMinInner = this->GetScanner()->IchMinTok(); |
| |
| this->GetScanner()->Scan(); |
| if (IsPossiblePatternStart()) |
| { |
| this->GetScanner()->Capture(&startExprOrIdentifier); |
| } |
| if (this->NextTokenConfirmsLetDecl() && m_token.tk != tkIN) |
| { |
| pnodeT = ParseVariableDeclaration<buildAST>(tkLET, ichMinInner |
| , /*fAllowIn = */FALSE |
| , /*pfForInOk = */&fForInOrOfOkay |
| , /*singleDefOnly*/FALSE |
| , /*allowInit*/TRUE |
| , /*isTopVarParse*/TRUE |
| , /*isFor*/TRUE |
| , &nativeForOkay); |
| break; |
| } |
| this->GetScanner()->SeekTo(parsedLet); |
| } |
| goto LDefaultTokenFor; |
| case tkLET: |
| case tkCONST: |
| case tkVAR: |
| { |
| auto ichMinInner = this->GetScanner()->IchMinTok(); |
| |
| this->GetScanner()->Scan(); |
| if (IsPossiblePatternStart()) |
| { |
| this->GetScanner()->Capture(&startExprOrIdentifier); |
| } |
| pnodeT = ParseVariableDeclaration<buildAST>(tok, ichMinInner |
| , /*fAllowIn = */FALSE |
| , /*pfForInOk = */&fForInOrOfOkay |
| , /*singleDefOnly*/FALSE |
| , /*allowInit*/TRUE |
| , /*isTopVarParse*/TRUE |
| , /*isFor*/TRUE |
| , &nativeForOkay); |
| } |
| break; |
| case tkSColon: |
| pnodeT = nullptr; |
| fForInOrOfOkay = FALSE; |
| break; |
| default: |
| { |
| LDefaultTokenFor: |
| RestorePoint exprStart; |
| tokens beforeToken = tok; |
| this->GetScanner()->Capture(&exprStart); |
| if (IsPossiblePatternStart()) |
| { |
| this->GetScanner()->Capture(&startExprOrIdentifier); |
| } |
| bool fLikelyPattern = false; |
| if (IsES6DestructuringEnabled() && (beforeToken == tkLBrack || beforeToken == tkLCurly)) |
| { |
| pnodeT = ParseExpr<buildAST>(koplNo, |
| &fCanAssign, |
| /*fAllowIn = */FALSE, |
| /*fAllowEllipsis*/FALSE, |
| /*pHint*/nullptr, |
| /*pHintLength*/nullptr, |
| /*pShortNameOffset*/nullptr, |
| /*pToken*/nullptr, |
| /**fUnaryOrParen*/false, |
| &fLikelyPattern); |
| } |
| else |
| { |
| IdentToken token; |
| pnodeT = ParseExpr<buildAST>(koplNo, &fCanAssign, /*fAllowIn = */FALSE, FALSE, NULL, nullptr, nullptr, &token); |
| TrackAssignment<buildAST>(pnodeT, &token); |
| } |
| |
| // We would veryfiy the grammar as destructuring grammar only when for..in/of case. As in the native for loop case the above ParseExpr call |
| // has already converted them appropriately. |
| if (fLikelyPattern && TokIsForInOrForOf()) |
| { |
| this->GetScanner()->SeekTo(exprStart); |
| ParseDestructuredLiteralWithScopeSave(tkNone, false/*isDecl*/, false /*topLevel*/, DIC_None, false /*allowIn*/); |
| |
| if (buildAST) |
| { |
| pnodeT = ConvertToPattern(pnodeT); |
| } |
| } |
| if (buildAST) |
| { |
| Assert(pnodeT); |
| pnodeT->isUsed = false; |
| } |
| } |
| break; |
| } |
| |
| if (TokIsForInOrForOf()) |
| { |
| bool isForOf = (m_token.tk != tkIN); |
| Assert(!isForOf || (m_token.tk == tkID && m_token.GetIdentifier(this->GetHashTbl()) == wellKnownPropertyPids.of)); |
| |
| if ((buildAST && nullptr == pnodeT) || !fForInOrOfOkay) |
| { |
| if (isForOf) |
| { |
| Error(ERRForOfNoInitAllowed); |
| } |
| else |
| { |
| Error(ERRForInNoInitAllowed); |
| } |
| } |
| if (!fCanAssign && |
| (m_sourceContextInfo |
| ? !PHASE_OFF_RAW(Js::EarlyReferenceErrorsPhase, m_sourceContextInfo->sourceContextId, GetCurrentFunctionNode()->functionId) |
| : !PHASE_OFF1(Js::EarlyReferenceErrorsPhase))) |
| { |
| Error(ERRInvalidLHSInFor); |
| } |
| |
| this->GetScanner()->Scan(); |
| ParseNodePtr pnodeObj = ParseExpr<buildAST>(isForOf ? koplCma : koplNo); |
| charcount_t ichLim = this->GetScanner()->IchLimTok(); |
| ChkCurTok(tkRParen, ERRnoRparen); |
| |
| ParseNodeForInOrForOf * pnodeForInOrForOf = nullptr; |
| if (buildAST) |
| { |
| if (isForOf) |
| { |
| pnodeForInOrForOf = CreateNodeForOpT<knopForOf>(ichMin); |
| } |
| else |
| { |
| pnodeForInOrForOf = CreateNodeForOpT<knopForIn>(ichMin); |
| } |
| pnodeForInOrForOf->pnodeBlock = pnodeBlock; |
| pnodeForInOrForOf->pnodeLval = pnodeT; |
| pnodeForInOrForOf->pnodeObj = pnodeObj; |
| pnodeForInOrForOf->ichLim = ichLim; |
| |
| TrackAssignment<true>(pnodeT, nullptr); |
| } |
| PushStmt<buildAST>(&stmt, pnodeForInOrForOf, isForOf ? knopForOf : knopForIn, pLabelIdList); |
| ParseNodePtr pnodeBody = ParseStatement<buildAST>(); |
| |
| if (buildAST) |
| { |
| pnodeForInOrForOf->pnodeBody = pnodeBody; |
| pnode = pnodeForInOrForOf; |
| } |
| PopStmt(&stmt); |
| } |
| else |
| { |
| if (!nativeForOkay) |
| { |
| Error(ERRDestructInit); |
| } |
| |
| ChkCurTok(tkSColon, ERRnoSemic); |
| ParseNodePtr pnodeCond = nullptr; |
| if (m_token.tk != tkSColon) |
| { |
| pnodeCond = ParseExpr<buildAST>(); |
| if (m_token.tk != tkSColon) |
| { |
| Error(ERRnoSemic); |
| } |
| } |
| |
| tokens tk; |
| tk = this->GetScanner()->Scan(); |
| |
| ParseNodePtr pnodeIncr = nullptr; |
| if (tk != tkRParen) |
| { |
| pnodeIncr = ParseExpr<buildAST>(); |
| if (pnodeIncr) |
| { |
| pnodeIncr->isUsed = false; |
| } |
| } |
| |
| charcount_t ichLim = this->GetScanner()->IchLimTok(); |
| |
| ChkCurTok(tkRParen, ERRnoRparen); |
| |
| ParseNodeFor * pnodeFor = nullptr; |
| if (buildAST) |
| { |
| pnodeFor = CreateNodeForOpT<knopFor>(ichMin); |
| pnodeFor->pnodeBlock = pnodeBlock; |
| pnodeFor->pnodeInverted = nullptr; |
| pnodeFor->pnodeInit = pnodeT; |
| pnodeFor->pnodeCond = pnodeCond; |
| pnodeFor->pnodeIncr = pnodeIncr; |
| pnodeFor->ichLim = ichLim; |
| } |
| PushStmt<buildAST>(&stmt, pnodeFor, knopFor, pLabelIdList); |
| ParseNodePtr pnodeBody = ParseStatement<buildAST>(); |
| if (buildAST) |
| { |
| pnodeFor->pnodeBody = pnodeBody; |
| pnode = pnodeFor; |
| } |
| PopStmt(&stmt); |
| } |
| |
| if (buildAST) |
| { |
| PopFuncBlockScope(ppnodeScopeSave, ppnodeExprScopeSave); |
| } |
| |
| FinishParseBlock(pnodeBlock); |
| |
| break; |
| } |
| |
| case tkSWITCH: |
| { |
| BOOL fSeenDefault = FALSE; |
| ParseNodeBlock * pnodeBlock = nullptr; |
| ParseNodePtr *ppnodeScopeSave = nullptr; |
| ParseNodePtr *ppnodeExprScopeSave = nullptr; |
| |
| ichMin = this->GetScanner()->IchMinTok(); |
| ChkNxtTok(tkLParen, ERRnoLparen); |
| ParseNodePtr pnodeVal = ParseExpr<buildAST>(); |
| charcount_t ichLim = this->GetScanner()->IchLimTok(); |
| |
| ChkCurTok(tkRParen, ERRnoRparen); |
| ChkCurTok(tkLCurly, ERRnoLcurly); |
| |
| ParseNodeSwitch * pnodeSwitch = nullptr; |
| if (buildAST) |
| { |
| pnodeSwitch = CreateNodeForOpT<knopSwitch>(ichMin); |
| } |
| PushStmt<buildAST>(&stmt, pnodeSwitch, knopSwitch, pLabelIdList); |
| pnodeBlock = StartParseBlock<buildAST>(PnodeBlockType::Regular, ScopeType_Block); |
| |
| ParseNodeCase ** ppnodeCase = nullptr; |
| if (buildAST) |
| { |
| pnodeSwitch->pnodeVal = pnodeVal; |
| pnodeSwitch->pnodeBlock = pnodeBlock; |
| pnodeSwitch->ichLim = ichLim; |
| PushFuncBlockScope(pnodeSwitch->pnodeBlock, &ppnodeScopeSave, &ppnodeExprScopeSave); |
| |
| pnodeSwitch->pnodeDefault = nullptr; |
| ppnodeCase = &pnodeSwitch->pnodeCases; |
| pnode = pnodeSwitch; |
| } |
| |
| for (;;) |
| { |
| ParseNodeCase * pnodeCase; |
| ParseNodePtr pnodeBody = nullptr; |
| switch (m_token.tk) |
| { |
| default: |
| goto LEndSwitch; |
| case tkCASE: |
| { |
| pnodeCase = this->ParseCase<buildAST>(&pnodeBody); |
| break; |
| } |
| case tkDEFAULT: |
| if (fSeenDefault) |
| { |
| Error(ERRdupDefault); |
| // No recovery necessary since this is a semantic, not structural, error |
| } |
| fSeenDefault = TRUE; |
| charcount_t ichMinT = this->GetScanner()->IchMinTok(); |
| this->GetScanner()->Scan(); |
| charcount_t ichMinInner = this->GetScanner()->IchLimTok(); |
| ChkCurTok(tkColon, ERRnoColon); |
| if (buildAST) |
| { |
| pnodeCase = CreateNodeForOpT<knopCase>(ichMinT); |
| pnodeSwitch->pnodeDefault = pnodeCase; |
| pnodeCase->ichLim = ichMinInner; |
| pnodeCase->pnodeExpr = nullptr; |
| } |
| ParseStmtList<buildAST>(&pnodeBody); |
| break; |
| } |
| // Create a block node to contain the statement list for this case. |
| // This helps us insert byte code to return the right value from |
| // global/eval code. |
| ParseNodeBlock * pnodeFakeBlock = CreateBlockNode(); |
| if (buildAST) |
| { |
| if (pnodeBody) |
| { |
| pnodeFakeBlock->ichMin = pnodeCase->ichMin; |
| pnodeFakeBlock->ichLim = pnodeCase->ichLim; |
| pnodeCase->pnodeBody = pnodeFakeBlock; |
| pnodeCase->pnodeBody->grfpn |= PNodeFlags::fpnSyntheticNode; // block is not a user specifier block |
| pnodeCase->pnodeBody->pnodeStmt = pnodeBody; |
| } |
| else |
| { |
| pnodeCase->pnodeBody = nullptr; |
| } |
| *ppnodeCase = pnodeCase; |
| ppnodeCase = &pnodeCase->pnodeNext; |
| } |
| } |
| LEndSwitch: |
| ChkCurTok(tkRCurly, ERRnoRcurly); |
| if (buildAST) |
| { |
| *ppnodeCase = nullptr; |
| PopFuncBlockScope(ppnodeScopeSave, ppnodeExprScopeSave); |
| FinishParseBlock(pnode->AsParseNodeSwitch()->pnodeBlock); |
| } |
| else |
| { |
| FinishParseBlock(pnodeBlock); |
| } |
| PopStmt(&stmt); |
| |
| break; |
| } |
| |
| case tkWHILE: |
| { |
| ichMin = this->GetScanner()->IchMinTok(); |
| ChkNxtTok(tkLParen, ERRnoLparen); |
| ParseNodePtr pnodeCond = ParseExpr<buildAST>(); |
| charcount_t ichLim = this->GetScanner()->IchLimTok(); |
| ChkCurTok(tkRParen, ERRnoRparen); |
| |
| ParseNodeWhile * pnodeWhile = nullptr; |
| if (buildAST) |
| { |
| pnodeWhile = CreateNodeForOpT<knopWhile>(ichMin); |
| pnodeWhile->pnodeCond = pnodeCond; |
| pnodeWhile->ichLim = ichLim; |
| } |
| bool stashedDisallowImportExportStmt = m_disallowImportExportStmt; |
| m_disallowImportExportStmt = true; |
| PushStmt<buildAST>(&stmt, pnodeWhile, knopWhile, pLabelIdList); |
| ParseNodePtr pnodeBody = ParseStatement<buildAST>(); |
| PopStmt(&stmt); |
| |
| if (buildAST) |
| { |
| pnodeWhile->pnodeBody = pnodeBody; |
| pnode = pnodeWhile; |
| } |
| m_disallowImportExportStmt = stashedDisallowImportExportStmt; |
| break; |
| } |
| |
| case tkDO: |
| { |
| ParseNodeWhile * pnodeWhile = nullptr; |
| if (buildAST) |
| { |
| pnodeWhile = CreateNodeForOpT<knopDoWhile>(); |
| } |
| PushStmt<buildAST>(&stmt, pnodeWhile, knopDoWhile, pLabelIdList); |
| this->GetScanner()->Scan(); |
| bool stashedDisallowImportExportStmt = m_disallowImportExportStmt; |
| m_disallowImportExportStmt = true; |
| ParseNodePtr pnodeBody = ParseStatement<buildAST>(); |
| m_disallowImportExportStmt = stashedDisallowImportExportStmt; |
| PopStmt(&stmt); |
| charcount_t ichMinT = this->GetScanner()->IchMinTok(); |
| |
| ChkCurTok(tkWHILE, ERRnoWhile); |
| ChkCurTok(tkLParen, ERRnoLparen); |
| |
| ParseNodePtr pnodeCond = ParseExpr<buildAST>(); |
| charcount_t ichLim = this->GetScanner()->IchLimTok(); |
| ChkCurTok(tkRParen, ERRnoRparen); |
| |
| if (buildAST) |
| { |
| pnodeWhile->pnodeBody = pnodeBody; |
| pnodeWhile->pnodeCond = pnodeCond; |
| pnodeWhile->ichLim = ichLim; |
| pnodeWhile->ichMin = ichMinT; |
| pnode = pnodeWhile; |
| } |
| |
| // REVIEW: Allow do...while statements to be embedded in other compound statements like if..else, or do..while? |
| // goto LNeedTerminator; |
| |
| // For now just eat the trailing semicolon if present. |
| if (m_token.tk == tkSColon) |
| { |
| if (pnode) |
| { |
| pnode->grfpn |= PNodeFlags::fpnExplicitSemicolon; |
| } |
| this->GetScanner()->Scan(); |
| } |
| else if (pnode) |
| { |
| pnode->grfpn |= PNodeFlags::fpnAutomaticSemicolon; |
| } |
| |
| break; |
| } |
| |
| case tkIF: |
| { |
| ichMin = this->GetScanner()->IchMinTok(); |
| ChkNxtTok(tkLParen, ERRnoLparen); |
| ParseNodePtr pnodeCond = ParseExpr<buildAST>(); |
| ParseNodeIf * pnodeIf = nullptr; |
| if (buildAST) |
| { |
| pnodeIf = CreateNodeForOpT<knopIf>(ichMin); |
| pnodeIf->ichLim = this->GetScanner()->IchLimTok(); |
| pnodeIf->pnodeCond = pnodeCond; |
| } |
| ChkCurTok(tkRParen, ERRnoRparen); |
| |
| bool stashedDisallowImportExportStmt = m_disallowImportExportStmt; |
| m_disallowImportExportStmt = true; |
| PushStmt<buildAST>(&stmt, pnodeIf, knopIf, pLabelIdList); |
| ParseNodePtr pnodeTrue = ParseStatement<buildAST>(); |
| ParseNodePtr pnodeFalse = nullptr; |
| if (m_token.tk == tkELSE) |
| { |
| this->GetScanner()->Scan(); |
| pnodeFalse = ParseStatement<buildAST>(); |
| } |
| if (buildAST) |
| { |
| pnodeIf->pnodeTrue = pnodeTrue; |
| pnodeIf->pnodeFalse = pnodeFalse; |
| pnode = pnodeIf; |
| } |
| PopStmt(&stmt); |
| m_disallowImportExportStmt = stashedDisallowImportExportStmt; |
| break; |
| } |
| |
| case tkTRY: |
| { |
| ParseNodeBlock * pnodeBlock = CreateBlockNode(); |
| pnodeBlock->grfpn |= PNodeFlags::fpnSyntheticNode; // block is not a user specifier block |
| PushStmt<buildAST>(&stmt, pnodeBlock, knopBlock, pLabelIdList); |
| ParseNodePtr pnodeStmt = ParseTryCatchFinally<buildAST>(); |
| if (buildAST) |
| { |
| pnodeBlock->pnodeStmt = pnodeStmt; |
| } |
| PopStmt(&stmt); |
| pnode = pnodeBlock; |
| break; |
| } |
| |
| case tkWITH: |
| { |
| if (IsStrictMode()) |
| { |
| Error(ERRES5NoWith); |
| } |
| if (m_currentNodeFunc) |
| { |
| GetCurrentFunctionNode()->SetHasWithStmt(); // Used by DeferNested |
| } |
| |
| ichMin = this->GetScanner()->IchMinTok(); |
| ChkNxtTok(tkLParen, ERRnoLparen); |
| ParseNodePtr pnodeObj = ParseExpr<buildAST>(); |
| if (!buildAST) |
| { |
| m_scopeCountNoAst++; |
| } |
| charcount_t ichLim = this->GetScanner()->IchLimTok(); |
| ChkCurTok(tkRParen, ERRnoRparen); |
| |
| ParseNodeWith * pnodeWith = nullptr; |
| if (buildAST) |
| { |
| pnodeWith = CreateNodeForOpT<knopWith>(ichMin); |
| } |
| PushStmt<buildAST>(&stmt, pnodeWith, knopWith, pLabelIdList); |
| |
| ParseNodePtr *ppnodeExprScopeSave = nullptr; |
| if (buildAST) |
| { |
| pnodeWith->pnodeObj = pnodeObj; |
| this->CheckArguments(pnodeWith->pnodeObj); |
| |
| if (m_ppnodeExprScope) |
| { |
| Assert(*m_ppnodeExprScope == nullptr); |
| *m_ppnodeExprScope = pnodeWith; |
| m_ppnodeExprScope = &pnodeWith->pnodeNext; |
| } |
| else |
| { |
| Assert(m_ppnodeScope); |
| Assert(*m_ppnodeScope == nullptr); |
| *m_ppnodeScope = pnodeWith; |
| m_ppnodeScope = &pnodeWith->pnodeNext; |
| } |
| pnodeWith->pnodeNext = nullptr; |
| pnodeWith->scope = nullptr; |
| |
| ppnodeExprScopeSave = m_ppnodeExprScope; |
| m_ppnodeExprScope = &pnodeWith->pnodeScopes; |
| pnodeWith->pnodeScopes = nullptr; |
| |
| pnodeWith->ichLim = ichLim; |
| pnode = pnodeWith; |
| } |
| |
| PushBlockInfo(CreateBlockNode()); |
| PushDynamicBlock(); |
| |
| ParseNodePtr pnodeBody = ParseStatement<buildAST>(); |
| if (buildAST) |
| { |
| pnode->AsParseNodeWith()->pnodeBody = pnodeBody; |
| m_ppnodeExprScope = ppnodeExprScopeSave; |
| } |
| else |
| { |
| m_scopeCountNoAst--; |
| } |
| |
| // The dynamic block is not stored in the actual parse tree and so will not |
| // be visited by the byte code generator. Grab the callsEval flag off it and |
| // pass on to outer block in case of: |
| // with (...) eval(...); // i.e. blockless form of with |
| bool callsEval = GetCurrentBlock()->GetCallsEval(); |
| PopBlockInfo(); |
| if (callsEval) |
| { |
| // be careful not to overwrite an existing true with false |
| GetCurrentBlock()->SetCallsEval(true); |
| } |
| |
| PopStmt(&stmt); |
| break; |
| } |
| |
| case tkLCurly: |
| pnode = ParseBlock<buildAST>(pLabelIdList); |
| break; |
| |
| case tkSColon: |
| pnode = nullptr; |
| this->GetScanner()->Scan(); |
| break; |
| |
| case tkBREAK: |
| if (buildAST) |
| { |
| pnode = CreateNodeForOpT<knopBreak>(); |
| } |
| fnop = fnopBreak; |
| goto LGetJumpStatement; |
| |
| case tkCONTINUE: |
| if (buildAST) |
| { |
| pnode = CreateNodeForOpT<knopContinue>(); |
| } |
| fnop = fnopContinue; |
| |
| LGetJumpStatement: |
| this->GetScanner()->ScanForcingPid(); |
| if (tkID == m_token.tk && !this->GetScanner()->FHadNewLine()) |
| { |
| // Labeled break or continue. |
| pid = m_token.GetIdentifier(this->GetHashTbl()); |
| if (buildAST) |
| { |
| ParseNodeJump * pnodeJump = pnode->AsParseNodeJump(); |
| pnodeJump->hasExplicitTarget = true; |
| pnodeJump->ichLim = this->GetScanner()->IchLimTok(); |
| |
| this->GetScanner()->Scan(); |
| PushStmt<buildAST>(&stmt, pnodeJump, pnodeJump->nop, pLabelIdList); |
| Assert(pnodeJump->grfnop == 0); |
| for (pstmt = m_pstmtCur; nullptr != pstmt; pstmt = pstmt->pstmtOuter) |
| { |
| for (LabelId* label = pstmt->pLabelId; label != nullptr; label = label->next) |
| { |
| if (pid == label->pid) |
| { |
| // Found the label. Make sure we can use it. We can |
| // break out of any statement, but we can only |
| // continue loops. |
| if (fnop == fnopContinue && |
| !(pstmt->pnodeStmt->Grfnop() & fnop)) |
| { |
| Error(ERRbadContinue); |
| } |
| else |
| { |
| pstmt->pnodeStmt->grfnop |= fnop; |
| pnodeJump->pnodeTarget = pstmt->pnodeStmt; |
| } |
| PopStmt(&stmt); |
| goto LNeedTerminator; |
| } |
| } |
| pnodeJump->grfnop |= |
| (pstmt->pnodeStmt->Grfnop() & fnopCleanup); |
| } |
| } |
| else |
| { |
| this->GetScanner()->Scan(); |
| // Check if label is found within the current label id list. |
| auto checkLabelList = [&](LabelId* list, StmtNest* checkStmtOp) |
| { |
| for (LabelId* pLabelId = list; pLabelId; pLabelId = pLabelId->next) |
| { |
| if (pid == pLabelId->pid) |
| { |
| // Found the label. Make sure we can use it. We can |
| // break out of any statement, but we can only |
| // continue loops. |
| if (fnop == fnopContinue && |
| !(ParseNode::Grfnop(checkStmtOp->op) & fnop)) |
| { |
| Error(ERRbadContinue); |
| } |
| return true; |
| } |
| } |
| return false; |
| }; |
| |
| if (checkLabelList(pLabelIdList, m_pstmtCur)) goto LNeedTerminator; |
| |
| for (pstmt = m_pstmtCur; pstmt; pstmt = pstmt->pstmtOuter) |
| { |
| if (checkLabelList(pstmt->pLabelId, pstmt)) goto LNeedTerminator; |
| } |
| } |
| Error(ERRnoLabel); |
| } |
| else |
| { |
| // If we're doing a fast scan, we're not tracking labels, so we can't accurately do this analysis. |
| // Let the thread that's doing the full parse detect the error, if there is one. |
| if (!this->IsDoingFastScan()) |
| { |
| // Unlabeled break or continue. |
| ParseNodeJump * pnodeJump = nullptr; |
| if (buildAST) |
| { |
| pnodeJump = pnode->AsParseNodeJump(); |
| pnodeJump->hasExplicitTarget = false; |
| PushStmt<buildAST>(&stmt, pnodeJump, pnodeJump->nop, pLabelIdList); |
| Assert(pnodeJump->grfnop == 0); |
| } |
| |
| for (pstmt = m_pstmtCur; nullptr != pstmt; pstmt = pstmt->pstmtOuter) |
| { |
| if (buildAST) |
| { |
| AnalysisAssert(pstmt->pnodeStmt); |
| if (pstmt->pnodeStmt->Grfnop() & fnop) |
| { |
| pstmt->pnodeStmt->grfnop |= fnop; |
| pnodeJump->pnodeTarget = pstmt->pnodeStmt; |
| PopStmt(&stmt); |
| goto LNeedTerminator; |
| } |
| pnodeJump->grfnop |= |
| (pstmt->pnodeStmt->Grfnop() & fnopCleanup); |
| } |
| else |
| { |
| if (ParseNode::Grfnop(pstmt->GetNop()) & fnop) |
| { |
| if (!pstmt->isDeferred) |
| { |
| AnalysisAssert(pstmt->pnodeStmt); |
| pstmt->pnodeStmt->grfnop |= fnop; |
| } |
| goto LNeedTerminator; |
| } |
| } |
| } |
| Error(fnop == fnopBreak ? ERRbadBreak : ERRbadContinue); |
| } |
| goto LNeedTerminator; |
| } |
| |
| case tkRETURN: |
| { |
| ParseNodeReturn * pnodeReturn; |
| if (buildAST) |
| { |
| if (nullptr == m_currentNodeFunc || IsTopLevelModuleFunc()) |
| { |
| Error(ERRbadReturn); |
| } |
| pnodeReturn = CreateNodeForOpT<knopReturn>(); |
| } |
| this->GetScanner()->Scan(); |
| ParseNodePtr pnodeExpr = nullptr; |
| ParseOptionalExpr<buildAST>(&pnodeExpr, true); |
| |
| // Class constructors have special semantics regarding return statements. |
| // This might require a reference to 'this' |
| if (GetCurrentFunctionNode()->IsClassConstructor()) |
| { |
| ReferenceSpecialName(wellKnownPropertyPids._this); |
| } |
| |
| if (buildAST) |
| { |
| pnodeReturn->pnodeExpr = pnodeExpr; |
| if (pnodeExpr) |
| { |
| this->CheckArguments(pnodeReturn->pnodeExpr); |
| pnodeReturn->ichLim = pnodeReturn->pnodeExpr->ichLim; |
| } |
| // See if return should call finally |
| PushStmt<buildAST>(&stmt, pnodeReturn, knopReturn, pLabelIdList); |
| Assert(pnodeReturn->grfnop == 0); |
| for (pstmt = m_pstmtCur; nullptr != pstmt; pstmt = pstmt->pstmtOuter) |
| { |
| if (pstmt->pnodeStmt->Grfnop() & fnopCleanup) |
| { |
| pnodeReturn->grfnop |= fnopCleanup; |
| break; |
| } |
| } |
| PopStmt(&stmt); |
| pnode = pnodeReturn; |
| } |
| goto LNeedTerminator; |
| } |
| |
| case tkTHROW: |
| { |
| if (buildAST) |
| { |
| pnode = CreateUniNode(knopThrow, nullptr); |
| } |
| this->GetScanner()->Scan(); |
| ParseNodePtr pnode1 = nullptr; |
| if (m_token.tk != tkSColon && |
| m_token.tk != tkRCurly && |
| !this->GetScanner()->FHadNewLine()) |
| { |
| pnode1 = ParseExpr<buildAST>(); |
| } |
| else |
| { |
| Error(ERRdanglingThrow); |
| } |
| |
| if (buildAST) |
| { |
| pnode->AsParseNodeUni()->pnode1 = pnode1; |
| if (pnode1) |
| { |
| this->CheckArguments(pnode->AsParseNodeUni()->pnode1); |
| pnode->ichLim = pnode->AsParseNodeUni()->pnode1->ichLim; |
| } |
| } |
| goto LNeedTerminator; |
| } |
| |
| case tkDEBUGGER: |
| if (buildAST) |
| { |
| pnode = CreateNodeForOpT<knopDebugger>(); |
| } |
| this->GetScanner()->Scan(); |
| goto LNeedTerminator; |
| |
| case tkIMPORT: |
| pnode = ParseImport<buildAST>(); |
| |
| goto LNeedTerminator; |
| |
| case tkEXPORT: |
| { |
| if (!(m_grfscr & fscrIsModuleCode)) |
| { |
| goto LDefaultToken; |
| } |
| |
| bool needTerminator = false; |
| pnode = ParseExportDeclaration<buildAST>(&needTerminator); |
| |
| if (needTerminator) |
| { |
| goto LNeedTerminator; |
| } |
| else |
| { |
| break; |
| } |
| } |
| |
| LDefaultToken: |
| default: |
| { |
| // First check for a label via lookahead. If not found, |
| // rewind and reparse as expression statement. |
| if (m_token.tk == tkID) |
| { |
| RestorePoint idStart; |
| this->GetScanner()->Capture(&idStart); |
| |
| IdentPtr pidInner = m_token.GetIdentifier(this->GetHashTbl()); |
| |
| this->GetScanner()->Scan(); |
| |
| if (m_token.tk == tkColon) |
| { |
| // We have a label. |
| if (LabelExists(pidInner, pLabelIdList)) |
| { |
| Error(ERRbadLabel); |
| } |
| LabelId* pLabelId = CreateLabelId(pidInner); |
| pLabelId->next = pLabelIdList; |
| pLabelIdList = pLabelId; |
| this->GetScanner()->Scan(); |
| labelledStatement = true; |
| goto LRestart; |
| } |
| |
| // No label, rewind back to the tkID and parse an expression |
| this->GetScanner()->SeekTo(idStart); |
| } |
| |
| // Must be an expression statement. |
| pnode = ParseExpr<buildAST>(); |
| |
| if (m_hasDeferredShorthandInitError) |
| { |
| Error(ERRnoColon); |
| } |
| |
| if (buildAST) |
| { |
| expressionStmt = true; |
| |
| AnalysisAssert(pnode); |
| pnode->isUsed = false; |
| } |
| } |
| |
| LNeedTerminator: |
| // Need a semicolon, new-line, } or end-of-file. |
| // We digest a semicolon if it's there. |
| switch (m_token.tk) |
| { |
| case tkSColon: |
| this->GetScanner()->Scan(); |
| if (pnode != nullptr) pnode->grfpn |= PNodeFlags::fpnExplicitSemicolon; |
| break; |
| case tkEOF: |
| case tkRCurly: |
| if (pnode != nullptr) pnode->grfpn |= PNodeFlags::fpnAutomaticSemicolon; |
| break; |
| default: |
| if (!this->GetScanner()->FHadNewLine()) |
| { |
| Error(ERRnoSemic); |
| } |
| else |
| { |
| if (pnode != nullptr) pnode->grfpn |= PNodeFlags::fpnAutomaticSemicolon; |
| } |
| break; |
| } |
| break; |
| } |
| |
| if (m_hasDeferredShorthandInitError) |
| { |
| Error(ERRnoColon); |
| } |
| |
| if (buildAST) |
| { |
| // All non expression statements excluded from the "this.x" optimization |
| // Another check while parsing expressions |
| if (!expressionStmt) |
| { |
| if (m_currentNodeFunc) |
| { |
| m_currentNodeFunc->SetHasNonThisStmt(); |
| } |
| else if (m_currentNodeProg) |
| { |
| m_currentNodeProg->SetHasNonThisStmt(); |
| } |
| } |
| |
| #if EXCEPTION_RECOVERY |
| // close the try/catch block |
| if (Js::Configuration::Global.flags.SwallowExceptions) |
| { |
| // pop the try block and fill in the body |
| PopStmt(&stmtTryBlock); |
| pTryBlock->pnodeStmt = pnode; |
| PopStmt(&stmtTry); |
| if (pnode != nullptr) |
| { |
| pTry->ichLim = pnode->ichLim; |
| } |
| pTry->pnodeBody = pTryBlock; |
| |
| |
| // create a catch block with an empty body |
| StmtNest stmtCatch; |
| ParseNodeCatch * pCatch; |
| pCatch = CreateNodeForOpT<knopCatch>(); |
| PushStmt<buildAST>(&stmtCatch, pCatch, knopCatch, nullptr); |
| pCatch->pnodeBody = nullptr; |
| if (pnode != nullptr) |
| { |
| pCatch->ichLim = pnode->ichLim; |
| } |
| pCatch->grfnop = 0; |
| pCatch->pnodeNext = nullptr; |
| |
| // create a fake name for the catch var. |
| const WCHAR *uniqueNameStr = _u("__ehobj"); |
| IdentPtr uniqueName = this->GetHashTbl()->PidHashNameLen(uniqueNameStr, static_cast<int32>(wcslen(uniqueNameStr))); |
| |
| pCatch->SetParam(CreateNameNode(uniqueName)); |
| |
| // Add this catch to the current list. We don't bother adjusting the catch and function expression |
| // lists here because the catch is just an empty statement. |
| |
| if (m_ppnodeExprScope) |
| { |
| Assert(*m_ppnodeExprScope == nullptr); |
| *m_ppnodeExprScope = pCatch; |
| m_ppnodeExprScope = &pCatch->pnodeNext; |
| } |
| else |
| { |
| Assert(m_ppnodeScope); |
| Assert(*m_ppnodeScope == nullptr); |
| *m_ppnodeScope = pCatch; |
| m_ppnodeScope = &pCatch->pnodeNext; |
| } |
| |
| pCatch->pnodeScopes = nullptr; |
| |
| PopStmt(&stmtCatch); |
| |
| // fill in and pop the try-catch |
| pParentTryCatch->pnodeTry = pTry; |
| pParentTryCatch->pnodeCatch = pCatch; |
| PopStmt(&stmtTryCatch); |
| PopStmt(&stmtTryCatchBlock); |
| |
| // replace the node that's being returned |
| pParentTryCatchBlock->pnodeStmt = pParentTryCatch; |
| pnode = pParentTryCatchBlock; |
| } |
| #endif // EXCEPTION_RECOVERY |
| |
| } |
| |
| return pnode; |
| } |
| |
| BOOL |
| Parser::TokIsForInOrForOf() |
| { |
| return m_token.tk == tkIN || |
| (m_token.tk == tkID && |
| m_token.GetIdentifier(this->GetHashTbl()) == wellKnownPropertyPids.of); |
| } |
| |
| /*************************************************************************** |
| Parse a sequence of statements. |
| ***************************************************************************/ |
| template<bool buildAST> |
| void Parser::ParseStmtList(ParseNodePtr *ppnodeList, ParseNodePtr **pppnodeLast, StrictModeEnvironment smEnvironment, const bool isSourceElementList, bool* strictModeOn) |
| { |
| BOOL doneDirectives = !isSourceElementList; // directives may only exist in a SourceElementList, not a StatementList |
| BOOL seenDirectiveContainingOctal = false; // Have we seen an octal directive before a use strict directive? |
| |
| BOOL old_UseStrictMode = m_fUseStrictMode; |
| |
| ParseNodePtr pnodeStmt; |
| ParseNodePtr *lastNodeRef = nullptr; |
| |
| if (buildAST) |
| { |
| Assert(ppnodeList); |
| *ppnodeList = nullptr; |
| } |
| |
| if (CONFIG_FLAG(ForceStrictMode)) |
| { |
| m_fUseStrictMode = TRUE; |
| } |
| |
| for (;;) |
| { |
| switch (m_token.tk) |
| { |
| case tkCASE: |
| case tkDEFAULT: |
| case tkRCurly: |
| case tkEOF: |
| if (buildAST && nullptr != pppnodeLast) |
| { |
| *pppnodeLast = lastNodeRef; |
| } |
| if (!buildAST) |
| { |
| m_fUseStrictMode = old_UseStrictMode; |
| } |
| return; |
| } |
| |
| if (doneDirectives == FALSE) |
| { |
| bool isOctalInString = false; |
| bool isUseStrictDirective = false; |
| bool isUseAsmDirective = false; |
| if (smEnvironment != SM_NotUsed && CheckForDirective(&isUseStrictDirective, &isUseAsmDirective, &isOctalInString)) |
| { |
| // Ignore "use asm" statement when not building the AST |
| isUseAsmDirective &= buildAST; |
| |
| if (isUseStrictDirective) |
| { |
| // Functions with non-simple parameter list cannot be made strict mode |
| if (GetCurrentFunctionNode()->HasNonSimpleParameterList()) |
| { |
| Error(ERRNonSimpleParamListInStrictMode); |
| } |
| |
| if (seenDirectiveContainingOctal) |
| { |
| // Directives seen before a "use strict" cannot contain an octal. |
| Error(ERRES5NoOctal); |
| } |
| if (!buildAST) |
| { |
| // Turning on strict mode in deferred code. |
| m_fUseStrictMode = TRUE; |
| if (!m_inDeferredNestedFunc) |
| { |
| // Top-level deferred function, so there's a parse node |
| Assert(m_currentNodeFunc != nullptr); |
| m_currentNodeFunc->SetStrictMode(); |
| } |
| else if (strictModeOn) |
| { |
| // This turns on strict mode in a deferred function, we need to go back |
| // and re-check duplicated formals. |
| *strictModeOn = true; |
| } |
| } |
| else |
| { |
| if (smEnvironment == SM_OnGlobalCode) |
| { |
| // Turning on strict mode at the top level |
| m_fUseStrictMode = TRUE; |
| } |
| else |
| { |
| // i.e. smEnvironment == SM_OnFunctionCode |
| Assert(m_currentNodeFunc != nullptr); |
| m_currentNodeFunc->SetStrictMode(); |
| } |
| } |
| } |
| else if (isUseAsmDirective) |
| { |
| if (smEnvironment != SM_OnGlobalCode) //Top level use asm doesn't mean anything. |
| { |
| // i.e. smEnvironment == SM_OnFunctionCode |
| Assert(m_currentNodeFunc != nullptr); |
| m_currentNodeFunc->SetAsmjsMode(); |
| m_currentNodeFunc->SetCanBeDeferred(false); |
| m_InAsmMode = true; |
| |
| CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, AsmJSFunction, m_scriptContext); |
| } |
| } |
| else if (isOctalInString) |
| { |
| seenDirectiveContainingOctal = TRUE; |
| } |
| } |
| else |
| { |
| // The first time we see anything other than a directive we can have no more directives. |
| doneDirectives = TRUE; |
| } |
| } |
| |
| if (nullptr != (pnodeStmt = ParseStatement<buildAST>())) |
| { |
| if (buildAST) |
| { |
| AddToNodeList(ppnodeList, &lastNodeRef, pnodeStmt); |
| } |
| } |
| } |
| } |
| |
| template <class Fn> |
| void Parser::FinishFunctionsInScope(ParseNodePtr pnodeScopeList, Fn fn) |
| { |
| Scope * scope; |
| Scope * origCurrentScope = this->m_currentScope; |
| ParseNodePtr pnodeScope; |
| ParseNodeBlock * pnodeBlock; |
| for (pnodeScope = pnodeScopeList; pnodeScope;) |
| { |
| switch (pnodeScope->nop) |
| { |
| case knopBlock: |
| { |
| ParseNodeBlock * pnodeBlockScope = pnodeScope->AsParseNodeBlock(); |
| m_nextBlockId = pnodeBlockScope->blockId + 1; |
| PushBlockInfo(pnodeBlockScope); |
| scope = pnodeBlockScope->scope; |
| if (scope && scope != origCurrentScope) |
| { |
| PushScope(scope); |
| } |
| FinishFunctionsInScope(pnodeBlockScope->pnodeScopes, fn); |
| if (scope && scope != origCurrentScope) |
| { |
| BindPidRefs<false>(GetCurrentBlockInfo(), m_nextBlockId - 1); |
| PopScope(scope); |
| } |
| PopBlockInfo(); |
| pnodeScope = pnodeBlockScope->pnodeNext; |
| break; |
| } |
| case knopFncDecl: |
| fn(pnodeScope->AsParseNodeFnc()); |
| pnodeScope = pnodeScope->AsParseNodeFnc()->pnodeNext; |
| break; |
| |
| case knopCatch: |
| scope = pnodeScope->AsParseNodeCatch()->scope; |
| if (scope) |
| { |
| PushScope(scope); |
| } |
| pnodeBlock = CreateBlockNode(PnodeBlockType::Regular); |
| pnodeBlock->scope = scope; |
| PushBlockInfo(pnodeBlock); |
| FinishFunctionsInScope(pnodeScope->AsParseNodeCatch()->pnodeScopes, fn); |
| if (scope) |
| { |
| BindPidRefs<false>(GetCurrentBlockInfo(), m_nextBlockId - 1); |
| PopScope(scope); |
| } |
| PopBlockInfo(); |
| pnodeScope = pnodeScope->AsParseNodeCatch()->pnodeNext; |
| break; |
| |
| case knopWith: |
| PushBlockInfo(CreateBlockNode()); |
| PushDynamicBlock(); |
| FinishFunctionsInScope(pnodeScope->AsParseNodeWith()->pnodeScopes, fn); |
| PopBlockInfo(); |
| pnodeScope = pnodeScope->AsParseNodeWith()->pnodeNext; |
| break; |
| |
| default: |
| AssertMsg(false, "Unexpected node with scope list"); |
| return; |
| } |
| } |
| } |
| |
| // Scripts above this size (minus string literals and comments) will have parsing of |
| // function bodies deferred. |
| ULONG Parser::GetDeferralThreshold(bool isProfileLoaded) |
| { |
| #ifdef ENABLE_DEBUG_CONFIG_OPTIONS |
| if (CONFIG_FLAG(ForceDeferParse) || |
| PHASE_FORCE1(Js::DeferParsePhase) || |
| Js::Configuration::Global.flags.IsEnabled(Js::ForceUndoDeferFlag)) |
| { |
| return 0; |
| } |
| else if (Js::Configuration::Global.flags.IsEnabled(Js::DeferParseFlag)) |
| { |
| return Js::Configuration::Global.flags.DeferParse; |
| } |
| else |
| #endif |
| { |
| if (isProfileLoaded) |
| { |
| return DEFAULT_CONFIG_ProfileBasedDeferParseThreshold; |
| } |
| return DEFAULT_CONFIG_DeferParseThreshold; |
| } |
| } |
| |
| void Parser::FinishDeferredFunction(ParseNodeBlock * pnodeScopeList) |
| { |
| ParseContext parseContext; |
| this->CaptureContext(&parseContext); |
| |
| m_nextBlockId = pnodeScopeList->blockId + 1; |
| |
| FinishFunctionsInScope(pnodeScopeList, |
| [this, &parseContext](ParseNodeFnc * pnodeFnc) |
| { |
| Assert(pnodeFnc->nop == knopFncDecl); |
| |
| // We need to scan this function based on the already known limits of the function declaration as some of |
| // the state such as fAllowIn may not be available at this point. Some of this state depends on the context |
| // of the function declaration. For example, a function declaration may be inside a for..in statement's var |
| // declaration. It may not be appropriate/possible to try and save all such context information. Functions |
| // that actually get deferred achieve this by going through the ParseSourceWithOffset code path. |
| this->GetScanner()->Clear(); |
| this->GetScanner()->SetText(parseContext.pszSrc, pnodeFnc->cbMin /*+ this->m_scan.m_cMinTokMultiUnits*/, pnodeFnc->LengthInBytes(), pnodeFnc->ichMin, parseContext.isUtf8, parseContext.grfscr, pnodeFnc->lineNumber); |
| this->GetScanner()->Scan(); |
| |
| // Non-simple params (such as default) require a good amount of logic to put vars on appropriate scopes. ParseFncDecl handles it |
| // properly (both on defer and non-defer case). This is to avoid write duplicated logic here as well. Function with non-simple-param |
| // will remain deferred until they are called. |
| if (pnodeFnc->pnodeBody == nullptr && !pnodeFnc->HasNonSimpleParameterList()) |
| { |
| // Go back and generate an AST for this function. |
| JS_ETW_INTERNAL(EventWriteJSCRIPT_PARSE_FUNC(this->GetScriptContext(), pnodeFnc->functionId, /*Undefer*/TRUE)); |
| |
| ParseNodeFnc * pnodeFncSave = this->m_currentNodeFunc; |
| this->m_currentNodeFunc = pnodeFnc; |
| |
| ParseNodeBlock * pnodeFncExprBlock = nullptr; |
| ParseNodePtr pnodeName = pnodeFnc->pnodeName; |
| if (pnodeName) |
| { |
| Assert(pnodeName->nop == knopVarDecl); |
| ParseNodeVar * pnodeVarName = pnodeName->AsParseNodeVar(); |
| Assert(pnodeVarName->pnodeNext == nullptr); |
| |
| if (!pnodeFnc->IsDeclaration()) |
| { |
| // Set up the named function expression symbol so references inside the function can be bound. |
| pnodeFncExprBlock = this->StartParseBlock<true>(PnodeBlockType::Function, ScopeType_FuncExpr); |
| PidRefStack *ref = this->PushPidRef(pnodeVarName->pid); |
| pnodeVarName->symRef = ref->GetSymRef(); |
| ref->SetSym(pnodeVarName->sym); |
| |
| Scope *fncExprScope = pnodeFncExprBlock->scope; |
| fncExprScope->AddNewSymbol(pnodeVarName->sym); |
| pnodeFnc->scope = fncExprScope; |
| } |
| } |
| |
| ParseNodeBlock * pnodeBlock = this->StartParseBlock<true>(PnodeBlockType::Parameter, ScopeType_Parameter); |
| pnodeFnc->pnodeScopes = pnodeBlock; |
| m_ppnodeScope = &pnodeBlock->pnodeScopes; |
| pnodeBlock->pnodeStmt = pnodeFnc; |
| |
| ParseNodePtr * varNodesList = &pnodeFnc->pnodeVars; |
| ParseNodeVar * argNode = nullptr; |
| if (!pnodeFnc->IsModule() && !pnodeFnc->IsLambda() && !(pnodeFnc->grfpn & PNodeFlags::fpnArguments_overriddenInParam)) |
| { |
| ParseNodePtr *const ppnodeVarSave = m_ppnodeVar; |
| m_ppnodeVar = &pnodeFnc->pnodeVars; |
| |
| argNode = this->AddArgumentsNodeToVars(pnodeFnc); |
| |
| varNodesList = m_ppnodeVar; |
| m_ppnodeVar = ppnodeVarSave; |
| } |
| |
| // Add the args to the scope, since we won't re-parse those. |
| Scope *scope = pnodeBlock->scope; |
| uint blockId = GetCurrentBlock()->blockId; |
| uint funcId = GetCurrentFunctionNode()->functionId; |
| auto addArgsToScope = [&](ParseNodePtr pnodeArg) { |
| if (pnodeArg->IsVarLetOrConst()) |
| { |
| ParseNodeVar * pnodeVarArg = pnodeArg->AsParseNodeVar(); |
| |
| PidRefStack *ref = this->FindOrAddPidRef(pnodeVarArg->pid, blockId, funcId); |
| pnodeVarArg->symRef = ref->GetSymRef(); |
| if (ref->GetSym() != nullptr) |
| { |
| // Duplicate parameter in a configuration that allows them. |
| // The symbol is already in the scope, just point it to the right declaration. |
| Assert(ref->GetSym() == pnodeVarArg->sym); |
| ref->GetSym()->SetDecl(pnodeVarArg); |
| } |
| else |
| { |
| ref->SetSym(pnodeArg->AsParseNodeVar()->sym); |
| scope->AddNewSymbol(pnodeVarArg->sym); |
| } |
| } |
| }; |
| |
| MapFormals(pnodeFnc, addArgsToScope); |
| MapFormalsFromPattern(pnodeFnc, addArgsToScope); |
| |
| ParseNodeBlock * pnodeInnerBlock = this->StartParseBlock<true>(PnodeBlockType::Function, ScopeType_FunctionBody); |
| pnodeFnc->pnodeBodyScope = pnodeInnerBlock; |
| |
| // Set the parameter block's child to the function body block. |
| *m_ppnodeScope = pnodeInnerBlock; |
| |
| ParseNodePtr *ppnodeScopeSave = nullptr; |
| ParseNodePtr *ppnodeExprScopeSave = nullptr; |
| |
| ppnodeScopeSave = m_ppnodeScope; |
| |
| // This synthetic block scope will contain all the nested scopes. |
| m_ppnodeScope = &pnodeInnerBlock->pnodeScopes; |
| pnodeInnerBlock->pnodeStmt = pnodeFnc; |
| |
| // Keep nested function declarations and expressions in the same list at function scope. |
| // (Indicate this by nulling out the current function expressions list.) |
| ppnodeExprScopeSave = m_ppnodeExprScope; |
| m_ppnodeExprScope = nullptr; |
| |
| // Shouldn't be any temps in the arg list. |
| Assert(*m_ppnodeVar == nullptr); |
| |
| // Start the var list. |
| m_ppnodeVar = varNodesList; |
| |
| if (scope != nullptr) |
| { |
| Assert(pnodeFnc->IsBodyAndParamScopeMerged()); |
| blockId = GetCurrentBlock()->blockId; |
| funcId = GetCurrentFunctionNode()->functionId; |
| scope->ForEachSymbol([this, blockId, funcId](Symbol* paramSym) |
| { |
| PidRefStack* ref = this->FindOrAddPidRef(paramSym->GetPid(), blockId, funcId); |
| ref->SetSym(paramSym); |
| }); |
| } |
| |
| Assert(m_currentNodeNonLambdaFunc == nullptr); |
| m_currentNodeNonLambdaFunc = pnodeFnc; |
| |
| this->FinishFncNode(pnodeFnc); |
| |
| Assert(pnodeFnc == m_currentNodeNonLambdaFunc); |
| m_currentNodeNonLambdaFunc = nullptr; |
| |
| m_ppnodeExprScope = ppnodeExprScopeSave; |
| |
| Assert(m_ppnodeScope); |
| Assert(nullptr == *m_ppnodeScope); |
| m_ppnodeScope = ppnodeScopeSave; |
| |
| this->FinishParseBlock(pnodeInnerBlock); |
| |
| if (!pnodeFnc->IsModule() && (m_token.tk == tkLCurly || !pnodeFnc->IsLambda())) |
| { |
| UpdateArgumentsNode(pnodeFnc, argNode); |
| } |
| |
| CreateSpecialSymbolDeclarations(pnodeFnc); |
| |
| this->FinishParseBlock(pnodeBlock); |
| if (pnodeFncExprBlock) |
| { |
| this->FinishParseBlock(pnodeFncExprBlock); |
| } |
| |
| this->m_currentNodeFunc = pnodeFncSave; |
| } |
| }); |
| |
| this->RestoreContext(&parseContext); |
| } |
| |
| void Parser::InitPids() |
| { |
| wellKnownPropertyPids.arguments = this->GetHashTbl()->PidHashNameLen(g_ssym_arguments.sz, g_ssym_arguments.cch); |
| wellKnownPropertyPids.async = this->GetHashTbl()->PidHashNameLen(g_ssym_async.sz, g_ssym_async.cch); |
| wellKnownPropertyPids.eval = this->GetHashTbl()->PidHashNameLen(g_ssym_eval.sz, g_ssym_eval.cch); |
| wellKnownPropertyPids.get = this->GetHashTbl()->PidHashNameLen(g_ssym_get.sz, g_ssym_get.cch); |
| wellKnownPropertyPids.set = this->GetHashTbl()->PidHashNameLen(g_ssym_set.sz, g_ssym_set.cch); |
| wellKnownPropertyPids.let = this->GetHashTbl()->PidHashNameLen(g_ssym_let.sz, g_ssym_let.cch); |
| wellKnownPropertyPids.constructor = this->GetHashTbl()->PidHashNameLen(g_ssym_constructor.sz, g_ssym_constructor.cch); |
| wellKnownPropertyPids.prototype = this->GetHashTbl()->PidHashNameLen(g_ssym_prototype.sz, g_ssym_prototype.cch); |
| wellKnownPropertyPids.__proto__ = this->GetHashTbl()->PidHashNameLen(_u("__proto__"), sizeof("__proto__") - 1); |
| wellKnownPropertyPids.of = this->GetHashTbl()->PidHashNameLen(_u("of"), sizeof("of") - 1); |
| wellKnownPropertyPids.target = this->GetHashTbl()->PidHashNameLen(_u("target"), sizeof("target") - 1); |
| wellKnownPropertyPids.as = this->GetHashTbl()->PidHashNameLen(_u("as"), sizeof("as") - 1); |
| wellKnownPropertyPids.from = this->GetHashTbl()->PidHashNameLen(_u("from"), sizeof("from") - 1); |
| wellKnownPropertyPids._default = this->GetHashTbl()->PidHashNameLen(_u("default"), sizeof("default") - 1); |
| wellKnownPropertyPids._star = this->GetHashTbl()->PidHashNameLen(_u("*"), sizeof("*") - 1); |
| wellKnownPropertyPids._this = this->GetHashTbl()->PidHashNameLen(_u("*this*"), sizeof("*this*") - 1); |
| wellKnownPropertyPids._newTarget = this->GetHashTbl()->PidHashNameLen(_u("*new.target*"), sizeof("*new.target*") - 1); |
| wellKnownPropertyPids._super = this->GetHashTbl()->PidHashNameLen(_u("*super*"), sizeof("*super*") - 1); |
| wellKnownPropertyPids._superConstructor = this->GetHashTbl()->PidHashNameLen(_u("*superconstructor*"), sizeof("*superconstructor*") - 1); |
| } |
| |
| void Parser::RestoreScopeInfo(Js::ScopeInfo * scopeInfo) |
| { |
| if (!scopeInfo) |
| { |
| return; |
| } |
| |
| PROBE_STACK_NO_DISPOSE(m_scriptContext, Js::Constants::MinStackByteCodeVisitor); |
| |
| RestoreScopeInfo(scopeInfo->GetParentScopeInfo()); // Recursively restore outer func scope info |
| |
| scopeInfo->SetScopeId(m_nextBlockId); |
| ParseNodeBlock * pnodeScope = nullptr; |
| ScopeType scopeType = scopeInfo->GetScopeType(); |
| PnodeBlockType blockType; |
| switch (scopeType) |
| { |
| case ScopeType_With: |
| PushDynamicBlock(); |
| // fall through |
| case ScopeType_Block: |
| case ScopeType_Catch: |
| case ScopeType_CatchParamPattern: |
| case ScopeType_GlobalEvalBlock: |
| blockType = PnodeBlockType::Regular; |
| break; |
| |
| case ScopeType_FunctionBody: |
| case ScopeType_FuncExpr: |
| blockType = PnodeBlockType::Function; |
| break; |
| |
| case ScopeType_Parameter: |
| blockType = PnodeBlockType::Parameter; |
| break; |
| |
| |
| default: |
| Assert(0); |
| return; |
| } |
| |
| pnodeScope = StartParseBlockWithCapacity<true>(blockType, scopeType, scopeInfo->GetSymbolCount()); |
| Scope *scope = pnodeScope->scope; |
| scope->SetScopeInfo(scopeInfo); |
| scopeInfo->ExtractScopeInfo(this, /*nullptr, nullptr,*/ scope); |
| } |
| |
| void Parser::FinishScopeInfo(Js::ScopeInfo * scopeInfo) |
| { |
| PROBE_STACK_NO_DISPOSE(m_scriptContext, Js::Constants::MinStackByteCodeVisitor); |
| |
| for (; scopeInfo != nullptr; scopeInfo = scopeInfo->GetParentScopeInfo()) |
| { |
| int scopeId = scopeInfo->GetScopeId(); |
| |
| scopeInfo->GetScope()->ForEachSymbol([this, scopeId](Symbol *sym) |
| { |
| this->BindPidRefsInScope(sym->GetPid(), sym, scopeId); |
| }); |
| PopScope(scopeInfo->GetScope()); |
| PopStmt(&m_currentBlockInfo->pstmt); |
| PopBlockInfo(); |
| } |
| } |
| |
| /*************************************************************************** |
| Parse the code. |
| ***************************************************************************/ |
| ParseNodeProg * Parser::Parse(LPCUTF8 pszSrc, size_t offset, size_t length, charcount_t charOffset, bool isUtf8, ULONG grfscr, ULONG lineNumber, Js::LocalFunctionId * nextFunctionId, CompileScriptException *pse) |
| { |
| ParseNodeProg * pnodeProg; |
| ParseNodePtr *lastNodeRef = nullptr; |
| |
| m_nextBlockId = 0; |
| |
| bool isDeferred = (grfscr & fscrDeferredFnc) != 0; |
| bool isModuleSource = (grfscr & fscrIsModuleCode) != 0; |
| bool isGlobalCode = (grfscr & fscrGlobalCode) != 0; |
| |
| if (this->m_scriptContext->IsScriptContextInDebugMode() |
| #ifdef ENABLE_PREJIT |
| || Js::Configuration::Global.flags.Prejit |
| #endif |
| || ((grfscr & fscrNoDeferParse) != 0) |
| ) |
| { |
| // Don't do deferred parsing if debugger is attached or feature is disabled |
| // by command-line switch. |
| grfscr &= ~fscrWillDeferFncParse; |
| } |
| else if (!isGlobalCode && |
| ( |
| PHASE_OFF1(Js::Phase::DeferEventHandlersPhase) || |
| this->m_scriptContext->IsScriptContextInSourceRundownOrDebugMode() |
| ) |
| ) |
| { |
| // Don't defer event handlers in debug/rundown mode, because we need to register the document, |
| // so we need to create a full FunctionBody for the script body. |
| grfscr &= ~fscrWillDeferFncParse; |
| } |
| |
| m_grfscr = grfscr; |
| m_length = length; |
| m_originalLength = length; |
| m_nextFunctionId = nextFunctionId; |
| |
| if (m_parseType != ParseType_Deferred) |
| { |
| JS_ETW(EventWriteJSCRIPT_PARSE_METHOD_START(m_sourceContextInfo->dwHostSourceContext, GetScriptContext(), *m_nextFunctionId, 0, m_parseType, Js::Constants::GlobalFunction)); |
| OUTPUT_TRACE(Js::DeferParsePhase, _u("Parsing function (%s) : %s (%d)\n"), GetParseType(), Js::Constants::GlobalFunction, *m_nextFunctionId); |
| } |
| |
| // Give the scanner the source and get the first token |
| this->GetScanner()->SetText(pszSrc, offset, length, charOffset, isUtf8, grfscr, lineNumber); |
| this->GetScanner()->Scan(); |
| |
| // Make the main 'knopProg' node |
| int32 initSize = 0; |
| m_pCurrentAstSize = &initSize; |
| pnodeProg = CreateProgNode(isModuleSource, lineNumber); |
| |
| if (!isDeferred || (isDeferred && isGlobalCode)) |
| { |
| // In the deferred case, if the global function is deferred parse (which is in no-refresh case), |
| // we will re-use the same function body, so start with the correct functionId. |
| pnodeProg->functionId = (*m_nextFunctionId)++; |
| } |
| |
| if (isModuleSource) |
| { |
| Assert(m_scriptContext->GetConfig()->IsES6ModuleEnabled()); |
| |
| pnodeProg->AsParseNodeModule()->localExportEntries = nullptr; |
| pnodeProg->AsParseNodeModule()->indirectExportEntries = nullptr; |
| pnodeProg->AsParseNodeModule()->starExportEntries = nullptr; |
| pnodeProg->AsParseNodeModule()->importEntries = nullptr; |
| pnodeProg->AsParseNodeModule()->requestedModules = nullptr; |
| } |
| |
| m_pCurrentAstSize = &(pnodeProg->astSize); |
| |
| // initialize parsing variables |
| m_currentNodeFunc = nullptr; |
| m_currentNodeDeferredFunc = nullptr; |
| m_currentNodeProg = pnodeProg; |
| m_cactIdentToNodeLookup = 1; |
| |
| m_pnestedCount = &pnodeProg->nestedCount; |
| m_inDeferredNestedFunc = false; |
| m_ppnodeVar = &pnodeProg->pnodeVars; |
| SetCurrentStatement(nullptr); |
| AssertMsg(m_pstmtCur == nullptr, "Statement stack should be empty when we start parse global code"); |
| |
| // Create block for const's and let's |
| ParseNodeBlock * pnodeGlobalBlock = StartParseBlock<true>(PnodeBlockType::Global, ScopeType_Global); |
| pnodeProg->scope = pnodeGlobalBlock->scope; |
| ParseNodeBlock * pnodeGlobalEvalBlock = nullptr; |
| |
| // Don't track function expressions separately from declarations at global scope. |
| m_ppnodeExprScope = nullptr; |
| |
| // This synthetic block scope will contain all the nested scopes. |
| pnodeProg->pnodeScopes = pnodeGlobalBlock; |
| m_ppnodeScope = &pnodeGlobalBlock->pnodeScopes; |
| |
| if ((this->m_grfscr & fscrEvalCode) && |
| !(this->m_functionBody && this->m_functionBody->GetScopeInfo())) |
| { |
| pnodeGlobalEvalBlock = StartParseBlock<true>(PnodeBlockType::Regular, ScopeType_GlobalEvalBlock); |
| pnodeProg->pnodeScopes = pnodeGlobalEvalBlock; |
| m_ppnodeScope = &pnodeGlobalEvalBlock->pnodeScopes; |
| } |
| |
| Js::ScopeInfo *scopeInfo = nullptr; |
| if (m_parseType == ParseType_Deferred && m_functionBody) |
| { |
| // this->m_functionBody can be cleared during parsing, but we need access to the scope info later. |
| scopeInfo = m_functionBody->GetScopeInfo(); |
| if (scopeInfo) |
| { |
| // Create an enclosing function context. |
| m_currentNodeFunc = CreateNodeForOpT<knopFncDecl>(); |
| m_currentNodeFunc->functionId = m_functionBody->GetLocalFunctionId(); |
| m_currentNodeFunc->nestedCount = m_functionBody->GetNestedCount(); |
| m_currentNodeFunc->SetStrictMode(!!this->m_fUseStrictMode); |
| |
| this->RestoreScopeInfo(scopeInfo); |
| |
| m_currentNodeFunc->SetIsGenerator(scopeInfo->IsGeneratorFunctionBody()); |
| m_currentNodeFunc->SetIsAsync(scopeInfo->IsAsyncFunctionBody()); |
| } |
| } |
| |
| // It's possible for the module global to be defer-parsed in debug scenarios. |
| if (isModuleSource && (!isDeferred || (isDeferred && isGlobalCode))) |
| { |
| ParseNodePtr moduleFunction = GenerateModuleFunctionWrapper<true>(); |
| pnodeProg->pnodeBody = nullptr; |
| AddToNodeList(&pnodeProg->pnodeBody, &lastNodeRef, moduleFunction); |
| } |
| else |
| { |
| if (isDeferred && !isGlobalCode) |
| { |
| // Defer parse for a single function should just parse that one function - there are no other statements. |
| ushort flags = fFncNoFlgs; |
| bool isAsync = false; |
| bool isGenerator = false; |
| bool isMethod = false; |
| |
| // The top-level deferred function body was defined by a function expression whose parsing was deferred. We are now |
| // parsing it, so unset the flag so that any nested functions are parsed normally. This flag is only applicable the |
| // first time we see it. |
| // |
| // Normally, deferred functions will be parsed in ParseStatement upon encountering the 'function' token. The first |
| // token of the source code of the function may not be a 'function' token though, so we still need to reset this flag |
| // for the first function we parse. This can happen in compat modes, for instance, for a function expression enclosed |
| // in parentheses, where the legacy behavior was to include the parentheses in the function's source code. |
| if (m_grfscr & fscrDeferredFncExpression) |
| { |
| m_grfscr &= ~fscrDeferredFncExpression; |
| } |
| else |
| { |
| flags |= fFncDeclaration; |
| } |
| |
| if (m_grfscr & fscrDeferredFncIsMethod) |
| { |
| m_grfscr &= ~fscrDeferredFncIsMethod; |
| isMethod = true; |
| flags |= fFncNoName | fFncMethod; |
| |
| if (m_grfscr & fscrDeferredFncIsGenerator) |
| { |
| m_grfscr &= ~fscrDeferredFncIsGenerator; |
| isGenerator = true; |
| flags |= fFncGenerator; |
| } |
| |
| if (m_token.tk == tkStar && m_scriptContext->GetConfig()->IsES6GeneratorsEnabled()) |
| { |
| Assert(isGenerator && !isMethod); |
| this->GetScanner()->Scan(); |
| } |
| } |
| |
| if (m_grfscr & fscrDeferredFncIsAsync) |
| { |
| m_grfscr &= ~fscrDeferredFncIsAsync; |
| isAsync = true; |
| flags |= fFncAsync; |
| } |
| |
| |
| #if DBG |
| if (isMethod && m_token.tk == tkID) |
| { |
| RestorePoint atPid; |
| IdentPtr pidHint = m_token.GetIdentifier(this->GetHashTbl()); |
| this->GetScanner()->Capture(&atPid); |
| this->GetScanner()->Scan(); |
| if ((pidHint == wellKnownPropertyPids.get || pidHint == wellKnownPropertyPids.set) && NextTokenIsPropertyNameStart()) |
| { |
| // Getter/setter |
| // Skip the get/set keyword and continue normally |
| AssertMsg(false, "We should not be re-parsing the get/set part of member accessor functions"); |
| } |
| else |
| { |
| // Not a getter/setter; rewind and treat the token as a name. |
| this->GetScanner()->SeekTo(atPid); |
| } |
| } |
| #endif |
| |
| // Ensure this isn't a computed name |
| AssertMsg(!(m_token.tk == tkLBrack && isMethod), "Can't defer parse a computed name expression, we should have started after this"); |
| |
| if (!isMethod && (m_token.tk == tkID || m_token.tk == tkLParen)) |
| { |
| // If first token of the function is tkID or tkLParen, this is a lambda. |
| flags |= fFncLambda; |
| } |
| |
| ParseNode * pnodeFnc = ParseFncDeclCheckScope<true>(flags); |
| pnodeProg->pnodeBody = nullptr; |
| AddToNodeList(&pnodeProg->pnodeBody, &lastNodeRef, pnodeFnc); |
| |
| // No need to update the cbStringMin property since no ParseableFunctionInfo will be created from this defer-parsed pnodeFnc |
| } |
| else |
| { |
| // Process a sequence of statements/declarations |
| ParseStmtList<true>( |
| &pnodeProg->pnodeBody, |
| &lastNodeRef, |
| SM_OnGlobalCode, |
| !(m_grfscr & fscrDeferredFncExpression) /* isSourceElementList */); |
| } |
| } |
| |
| if (m_parseType == ParseType_Deferred) |
| { |
| if (scopeInfo) |
| { |
| this->FinishScopeInfo(scopeInfo); |
| } |
| } |
| |
| pnodeProg->m_UsesArgumentsAtGlobal = m_UsesArgumentsAtGlobal; |
| |
| if (IsStrictMode()) |
| { |
| pnodeProg->SetStrictMode(); |
| } |
| |
| #if DEBUG |
| if (m_grfscr & fscrEnforceJSON && !IsJSONValid(pnodeProg->pnodeBody)) |
| { |
| Error(ERRsyntax); |
| } |
| #endif |
| |
| if (tkEOF != m_token.tk) |
| Error(ERRsyntax); |
| |
| // Append an EndCode node. |
| AddToNodeList(&pnodeProg->pnodeBody, &lastNodeRef, |
| CreateNodeForOpT<knopEndCode>()); |
| Assert(lastNodeRef); |
| Assert(*lastNodeRef); |
| Assert((*lastNodeRef)->nop == knopEndCode); |
| (*lastNodeRef)->ichMin = 0; |
| (*lastNodeRef)->ichLim = 0; |
| |
| // Get the extent of the code. |
| pnodeProg->ichLim = this->GetScanner()->IchLimTok(); |
| pnodeProg->cbLim = this->GetScanner()->IecpLimTok(); |
| |
| // Terminate the local list |
| *m_ppnodeVar = nullptr; |
| |
| Assert(nullptr == *m_ppnodeScope); |
| Assert(nullptr == pnodeProg->pnodeNext); |
| |
| #ifdef ENABLE_DEBUG_CONFIG_OPTIONS |
| if (Js::Configuration::Global.flags.IsEnabled(Js::ForceUndoDeferFlag)) |
| { |
| m_stoppedDeferredParse = true; |
| } |
| #endif |
| |
| if (m_stoppedDeferredParse) |
| { |
| #if ENABLE_BACKGROUND_PARSING |
| if (this->m_hasParallelJob) |
| { |
| BackgroundParser *bgp = static_cast<BackgroundParser*>(m_scriptContext->GetBackgroundParser()); |
| Assert(bgp); |
| this->WaitForBackgroundJobs(bgp, pse); |
| } |
| #endif |
| |
| // Do any remaining bindings of globals referenced in non-deferred functions. |
| if (pnodeGlobalEvalBlock) |
| { |
| FinishParseBlock(pnodeGlobalEvalBlock); |
| } |
| |
| FinishParseBlock(pnodeGlobalBlock); |
| |
| // Clear out references to undeclared identifiers. |
| this->GetHashTbl()->VisitPids([&](IdentPtr pid) { pid->SetTopRef(nullptr); }); |
| |
| // Restore global scope and blockinfo stacks preparatory to reparsing deferred functions. |
| PushScope(pnodeGlobalBlock->scope); |
| BlockInfoStack *newBlockInfo = PushBlockInfo(pnodeGlobalBlock); |
| PushStmt<true>(&newBlockInfo->pstmt, pnodeGlobalBlock, knopBlock, nullptr); |
| |
| if (pnodeGlobalEvalBlock) |
| { |
| PushScope(pnodeGlobalEvalBlock->scope); |
| newBlockInfo = PushBlockInfo(pnodeGlobalEvalBlock); |
| PushStmt<true>(&newBlockInfo->pstmt, pnodeGlobalEvalBlock, knopBlock, nullptr); |
| } |
| |
| // Finally, see if there are any function bodies we now want to generate because we |
| // decided to stop deferring. |
| FinishDeferredFunction(pnodeProg->pnodeScopes); |
| } |
| |
| if (pnodeGlobalEvalBlock) |
| { |
| FinishParseBlock(pnodeGlobalEvalBlock); |
| } |
| // Append block as body of pnodeProg |
| FinishParseBlock(pnodeGlobalBlock); |
| |
| m_scriptContext->AddSourceSize(m_length); |
| |
| if (m_parseType != ParseType_Deferred) |
| { |
| JS_ETW(EventWriteJSCRIPT_PARSE_METHOD_STOP(m_sourceContextInfo->dwHostSourceContext, GetScriptContext(), pnodeProg->functionId, *m_pCurrentAstSize, false, Js::Constants::GlobalFunction)); |
| } |
| |
| if (isModuleSource) |
| { |
| // verify that any local module exports are defined |
| VerifyModuleLocalExportEntries(); |
| } |
| |
| return pnodeProg; |
| } |
| |
| |
| bool Parser::CheckForDirective(bool* pIsUseStrict, bool *pIsUseAsm, bool* pIsOctalInString) |
| { |
| // A directive is a string constant followed by a statement terminating token |
| if (m_token.tk != tkStrCon) |
| return false; |
| |
| // Careful, need to check for octal before calling this->GetScanner()->Scan() |
| // because Scan() clears the "had octal" flag on the scanner and |
| // this->GetScanner()->Restore() does not restore this flag. |
| if (pIsOctalInString != nullptr) |
| { |
| *pIsOctalInString = this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber(); |
| } |
| |
| Ident* pidDirective = m_token.GetStr(); |
| RestorePoint start; |
| this->GetScanner()->Capture(&start); |
| this->GetScanner()->Scan(); |
| |
| bool isDirective = true; |
| |
| switch (m_token.tk) |
| { |
| case tkSColon: |
| case tkEOF: |
| case tkLCurly: |
| case tkRCurly: |
| break; |
| default: |
| if (!this->GetScanner()->FHadNewLine()) |
| { |
| isDirective = false; |
| } |
| break; |
| } |
| |
| if (isDirective) |
| { |
| if (pIsUseStrict != nullptr) |
| { |
| *pIsUseStrict = CheckStrictModeStrPid(pidDirective); |
| } |
| if (pIsUseAsm != nullptr) |
| { |
| *pIsUseAsm = CheckAsmjsModeStrPid(pidDirective); |
| } |
| } |
| |
| this->GetScanner()->SeekTo(start); |
| return isDirective; |
| } |
| |
| bool Parser::CheckStrictModeStrPid(IdentPtr pid) |
| { |
| #ifdef ENABLE_DEBUG_CONFIG_OPTIONS |
| if (Js::Configuration::Global.flags.NoStrictMode) |
| return false; |
| #endif |
| |
| return pid != nullptr && |
| pid->Cch() == 10 && |
| !this->GetScanner()->IsEscapeOnLastTkStrCon() && |
| wcsncmp(pid->Psz(), _u("use strict"), 10) == 0; |
| } |
| |
| bool Parser::CheckAsmjsModeStrPid(IdentPtr pid) |
| { |
| #ifdef ASMJS_PLAT |
| if (!CONFIG_FLAG(AsmJs)) |
| { |
| return false; |
| } |
| |
| bool isAsmCandidate = (pid != nullptr && |
| AutoSystemInfo::Data.SSE2Available() && |
| pid->Cch() == 7 && |
| !this->GetScanner()->IsEscapeOnLastTkStrCon() && |
| wcsncmp(pid->Psz(), _u("use asm"), 10) == 0); |
| |
| #ifdef ENABLE_SCRIPT_DEBUGGING |
| if (isAsmCandidate && m_scriptContext->IsScriptContextInDebugMode()) |
| { |
| // We would like to report this to debugger - they may choose to disable debugging. |
| // TODO : localization of the string? |
| m_scriptContext->RaiseMessageToDebugger(DEIT_ASMJS_IN_DEBUGGING, _u("AsmJs initialization error - AsmJs disabled due to script debugger"), m_sourceContextInfo && !m_sourceContextInfo->IsDynamic() ? m_sourceContextInfo->url : nullptr); |
| return false; |
| } |
| #endif |
| |
| return isAsmCandidate && !(m_grfscr & fscrNoAsmJs); |
| #else |
| return false; |
| #endif |
| } |
| |
| HRESULT Parser::ParseUtf8Source(__out ParseNodeProg ** parseTree, LPCUTF8 pSrc, size_t length, ULONG grfsrc, CompileScriptException *pse, |
| Js::LocalFunctionId * nextFunctionId, SourceContextInfo * sourceContextInfo) |
| { |
| m_functionBody = nullptr; |
| m_parseType = ParseType_Upfront; |
| return ParseSourceInternal(parseTree, pSrc, 0, length, 0, true, grfsrc, pse, nextFunctionId, 0, sourceContextInfo); |
| } |
| |
| HRESULT Parser::ParseCesu8Source(__out ParseNodeProg ** parseTree, LPCUTF8 pSrc, size_t length, ULONG grfsrc, CompileScriptException *pse, |
| Js::LocalFunctionId * nextFunctionId, SourceContextInfo * sourceContextInfo) |
| { |
| m_functionBody = nullptr; |
| m_parseType = ParseType_Upfront; |
| return ParseSourceInternal(parseTree, pSrc, 0, length, 0, false, grfsrc, pse, nextFunctionId, 0, sourceContextInfo); |
| } |
| |
| #if ENABLE_BACKGROUND_PARSING |
| void Parser::PrepareForBackgroundParse() |
| { |
| this->GetScanner()->PrepareForBackgroundParse(m_scriptContext); |
| } |
| |
| void Parser::AddBackgroundParseItem(BackgroundParseItem *const item) |
| { |
| if (currBackgroundParseItem == nullptr) |
| { |
| backgroundParseItems = item; |
| } |
| else |
| { |
| currBackgroundParseItem->SetNext(item); |
| } |
| currBackgroundParseItem = item; |
| } |
| |
| void Parser::AddFastScannedRegExpNode(ParseNodePtr const pnode) |
| { |
| Assert(!IsBackgroundParser()); |
| Assert(m_doingFastScan); |
| |
| if (fastScannedRegExpNodes == nullptr) |
| { |
| fastScannedRegExpNodes = Anew(&m_nodeAllocator, NodeDList, &m_nodeAllocator); |
| } |
| fastScannedRegExpNodes->Append(pnode); |
| } |
| |
| void Parser::AddBackgroundRegExpNode(ParseNodePtr const pnode) |
| { |
| Assert(IsBackgroundParser()); |
| Assert(currBackgroundParseItem != nullptr); |
| |
| currBackgroundParseItem->AddRegExpNode(pnode, &m_nodeAllocator); |
| } |
| |
| HRESULT Parser::ParseFunctionInBackground(ParseNodeFnc * pnodeFnc, ParseContext *parseContext, bool topLevelDeferred, CompileScriptException *pse) |
| { |
| m_functionBody = nullptr; |
| m_parseType = ParseType_Upfront; |
| HRESULT hr = S_OK; |
| SmartFPUControl smartFpuControl; |
| uint nextFunctionId = pnodeFnc->functionId + 1; |
| |
| this->RestoreContext(parseContext); |
| m_nextFunctionId = &nextFunctionId; |
| m_deferringAST = topLevelDeferred; |
| m_inDeferredNestedFunc = false; |
| m_scopeCountNoAst = 0; |
| |
| SetCurrentStatement(nullptr); |
| |
| pnodeFnc->pnodeVars = nullptr; |
| pnodeFnc->pnodeParams = nullptr; |
| pnodeFnc->pnodeBody = nullptr; |
| pnodeFnc->nestedCount = 0; |
| |
| ParseNodeFnc * pnodeParentFnc = GetCurrentFunctionNode(); |
| m_currentNodeFunc = pnodeFnc; |
| m_currentNodeDeferredFunc = nullptr; |
| m_ppnodeScope = nullptr; |
| m_ppnodeExprScope = nullptr; |
| |
| m_pnestedCount = &pnodeFnc->nestedCount; |
| m_pCurrentAstSize = &pnodeFnc->astSize; |
| |
| ParseNodeBlock * pnodeBlock = StartParseBlock<true>(PnodeBlockType::Function, ScopeType_FunctionBody); |
| pnodeFnc->pnodeScopes = pnodeBlock; |
| m_ppnodeScope = &pnodeBlock->pnodeScopes; |
| bool handled = false; |
| |
| uint uDeferSave = m_grfscr & (fscrCanDeferFncParse | fscrWillDeferFncParse); |
| |
| try |
| { |
| this->GetScanner()->Scan(); |
| |
| m_ppnodeVar = &pnodeFnc->pnodeParams; |
| this->ParseFncFormals<true>(pnodeFnc, pnodeParentFnc, fFncNoFlgs); |
| |
| if (m_token.tk == tkRParen) |
| { |
| this->GetScanner()->Scan(); |
| } |
| |
| ChkCurTok(tkLCurly, ERRnoLcurly); |
| |
| m_ppnodeVar = &pnodeFnc->pnodeVars; |
| |
| // Put the scanner into "no hashing" mode. |
| BYTE deferFlags = this->GetScanner()->SetDeferredParse(topLevelDeferred); |
| |
| // Process a sequence of statements/declarations |
| if (topLevelDeferred) |
| { |
| ParseStmtList<false>(nullptr, nullptr, SM_DeferredParse, true); |
| } |
| else |
| { |
| ParseNodePtr *lastNodeRef = nullptr; |
| ParseStmtList<true>(&pnodeFnc->pnodeBody, &lastNodeRef, SM_OnFunctionCode, true); |
| AddArgumentsNodeToVars(pnodeFnc); |
| // Append an EndCode node. |
| AddToNodeList(&pnodeFnc->pnodeBody, &lastNodeRef, CreateNodeForOpT<knopEndCode>()); |
| } |
| |
| // Restore the scanner's default hashing mode. |
| this->GetScanner()->SetDeferredParseFlags(deferFlags); |
| |
| #if DBG |
| pnodeFnc->deferredParseNextFunctionId = *this->m_nextFunctionId; |
| #endif |
| this->m_deferringAST = FALSE; |
| |
| // Append block as body of pnodeProg |
| FinishParseBlock(pnodeBlock); |
| } |
| catch (ParseExceptionObject& e) |
| { |
| hr = e.GetError(); |
| hr = pse->ProcessError(this->GetScanner(), hr, nullptr, e.GetStringOne(), e.GetStringTwo()); |
| handled = true; |
| } |
| |
| if (handled == false && FAILED(hr)) |
| { |
| hr = pse->ProcessError(this->GetScanner(), hr, nullptr); |
| } |
| |
| if (IsStrictMode()) |
| { |
| pnodeFnc->SetStrictMode(); |
| } |
| |
| if (topLevelDeferred) |
| { |
| pnodeFnc->pnodeVars = nullptr; |
| } |
| |
| m_grfscr |= uDeferSave; |
| |
| Assert(nullptr == *m_ppnodeScope); |
| |
| return hr; |
| } |
| |
| #endif |
| |
| HRESULT Parser::ParseSourceWithOffset(__out ParseNodeProg ** parseTree, LPCUTF8 pSrc, size_t offset, size_t cbLength, charcount_t cchOffset, |
| bool isCesu8, ULONG grfscr, CompileScriptException *pse, Js::LocalFunctionId * nextFunctionId, ULONG lineNumber, SourceContextInfo * sourceContextInfo, |
| Js::ParseableFunctionInfo* functionInfo) |
| { |
| m_functionBody = functionInfo; |
| if (m_functionBody) |
| { |
| m_currDeferredStub = m_functionBody->GetDeferredStubs(); |
| m_currDeferredStubCount = m_currDeferredStub != nullptr ? m_functionBody->GetNestedCount() : 0; |
| m_InAsmMode = grfscr & fscrNoAsmJs ? false : m_functionBody->GetIsAsmjsMode(); |
| } |
| m_deferAsmJs = !m_InAsmMode; |
| m_parseType = ParseType_Deferred; |
| return ParseSourceInternal(parseTree, pSrc, offset, cbLength, cchOffset, !isCesu8, grfscr, pse, nextFunctionId, lineNumber, sourceContextInfo); |
| } |
| |
| bool Parser::IsStrictMode() const |
| { |
| return (m_fUseStrictMode || |
| (m_currentNodeFunc != nullptr && m_currentNodeFunc->GetStrictMode())); |
| } |
| |
| BOOL Parser::ExpectingExternalSource() |
| { |
| return m_fExpectExternalSource; |
| } |
| |
| Symbol *ParseNodeFnc::GetFuncSymbol() |
| { |
| if (pnodeName) |
| { |
| Assert(pnodeName->nop == knopVarDecl); |
| return pnodeName->sym; |
| } |
| return nullptr; |
| } |
| |
| void ParseNodeFnc::SetFuncSymbol(Symbol *sym) |
| { |
| Assert(pnodeName); |
| Assert(pnodeName->nop == knopVarDecl); |
| pnodeName->sym = sym; |
| } |
| |
| ParseNodePtr ParseNodeFnc::GetParamScope() const |
| { |
| if (this->pnodeScopes == nullptr) |
| { |
| return nullptr; |
| } |
| Assert(this->pnodeScopes->nop == knopBlock && |
| this->pnodeScopes->pnodeNext == nullptr); |
| return this->pnodeScopes->pnodeScopes; |
| } |
| |
| ParseNodePtr ParseNodeFnc::GetBodyScope() const |
| { |
| if (this->pnodeBodyScope == nullptr) |
| { |
| return nullptr; |
| } |
| Assert(this->pnodeBodyScope->nop == knopBlock && |
| this->pnodeBodyScope->pnodeNext == nullptr); |
| return this->pnodeBodyScope->pnodeScopes; |
| } |
| |
| bool ParseNodeBlock::HasBlockScopedContent() const |
| { |
| // A block has its own content if a let, const, or function is declared there. |
| |
| if (this->pnodeLexVars != nullptr || this->blockType == Parameter) |
| { |
| return true; |
| } |
| |
| // The enclosing scopes can contain functions and other things, so walk the list |
| // looking specifically for functions. |
| |
| for (ParseNodePtr pnode = this->pnodeScopes; pnode;) |
| { |
| switch (pnode->nop) { |
| |
| case knopFncDecl: |
| return true; |
| |
| case knopBlock: |
| pnode = pnode->AsParseNodeBlock()->pnodeNext; |
| break; |
| |
| case knopCatch: |
| pnode = pnode->AsParseNodeCatch()->pnodeNext; |
| break; |
| |
| case knopWith: |
| pnode = pnode->AsParseNodeWith()->pnodeNext; |
| break; |
| |
| default: |
| Assert(UNREACHED); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| class ByteCodeGenerator; |
| |
| // Copy AST; this works mostly on expressions for now |
| ParseNode* Parser::CopyPnode(ParseNode *pnode) { |
| if (pnode == NULL) |
| return NULL; |
| switch (pnode->nop) { |
| //PTNODE(knopName , "name" ,None ,Pid ,fnopLeaf) |
| case knopName: { |
| ParseNodeName * nameNode = CreateNameNode(pnode->AsParseNodeName()->pid); |
| nameNode->ichMin = pnode->ichMin; |
| nameNode->ichLim = pnode->ichLim; |
| nameNode->sym = pnode->AsParseNodeName()->sym; |
| return nameNode; |
| } |
| //PTNODE(knopInt , "int const" ,None ,Int ,fnopLeaf|fnopConst) |
| case knopInt: |
| return pnode; |
| //PTNODE(knopBigInt , "bigint const" ,None ,BigInt ,fnopLeaf|fnopConst) |
| case knopBigInt: |
| return pnode; |
| //PTNODE(knopFlt , "flt const" ,None ,Flt ,fnopLeaf|fnopConst) |
| case knopFlt: |
| return pnode; |
| //PTNODE(knopStr , "str const" ,None ,Pid ,fnopLeaf|fnopConst) |
| case knopStr: |
| return pnode; |
| //PTNODE(knopRegExp , "reg expr" ,None ,Pid ,fnopLeaf|fnopConst) |
| case knopRegExp: |
| return pnode; |
| break; |
| //PTNODE(knopNull , "null" ,Null ,None ,fnopLeaf) |
| case knopNull: |
| return pnode; |
| //PTNODE(knopFalse , "false" ,False ,None ,fnopLeaf) |
| case knopFalse: |
| { |
| ParseNode* ret = CreateNodeForOpT<knopFalse>(pnode->ichMin, pnode->ichLim); |
| ret->location = pnode->location; |
| return ret; |
| } |
| //PTNODE(knopTrue , "true" ,True ,None ,fnopLeaf) |
| case knopTrue: |
| { |
| ParseNode* ret = CreateNodeForOpT<knopTrue>(pnode->ichMin, pnode->ichLim); |
| ret->location = pnode->location; |
| return ret; |
| } |
| //PTNODE(knopEmpty , "empty" ,Empty ,None ,fnopLeaf) |
| case knopEmpty: |
| return CreateNodeForOpT<knopEmpty>(pnode->ichMin, pnode->ichLim); |
| // Unary operators. |
| //PTNODE(knopNot , "~" ,BitNot ,Uni ,fnopUni) |
| //PTNODE(knopNeg , "unary -" ,Neg ,Uni ,fnopUni) |
| //PTNODE(knopPos , "unary +" ,Pos ,Uni ,fnopUni) |
| //PTNODE(knopLogNot , "!" ,LogNot ,Uni ,fnopUni) |
| //PTNODE(knopEllipsis , "..." ,Spread ,Uni , fnopUni) |
| //PTNODE(knopDecPost , "-- post" ,Dec ,Uni ,fnopUni|fnopAsg) |
| //PTNODE(knopIncPre , "++ pre" ,Inc ,Uni ,fnopUni|fnopAsg) |
| //PTNODE(knopDecPre , "-- pre" ,Dec ,Uni ,fnopUni|fnopAsg) |
| //PTNODE(knopTypeof , "typeof" ,None ,Uni ,fnopUni) |
| //PTNODE(knopVoid , "void" ,Void ,Uni ,fnopUni) |
| //PTNODE(knopDelete , "delete" ,None ,Uni ,fnopUni) |
| case knopNot: |
| case knopNeg: |
| case knopPos: |
| case knopLogNot: |
| case knopEllipsis: |
| case knopIncPost: |
| case knopDecPost: |
| case knopIncPre: |
| case knopDecPre: |
| case knopTypeof: |
| case knopVoid: |
| case knopDelete: |
| return CreateUniNode(pnode->nop, CopyPnode(pnode->AsParseNodeUni()->pnode1), pnode->ichMin, pnode->ichLim); |
| //PTNODE(knopArray , "arr cnst" ,None ,Uni ,fnopUni) |
| //PTNODE(knopObject , "obj cnst" ,None ,Uni ,fnopUni) |
| case knopArray: |
| case knopObject: |
| // TODO: need to copy arr |
| Assert(false); |
| break; |
| // Binary operators |
| //PTNODE(knopAdd , "+" ,Add ,Bin ,fnopBin) |
| //PTNODE(knopSub , "-" ,Sub ,Bin ,fnopBin) |
| //PTNODE(knopMul , "*" ,Mul ,Bin ,fnopBin) |
| //PTNODE(knopExpo , "**" ,Expo ,Bin ,fnopBin) |
| //PTNODE(knopDiv , "/" ,Div ,Bin ,fnopBin) |
| //PTNODE(knopMod , "%" ,Mod ,Bin ,fnopBin) |
| //PTNODE(knopOr , "|" ,BitOr ,Bin ,fnopBin) |
| //PTNODE(knopXor , "^" ,BitXor ,Bin ,fnopBin) |
| //PTNODE(knopAnd , "&" ,BitAnd ,Bin ,fnopBin) |
| //PTNODE(knopEq , "==" ,EQ ,Bin ,fnopBin|fnopRel) |
| //PTNODE(knopNe , "!=" ,NE ,Bin ,fnopBin|fnopRel) |
| //PTNODE(knopLt , "<" ,LT ,Bin ,fnopBin|fnopRel) |
| //PTNODE(knopLe , "<=" ,LE ,Bin ,fnopBin|fnopRel) |
| //PTNODE(knopGe , ">=" ,GE ,Bin ,fnopBin|fnopRel) |
| //PTNODE(knopGt , ">" ,GT ,Bin ,fnopBin|fnopRel) |
| //PTNODE(knopEqv , "===" ,Eqv ,Bin ,fnopBin|fnopRel) |
| //PTNODE(knopIn , "in" ,In ,Bin ,fnopBin|fnopRel) |
| //PTNODE(knopInstOf , "instanceof",InstOf ,Bin ,fnopBin|fnopRel) |
| //PTNODE(knopNEqv , "!==" ,NEqv ,Bin ,fnopBin|fnopRel) |
| //PTNODE(knopComma , "," ,None ,Bin ,fnopBin) |
| //PTNODE(knopLogOr , "||" ,None ,Bin ,fnopBin) |
| //PTNODE(knopLogAnd , "&&" ,None ,Bin ,fnopBin) |
| //PTNODE(knopLsh , "<<" ,Lsh ,Bin ,fnopBin) |
| //PTNODE(knopRsh , ">>" ,Rsh ,Bin ,fnopBin) |
| //PTNODE(knopRs2 , ">>>" ,Rs2 ,Bin ,fnopBin) |
| case knopAdd: |
| case knopSub: |
| case knopMul: |
| case knopExpo: |
| case knopDiv: |
| case knopMod: |
| case knopOr: |
| case knopXor: |
| case knopAnd: |
| case knopEq: |
| case knopNe: |
| case knopLt: |
| case knopLe: |
| case knopGe: |
| case knopGt: |
| case knopEqv: |
| case knopIn: |
| case knopInstOf: |
| case knopNEqv: |
| case knopComma: |
| case knopLogOr: |
| case knopLogAnd: |
| case knopLsh: |
| case knopRsh: |
| case knopRs2: |
| //PTNODE(knopAsg , "=" ,None ,Bin ,fnopBin|fnopAsg) |
| case knopAsg: |
| //PTNODE(knopDot , "." ,None ,Bin ,fnopBin) |
| case knopDot: |
| //PTNODE(knopAsgAdd , "+=" ,Add ,Bin ,fnopBin|fnopAsg) |
| case knopAsgAdd: |
| //PTNODE(knopAsgSub , "-=" ,Sub ,Bin ,fnopBin|fnopAsg) |
| case knopAsgSub: |
| //PTNODE(knopAsgMul , "*=" ,Mul ,Bin ,fnopBin|fnopAsg) |
| case knopAsgMul: |
| //PTNODE(knopAsgDiv , "/=" ,Div ,Bin ,fnopBin|fnopAsg) |
| case knopAsgExpo: |
| //PTNODE(knopAsgExpo , "**=" ,Expo ,Bin ,fnopBin|fnopAsg) |
| case knopAsgDiv: |
| //PTNODE(knopAsgMod , "%=" ,Mod ,Bin ,fnopBin|fnopAsg) |
| case knopAsgMod: |
| //PTNODE(knopAsgAnd , "&=" ,BitAnd ,Bin ,fnopBin|fnopAsg) |
| case knopAsgAnd: |
| //PTNODE(knopAsgXor , "^=" ,BitXor ,Bin ,fnopBin|fnopAsg) |
| case knopAsgXor: |
| //PTNODE(knopAsgOr , "|=" ,BitOr ,Bin ,fnopBin|fnopAsg) |
| case knopAsgOr: |
| //PTNODE(knopAsgLsh , "<<=" ,Lsh ,Bin ,fnopBin|fnopAsg) |
| case knopAsgLsh: |
| //PTNODE(knopAsgRsh , ">>=" ,Rsh ,Bin ,fnopBin|fnopAsg) |
| case knopAsgRsh: |
| //PTNODE(knopAsgRs2 , ">>>=" ,Rs2 ,Bin ,fnopBin|fnopAsg) |
| case knopAsgRs2: |
| //PTNODE(knopMember , ":" ,None ,Bin ,fnopBin) |
| case knopMember: |
| case knopMemberShort: |
| //PTNODE(knopIndex , "[]" ,None ,Bin ,fnopBin) |
| //PTNODE(knopList , "<list>" ,None ,Bin ,fnopNone) |
| |
| case knopIndex: |
| case knopList: |
| return CreateBinNode(pnode->nop, CopyPnode(pnode->AsParseNodeBin()->pnode1), |
| CopyPnode(pnode->AsParseNodeBin()->pnode2), pnode->ichMin, pnode->ichLim); |
| |
| //PTNODE(knopCall , "()" ,None ,Bin ,fnopBin) |
| //PTNODE(knopNew , "new" ,None ,Bin ,fnopBin) |
| case knopNew: |
| case knopCall: |
| return CreateCallNode(pnode->nop, CopyPnode(pnode->AsParseNodeCall()->pnodeTarget), |
| CopyPnode(pnode->AsParseNodeCall()->pnodeArgs), pnode->ichMin, pnode->ichLim); |
| //PTNODE(knopQmark , "?" ,None ,Tri ,fnopBin) |
| case knopQmark: |
| return CreateTriNode(pnode->nop, CopyPnode(pnode->AsParseNodeTri()->pnode1), |
| CopyPnode(pnode->AsParseNodeTri()->pnode2), CopyPnode(pnode->AsParseNodeTri()->pnode3), |
| pnode->ichMin, pnode->ichLim); |
| // General nodes. |
| //PTNODE(knopVarDecl , "varDcl" ,None ,Var ,fnopNone) |
| case knopVarDecl: { |
| ParseNodeVar* copyNode = Anew(&m_nodeAllocator, ParseNodeVar, knopVarDecl, pnode->ichMin, pnode->ichLim, nullptr); |
| copyNode->pnodeInit = CopyPnode(pnode->AsParseNodeVar()->pnodeInit); |
| copyNode->sym = pnode->AsParseNodeVar()->sym; |
| // TODO: mult-decl |
| Assert(pnode->AsParseNodeVar()->pnodeNext == NULL); |
| copyNode->pnodeNext = NULL; |
| return copyNode; |
| } |
| //PTNODE(knopFncDecl , "fncDcl" ,None ,Fnc ,fnopLeaf) |
| //PTNODE(knopProg , "program" ,None ,Fnc ,fnopNone) |
| case knopFncDecl: |
| case knopProg: |
| Assert(false); |
| break; |
| //PTNODE(knopEndCode , "<endcode>" ,None ,None ,fnopNone) |
| case knopEndCode: |
| break; |
| //PTNODE(knopDebugger , "debugger" ,None ,None ,fnopNone) |
| case knopDebugger: |
| break; |
| //PTNODE(knopFor , "for" ,None ,For ,fnopBreak|fnopContinue) |
| case knopFor: { |
| ParseNode* copyNode = CreateNodeForOpT<knopFor>(pnode->ichMin, pnode->ichLim); |
| copyNode->AsParseNodeFor()->pnodeInverted = NULL; |
| copyNode->AsParseNodeFor()->pnodeInit = CopyPnode(pnode->AsParseNodeFor()->pnodeInit); |
| copyNode->AsParseNodeFor()->pnodeCond = CopyPnode(pnode->AsParseNodeFor()->pnodeCond); |
| copyNode->AsParseNodeFor()->pnodeIncr = CopyPnode(pnode->AsParseNodeFor()->pnodeIncr); |
| copyNode->AsParseNodeFor()->pnodeBody = CopyPnode(pnode->AsParseNodeFor()->pnodeBody); |
| return copyNode; |
| } |
| //PTNODE(knopIf , "if" ,None ,If ,fnopNone) |
| case knopIf: |
| Assert(false); |
| break; |
| //PTNODE(knopWhile , "while" ,None ,While,fnopBreak|fnopContinue) |
| case knopWhile: |
| Assert(false); |
| break; |
| //PTNODE(knopDoWhile , "do-while" ,None ,While,fnopBreak|fnopContinue) |
| case knopDoWhile: |
| Assert(false); |
| break; |
| //PTNODE(knopForIn , "for in" ,None ,ForIn,fnopBreak|fnopContinue|fnopCleanup) |
| case knopForIn: |
| Assert(false); |
| break; |
| case knopForOf: |
| Assert(false); |
| break; |
| //PTNODE(knopReturn , "return" ,None ,Uni ,fnopNone) |
| case knopReturn: { |
| ParseNode* copyNode = CreateNodeForOpT<knopReturn>(pnode->ichMin, pnode->ichLim); |
| copyNode->AsParseNodeReturn()->pnodeExpr = CopyPnode(pnode->AsParseNodeReturn()->pnodeExpr); |
| return copyNode; |
| } |
| //PTNODE(knopBlock , "{}" ,None ,Block,fnopNone) |
| case knopBlock: { |
| ParseNodeBlock* copyNode = CreateBlockNode(pnode->ichMin, pnode->ichLim, pnode->AsParseNodeBlock()->blockType); |
| if (pnode->grfpn & PNodeFlags::fpnSyntheticNode) { |
| // fpnSyntheticNode is sometimes set on PnodeBlockType::Regular blocks which |
| // CreateBlockNode() will not automatically set for us, so set it here if it's |
| // specified on the source node. |
| copyNode->grfpn |= PNodeFlags::fpnSyntheticNode; |
| } |
| copyNode->pnodeStmt = CopyPnode(pnode->AsParseNodeBlock()->pnodeStmt); |
| return copyNode; |
| } |
| //PTNODE(knopWith , "with" ,None ,With ,fnopCleanup) |
| case knopWith: |
| Assert(false); |
| break; |
| //PTNODE(knopBreak , "break" ,None ,Jump ,fnopNone) |
| case knopBreak: |
| Assert(false); |
| break; |
| //PTNODE(knopContinue , "continue" ,None ,Jump ,fnopNone) |
| case knopContinue: |
| Assert(false); |
| break; |
| //PTNODE(knopSwitch , "switch" ,None ,Switch,fnopBreak) |
| case knopSwitch: |
| Assert(false); |
| break; |
| //PTNODE(knopCase , "case" ,None ,Case ,fnopNone) |
| case knopCase: |
| Assert(false); |
| break; |
| //PTNODE(knopTryFinally,"try-finally",None,TryFinally,fnopCleanup) |
| case knopTryFinally: |
| Assert(false); |
| break; |
| case knopFinally: |
| Assert(false); |
| break; |
| //PTNODE(knopCatch , "catch" ,None ,Catch,fnopNone) |
| case knopCatch: |
| Assert(false); |
| break; |
| //PTNODE(knopTryCatch , "try-catch" ,None ,TryCatch ,fnopCleanup) |
| case knopTryCatch: |
| Assert(false); |
| break; |
| //PTNODE(knopTry , "try" ,None ,Try ,fnopCleanup) |
| case knopTry: |
| Assert(false); |
| break; |
| //PTNODE(knopThrow , "throw" ,None ,Uni ,fnopNone) |
| case knopThrow: |
| Assert(false); |
| break; |
| default: |
| Assert(false); |
| break; |
| } |
| return NULL; |
| } |
| |
| // Returns true when str is string for Nan, Infinity or -Infinity. |
| // Does not check for double number value being in NaN/Infinity range. |
| // static |
| template<bool CheckForNegativeInfinity> |
| inline bool Parser::IsNaNOrInfinityLiteral(LPCOLESTR str) |
| { |
| // Note: wcscmp crashes when one of the parameters is NULL. |
| return str && |
| (wcscmp(_u("NaN"), str) == 0 || |
| wcscmp(_u("Infinity"), str) == 0 || |
| (CheckForNegativeInfinity && wcscmp(_u("-Infinity"), str) == 0)); |
| } |
| |
| template <bool buildAST> |
| IdentPtr Parser::ParseSuper(bool fAllowCall) |
| { |
| ParseNodeFnc * currentNodeFunc = GetCurrentFunctionNode(); |
| ParseNodeFnc * currentNonLambdaFunc = GetCurrentNonLambdaFunctionNode(); |
| IdentPtr superPid = nullptr; |
| |
| switch (m_token.tk) |
| { |
| case tkDot: // super.prop |
| case tkLBrack: // super[foo] |
| superPid = wellKnownPropertyPids._super; |
| break; |
| case tkLParen: // super(args) |
| superPid = wellKnownPropertyPids._superConstructor; |
| break; |
| |
| default: |
| Error(ERRInvalidSuper); |
| break; |
| } |
| |
| currentNodeFunc->SetHasSuperReference(TRUE); |
| CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, Super, m_scriptContext); |
| |
| // If we are defer parsing, we can skip verifying that the super reference is valid. |
| // If it wasn't the parser would have thrown during upfront parsing and we wouldn't be defer parsing the function. |
| if (m_parseType == ParseType_Deferred) |
| { |
| return superPid; |
| } |
| |
| if (!fAllowCall && (m_token.tk == tkLParen)) |
| { |
| Error(ERRInvalidSuper); // new super() is not allowed |
| } |
| else if ((currentNodeFunc->IsConstructor() && currentNodeFunc->superRestrictionState == SuperRestrictionState::CallAndPropertyAllowed) |
| || (currentNonLambdaFunc != nullptr && currentNonLambdaFunc->superRestrictionState == SuperRestrictionState::CallAndPropertyAllowed)) |
| { |
| // Any super access is good within a class constructor |
| } |
| else if ((this->m_grfscr & fscrEval) == fscrEval || (currentNonLambdaFunc != nullptr && currentNonLambdaFunc->superRestrictionState == SuperRestrictionState::PropertyAllowed)) |
| { |
| // Currently for eval cases during compile time we use propertyallowed and throw during runtime for error cases |
| if (m_token.tk == tkLParen) |
| { |
| if ((this->m_grfscr & fscrEval) == fscrNil) |
| { |
| // Cannot call super within a class member |
| Error(ERRInvalidSuper); |
| } |
| else |
| { |
| Js::JavascriptFunction * caller = nullptr; |
| if (Js::JavascriptStackWalker::GetCaller(&caller, m_scriptContext)) |
| { |
| Js::FunctionBody * callerBody = caller->GetFunctionBody(); |
| Assert(callerBody); |
| if (!callerBody->GetFunctionInfo()->GetAllowDirectSuper()) |
| { |
| Error(ERRInvalidSuper); |
| } |
| } |
| } |
| } |
| } |
| else |
| { |
| // Anything else is an error |
| Error(ERRInvalidSuper); |
| } |
| |
| return superPid; |
| } |
| |
| void Parser::AppendToList(ParseNodePtr *node, ParseNodePtr nodeToAppend) |
| { |
| Assert(nodeToAppend); |
| ParseNodePtr* lastPtr = node; |
| while ((*lastPtr) && (*lastPtr)->nop == knopList) |
| { |
| lastPtr = &(*lastPtr)->AsParseNodeBin()->pnode2; |
| } |
| auto last = (*lastPtr); |
| if (last) |
| { |
| *lastPtr = CreateBinNode(knopList, last, nodeToAppend, last->ichMin, nodeToAppend->ichLim); |
| } |
| else |
| { |
| *lastPtr = nodeToAppend; |
| } |
| } |
| |
| ParseNodePtr Parser::ConvertArrayToArrayPattern(ParseNodePtr pnode) |
| { |
| Assert(pnode->nop == knopArray); |
| pnode->nop = knopArrayPattern; |
| |
| ForEachItemRefInList(&pnode->AsParseNodeArrLit()->pnode1, [&](ParseNodePtr *itemRef) { |
| ParseNodePtr item = *itemRef; |
| if (item->nop == knopEllipsis) |
| { |
| itemRef = &item->AsParseNodeUni()->pnode1; |
| item = *itemRef; |
| if (!(item->nop == knopName |
| || item->nop == knopDot |
| || item->nop == knopIndex |
| || item->nop == knopArray |
| || item->nop == knopObject)) |
| { |
| Error(ERRInvalidAssignmentTarget); |
| } |
| } |
| else if (item->nop == knopAsg) |
| { |
| itemRef = &item->AsParseNodeBin()->pnode1; |
| item = *itemRef; |
| } |
| |
| if (item->nop == knopArray) |
| { |
| ConvertArrayToArrayPattern(item); |
| } |
| else if (item->nop == knopObject) |
| { |
| *itemRef = ConvertObjectToObjectPattern(item); |
| } |
| }); |
| |
| return pnode; |
| } |
| |
| ParseNodeUni * Parser::ConvertObjectToObjectPattern(ParseNodePtr pnodeMemberList) |
| { |
| charcount_t ichMin = this->GetScanner()->IchMinTok(); |
| charcount_t ichLim = this->GetScanner()->IchLimTok(); |
| if (pnodeMemberList != nullptr && pnodeMemberList->nop == knopObject) |
| { |
| ichMin = pnodeMemberList->ichMin; |
| ichLim = pnodeMemberList->ichLim; |
| pnodeMemberList = pnodeMemberList->AsParseNodeUni()->pnode1; |
| } |
| |
| ParseNodeObjLit * objectPatternNode = CreateObjectPatternNode(pnodeMemberList, ichMin, ichLim, true/*convertToPattern*/); |
| return objectPatternNode; |
| } |
| |
| ParseNodePtr Parser::GetRightSideNodeFromPattern(ParseNodePtr pnode) |
| { |
| Assert(pnode != nullptr); |
| ParseNodePtr rightNode = nullptr; |
| OpCode op = pnode->nop; |
| if (op == knopObject) |
| { |
| rightNode = ConvertObjectToObjectPattern(pnode); |
| } |
| else if (op == knopArray) |
| { |
| rightNode = ConvertArrayToArrayPattern(pnode); |
| } |
| else |
| { |
| rightNode = pnode; |
| if (op == knopAsg) |
| { |
| TrackAssignment<true>(pnode->AsParseNodeBin()->pnode1, nullptr); |
| } |
| } |
| |
| return rightNode; |
| } |
| |
| ParseNodePtr Parser::ConvertMemberToMemberPattern(ParseNodePtr pnodeMember) |
| { |
| if (pnodeMember->nop == knopObjectPatternMember || pnodeMember->nop == knopEllipsis) |
| { |
| return pnodeMember; |
| } |
| |
| Assert(pnodeMember->nop == knopMember || pnodeMember->nop == knopMemberShort); |
| |
| ParseNodePtr rightNode = GetRightSideNodeFromPattern(pnodeMember->AsParseNodeBin()->pnode2); |
| ParseNodePtr resultNode = CreateBinNode(knopObjectPatternMember, pnodeMember->AsParseNodeBin()->pnode1, rightNode); |
| resultNode->ichMin = pnodeMember->ichMin; |
| resultNode->ichLim = pnodeMember->ichLim; |
| return resultNode; |
| } |
| |
| ParseNodePtr Parser::ConvertToPattern(ParseNodePtr pnode) |
| { |
| if (pnode != nullptr) |
| { |
| if (pnode->nop == knopArray) |
| { |
| ConvertArrayToArrayPattern(pnode); |
| } |
| else if (pnode->nop == knopObject) |
| { |
| pnode = ConvertObjectToObjectPattern(pnode); |
| } |
| } |
| return pnode; |
| } |
| |
| // This essentially be called for verifying the structure of the current tree with satisfying the destructuring grammar. |
| void Parser::ParseDestructuredLiteralWithScopeSave(tokens declarationType, |
| bool isDecl, |
| bool topLevel, |
| DestructuringInitializerContext initializerContext/* = DIC_None*/, |
| bool allowIn /*= true*/) |
| { |
| // We are going to parse the text again to validate the current grammar as Destructuring. Saving some scopes and |
| // AST related information before the validation parsing and later they will be restored. |
| |
| ParseNodeFnc * pnodeFncSave = m_currentNodeFunc; |
| ParseNodeFnc * pnodeDeferredFncSave = m_currentNodeDeferredFunc; |
| if (m_currentNodeDeferredFunc == nullptr) |
| { |
| m_currentNodeDeferredFunc = m_currentNodeFunc; |
| } |
| int32 *pAstSizeSave = m_pCurrentAstSize; |
| uint *pNestedCountSave = m_pnestedCount; |
| ParseNodePtr *ppnodeScopeSave = m_ppnodeScope; |
| ParseNodePtr *ppnodeExprScopeSave = m_ppnodeExprScope; |
| |
| ParseNodePtr newTempScope = nullptr; |
| m_ppnodeScope = &newTempScope; |
| |
| int32 newTempAstSize = 0; |
| m_pCurrentAstSize = &newTempAstSize; |
| |
| uint newTempNestedCount = 0; |
| m_pnestedCount = &newTempNestedCount; |
| |
| m_ppnodeExprScope = nullptr; |
| |
| charcount_t funcInArraySave = m_funcInArray; |
| uint funcInArrayDepthSave = m_funcInArrayDepth; |
| |
| // we need to reset this as we are going to parse the grammar again. |
| m_hasDeferredShorthandInitError = false; |
| |
| ParseDestructuredLiteral<false>(declarationType, isDecl, topLevel, initializerContext, allowIn); |
| |
| m_currentNodeFunc = pnodeFncSave; |
| m_currentNodeDeferredFunc = pnodeDeferredFncSave; |
| m_pCurrentAstSize = pAstSizeSave; |
| m_pnestedCount = pNestedCountSave; |
| m_ppnodeScope = ppnodeScopeSave; |
| m_ppnodeExprScope = ppnodeExprScopeSave; |
| m_funcInArray = funcInArraySave; |
| m_funcInArrayDepth = funcInArrayDepthSave; |
| } |
| |
| template <bool buildAST> |
| ParseNodePtr Parser::ParseDestructuredLiteral(tokens declarationType, |
| bool isDecl, |
| bool topLevel/* = true*/, |
| DestructuringInitializerContext initializerContext/* = DIC_None*/, |
| bool allowIn/* = true*/, |
| BOOL *forInOfOkay/* = nullptr*/, |
| BOOL *nativeForOkay/* = nullptr*/) |
| { |
| ParseNodeUni * pnode = nullptr; |
| Assert(IsPossiblePatternStart()); |
| |
| PROBE_STACK_NO_DISPOSE(m_scriptContext, Js::Constants::MinStackDefault); |
| |
| if (m_token.tk == tkLCurly) |
| { |
| pnode = ParseDestructuredObjectLiteral<buildAST>(declarationType, isDecl, topLevel); |
| } |
| else |
| { |
| pnode = ParseDestructuredArrayLiteral<buildAST>(declarationType, isDecl, topLevel); |
| } |
| |
| return ParseDestructuredInitializer<buildAST>(pnode, isDecl, topLevel, initializerContext, allowIn, forInOfOkay, nativeForOkay); |
| } |
| |
| template <bool buildAST> |
| ParseNodePtr Parser::ParseDestructuredInitializer(ParseNodeUni * lhsNode, |
| bool isDecl, |
| bool topLevel, |
| DestructuringInitializerContext initializerContext, |
| bool allowIn, |
| BOOL *forInOfOkay, |
| BOOL *nativeForOkay) |
| { |
| this->GetScanner()->Scan(); |
| if (topLevel && nativeForOkay == nullptr) |
| { |
| if (initializerContext != DIC_ForceErrorOnInitializer && m_token.tk != tkAsg) |
| { |
| // e.g. var {x}; |
| Error(ERRDestructInit); |
| } |
| else if (initializerContext == DIC_ForceErrorOnInitializer && m_token.tk == tkAsg) |
| { |
| // e.g. catch([x] = [0]) |
| Error(ERRDestructNotInit); |
| } |
| } |
| |
| if (m_token.tk != tkAsg || initializerContext == DIC_ShouldNotParseInitializer) |
| { |
| if (topLevel && nativeForOkay != nullptr) |
| { |
| // Native loop should have destructuring initializer |
| *nativeForOkay = FALSE; |
| } |
| |
| return lhsNode; |
| } |
| |
| if (forInOfOkay) |
| { |
| *forInOfOkay = FALSE; |
| } |
| |
| this->GetScanner()->Scan(); |
| |
| |
| bool alreadyHasInitError = m_hasDeferredShorthandInitError; |
| |
| ParseNodePtr pnodeDefault = ParseExpr<buildAST>(koplCma, nullptr, allowIn); |
| |
| if (m_hasDeferredShorthandInitError && !alreadyHasInitError) |
| { |
| Error(ERRnoColon); |
| } |
| |
| ParseNodeBin * pnodeDestructAsg = nullptr; |
| if (buildAST) |
| { |
| Assert(lhsNode != nullptr); |
| |
| pnodeDestructAsg = CreateBinNode(knopAsg, lhsNode, pnodeDefault, lhsNode->ichMin, pnodeDefault->ichLim); |
| } |
| return pnodeDestructAsg; |
| } |
| |
| template <bool buildAST> |
| ParseNodeUni * Parser::ParseDestructuredObjectLiteral(tokens declarationType, bool isDecl, bool topLevel/* = true*/) |
| { |
| Assert(m_token.tk == tkLCurly); |
| charcount_t ichMin = this->GetScanner()->IchMinTok(); |
| this->GetScanner()->Scan(); |
| |
| if (!isDecl) |
| { |
| declarationType = tkLCurly; |
| } |
| ParseNodePtr pnodeMemberList = ParseMemberList<buildAST>(nullptr/*pNameHint*/, nullptr/*pHintLength*/, declarationType); |
| |
| charcount_t ichLim = this->GetScanner()->IchLimTok(); |
| |
| ParseNodeObjLit * objectPatternNode = buildAST ? CreateObjectPatternNode(pnodeMemberList, ichMin, ichLim) : nullptr; |
| |
| Assert(m_token.tk == tkRCurly); |
| return objectPatternNode; |
| } |
| |
| |
| |
| template <bool buildAST> |
| ParseNodePtr Parser::ParseDestructuredVarDecl(tokens declarationType, bool isDecl, bool *hasSeenRest, bool topLevel/* = true*/, bool allowEmptyExpression/* = true*/, bool isObjectPattern/* =false*/) |
| { |
| ParseNodePtr pnodeElem = nullptr; |
| int parenCount = 0; |
| bool seenRest = false; |
| IdentToken token; |
| |
| // Save the Block ID prior to the increments, so we can restore it back. |
| int originalCurrentBlockId = GetCurrentBlock()->blockId; |
| |
| // Eat the left parentheses only when its not a declaration. This will make sure we throw syntax errors early. |
| if (!isDecl) |
| { |
| while (m_token.tk == tkLParen) |
| { |
| this->GetScanner()->Scan(); |
| ++parenCount; |
| |
| // Match the block increment we do upon entering parenthetical expressions |
| // so that the block ID's will match on reparsing of parameters. |
| GetCurrentBlock()->blockId = m_nextBlockId++; |
| } |
| } |
| |
| if (m_token.tk == tkEllipsis) |
| { |
| // As per ES 2015 : Rest can have left-hand-side-expression when on assignment expression, but under declaration only binding identifier is allowed |
| // But spec is going to change for this one to allow LHS-expression both on expression and declaration - so making that happen early. |
| |
| seenRest = true; |
| this->GetScanner()->Scan(); |
| |
| // Eat the left parentheses only when its not a declaration. This will make sure we throw syntax errors early. |
| if (!isDecl) |
| { |
| while (m_token.tk == tkLParen) |
| { |
| this->GetScanner()->Scan(); |
| ++parenCount; |
| |
| // Match the block increment we do upon entering parenthetical expressions |
| // so that the block ID's will match on reparsing of parameters. |
| GetCurrentBlock()->blockId = m_nextBlockId++; |
| } |
| } |
| |
| |
| if (m_token.tk != tkID && m_token.tk != tkTHIS && m_token.tk != tkSUPER) |
| { |
| bool nestedDestructuring = m_token.tk == tkLCurly || m_token.tk == tkLBrack; |
| if ((isObjectPattern && nestedDestructuring) || (!isObjectPattern && !nestedDestructuring)) |
| { |
| if (isDecl) |
| { |
| Error(ERRnoIdent); |
| } |
| else |
| { |
| Error(ERRInvalidAssignmentTarget); |
| } |
| } |
| } |
| } |
| |
| if (IsPossiblePatternStart()) |
| { |
| // For the possible pattern start we do not allow the parens before |
| if (parenCount != 0) |
| { |
| Error(ERRDestructIDRef); |
| } |
| |
| // Go recursively |
| pnodeElem = ParseDestructuredLiteral<buildAST>(declarationType, isDecl, false /*topLevel*/, seenRest ? DIC_ShouldNotParseInitializer : DIC_None); |
| if (!isDecl) |
| { |
| BOOL fCanAssign; |
| // Look for postfix operator |
| pnodeElem = ParsePostfixOperators<buildAST>(pnodeElem, TRUE, FALSE, FALSE, TRUE, &fCanAssign, &token); |
| } |
| } |
| else if (m_token.tk == tkSUPER || m_token.tk == tkID || m_token.tk == tkTHIS) |
| { |
| if (isDecl) |
| { |
| charcount_t ichMin = this->GetScanner()->IchMinTok(); |
| pnodeElem = ParseVariableDeclaration<buildAST>(declarationType, ichMin |
| ,/* fAllowIn */false, /* pfForInOk */nullptr, /* singleDefOnly */true, /* allowInit */!seenRest, false /*topLevelParse*/); |
| |
| } |
| else |
| { |
| BOOL fCanAssign; |
| // We aren't declaring anything, so scan the ID reference manually. |
| pnodeElem = ParseTerm<buildAST>(/* fAllowCall */ m_token.tk != tkSUPER, nullptr /*pNameHint*/, nullptr /*pHintLength*/, nullptr /*pShortNameOffset*/, &token, false, |
| FALSE, &fCanAssign); |
| |
| // In this destructuring case we can force error here as we cannot assign. |
| |
| if (!fCanAssign) |
| { |
| Error(ERRInvalidAssignmentTarget); |
| } |
| |
| if (buildAST) |
| { |
| TrackAssignment<buildAST>(pnodeElem, nullptr); |
| } |
| |
| if (buildAST) |
| { |
| if (IsStrictMode() && pnodeElem != nullptr && pnodeElem->nop == knopName) |
| { |
| CheckStrictModeEvalArgumentsUsage(pnodeElem->AsParseNodeName()->pid); |
| } |
| } |
| else |
| { |
| if (IsStrictMode() && token.tk == tkID) |
| { |
| CheckStrictModeEvalArgumentsUsage(token.pid); |
| } |
| } |
| } |
| } |
| else if (!((m_token.tk == tkComma || m_token.tk == tkRBrack || m_token.tk == tkRCurly) && allowEmptyExpression)) |
| { |
| if (m_token.IsOperator()) |
| { |
| Error(ERRDestructNoOper); |
| } |
| Error(ERRDestructIDRef); |
| } |
| |
| // Swallow RParens before a default expression, if any. |
| // We eat the left parentheses only when its not a declaration. This will make sure we throw syntax errors early. We need to do the same for right parentheses. |
| if (!isDecl) |
| { |
| while (m_token.tk == tkRParen) |
| { |
| this->GetScanner()->Scan(); |
| --parenCount; |
| } |
| |
| // Restore the Block ID of the current block after the parsing of destructured variable declarations and initializers. |
| GetCurrentBlock()->blockId = originalCurrentBlockId; |
| } |
| |
| if (parenCount != 0) |
| { |
| Error(ERRnoRparen); |
| } |
| |
| if (hasSeenRest != nullptr) |
| { |
| *hasSeenRest = seenRest; |
| } |
| |
| if (m_token.tk == tkAsg) |
| { |
| // Parse the initializer. |
| if (seenRest) |
| { |
| Error(ERRRestWithDefault); |
| } |
| this->GetScanner()->Scan(); |
| |
| bool alreadyHasInitError = m_hasDeferredShorthandInitError; |
| ParseNodePtr pnodeInit = ParseExpr<buildAST>(koplCma); |
| |
| if (m_hasDeferredShorthandInitError && !alreadyHasInitError) |
| { |
| Error(ERRnoColon); |
| } |
| |
| if (buildAST) |
| { |
| pnodeElem = CreateBinNode(knopAsg, pnodeElem, pnodeInit); |
| } |
| } |
| |
| if (buildAST && seenRest) |
| { |
| ParseNodePtr pnodeRest = CreateUniNode(knopEllipsis, pnodeElem); |
| pnodeElem = pnodeRest; |
| } |
| |
| if (!(m_token.tk == tkComma || m_token.tk == tkRBrack || m_token.tk == tkRCurly)) |
| { |
| if (m_token.IsOperator()) |
| { |
| Error(ERRDestructNoOper); |
| } |
| Error(ERRsyntax); |
| } |
| |
| if (!buildAST && token.tk == tkID) |
| { |
| TrackAssignment<buildAST>(nullptr, &token); |
| } |
| |
| return pnodeElem; |
| } |
| |
| template <bool buildAST> |
| ParseNodeUni * Parser::ParseDestructuredArrayLiteral(tokens declarationType, bool isDecl, bool topLevel) |
| { |
| Assert(m_token.tk == tkLBrack); |
| charcount_t ichMin = this->GetScanner()->IchMinTok(); |
| |
| this->GetScanner()->Scan(); |
| |
| ParseNodeArrLit * pnodeDestructArr = nullptr; |
| ParseNodePtr pnodeList = nullptr; |
| ParseNodePtr *lastNodeRef = nullptr; |
| uint count = 0; |
| bool hasMissingValues = false; |
| bool seenRest = false; |
| |
| if (m_token.tk != tkRBrack) |
| { |
| while (true) |
| { |
| ParseNodePtr pnodeElem = ParseDestructuredVarDecl<buildAST>(declarationType, isDecl, &seenRest, topLevel); |
| if (buildAST) |
| { |
| if (pnodeElem == nullptr && buildAST) |
| { |
| pnodeElem = CreateNodeForOpT<knopEmpty>(); |
| hasMissingValues = true; |
| } |
| AddToNodeListEscapedUse(&pnodeList, &lastNodeRef, pnodeElem); |
| } |
| count++; |
| |
| if (m_token.tk == tkRBrack) |
| { |
| break; |
| } |
| |
| if (m_token.tk != tkComma) |
| { |
| Error(ERRDestructNoOper); |
| } |
| |
| if (seenRest) // Rest must be in the last position. |
| { |
| Error(ERRDestructRestLast); |
| } |
| |
| this->GetScanner()->Scan(); |
| |
| // break if we have the trailing comma as well, eg. [a,] |
| if (m_token.tk == tkRBrack) |
| { |
| break; |
| } |
| } |
| } |
| |
| if (buildAST) |
| { |
| pnodeDestructArr = CreateNodeForOpT<knopArrayPattern>(ichMin); |
| pnodeDestructArr->pnode1 = pnodeList; |
| pnodeDestructArr->arrayOfTaggedInts = false; |
| pnodeDestructArr->arrayOfInts = false; |
| pnodeDestructArr->arrayOfNumbers = false; |
| pnodeDestructArr->hasMissingValues = hasMissingValues; |
| pnodeDestructArr->count = count; |
| pnodeDestructArr->spreadCount = seenRest ? 1 : 0; |
| |
| if (pnodeDestructArr->pnode1) |
| { |
| this->CheckArguments(pnodeDestructArr->pnode1); |
| } |
| } |
| |
| return pnodeDestructArr; |
| } |
| |
| void Parser::CaptureContext(ParseContext *parseContext) const |
| { |
| parseContext->pszSrc = this->GetScanner()->PchBase(); |
| parseContext->length = this->m_originalLength; |
| parseContext->characterOffset = this->GetScanner()->IchMinTok(); |
| parseContext->offset = parseContext->characterOffset + this->GetScanner()->m_cMultiUnits; |
| parseContext->grfscr = this->m_grfscr; |
| parseContext->lineNumber = this->GetScanner()->LineCur(); |
| |
| parseContext->pnodeProg = this->m_currentNodeProg; |
| parseContext->isUtf8 = this->GetScanner()->IsUtf8(); |
| parseContext->strictMode = this->IsStrictMode(); |
| parseContext->sourceContextInfo = this->m_sourceContextInfo; |
| parseContext->currentBlockInfo = this->m_currentBlockInfo; |
| parseContext->nextBlockId = this->m_nextBlockId; |
| } |
| |
| void Parser::RestoreContext(ParseContext *const parseContext) |
| { |
| m_sourceContextInfo = parseContext->sourceContextInfo; |
| m_currentBlockInfo = parseContext->currentBlockInfo; |
| m_nextBlockId = parseContext->nextBlockId; |
| m_grfscr = parseContext->grfscr; |
| m_length = parseContext->length; |
| this->GetScanner()->SetText(parseContext->pszSrc, parseContext->offset, parseContext->length, parseContext->characterOffset, parseContext->isUtf8, parseContext->grfscr, parseContext->lineNumber); |
| m_currentNodeProg = parseContext->pnodeProg; |
| m_fUseStrictMode = parseContext->strictMode; |
| } |
| |
| class ByteCodeGenerator; |
| #if DBG_DUMP |
| |
| #define INDENT_SIZE 2 |
| |
| void PrintPnodeListWIndent(ParseNode *pnode, int indentAmt); |
| void PrintFormalsWIndent(ParseNode *pnode, int indentAmt); |
| |
| |
| void Indent(int indentAmt) { |
| for (int i = 0; i < indentAmt; i++) { |
| Output::Print(_u(" ")); |
| } |
| } |
| |
| void PrintBlockType(PnodeBlockType type) |
| { |
| switch (type) |
| { |
| case Global: |
| Output::Print(_u("(Global)")); |
| break; |
| case Function: |
| Output::Print(_u("(Function)")); |
| break; |
| case Regular: |
| Output::Print(_u("(Regular)")); |
| break; |
| case Parameter: |
| Output::Print(_u("(Parameter)")); |
| break; |
| default: |
| Output::Print(_u("(unknown blocktype)")); |
| break; |
| } |
| } |
| |
| void PrintScopesWIndent(ParseNode *pnode, int indentAmt) { |
| ParseNode *scope = nullptr; |
| bool firstOnly = false; |
| switch (pnode->nop) |
| { |
| case knopProg: |
| case knopFncDecl: scope = pnode->AsParseNodeFnc()->pnodeScopes; break; |
| case knopBlock: scope = pnode->AsParseNodeBlock()->pnodeScopes; break; |
| case knopCatch: scope = pnode->AsParseNodeCatch()->pnodeScopes; break; |
| case knopWith: scope = pnode->AsParseNodeWith()->pnodeScopes; break; |
| case knopSwitch: scope = pnode->AsParseNodeSwitch()->pnodeBlock; firstOnly = true; break; |
| case knopFor: scope = pnode->AsParseNodeFor()->pnodeBlock; firstOnly = true; break; |
| case knopForIn: scope = pnode->AsParseNodeForInOrForOf()->pnodeBlock; firstOnly = true; break; |
| case knopForOf: scope = pnode->AsParseNodeForInOrForOf()->pnodeBlock; firstOnly = true; break; |
| } |
| if (scope) { |
| Output::Print(_u("[%4d, %4d): "), scope->ichMin, scope->ichLim); |
| Indent(indentAmt); |
| Output::Print(_u("Scopes: ")); |
| ParseNode *next = nullptr; |
| ParseNode *syntheticBlock = nullptr; |
| while (scope) { |
| switch (scope->nop) { |
| case knopFncDecl: Output::Print(_u("knopFncDecl")); next = scope->AsParseNodeFnc()->pnodeNext; break; |
| case knopBlock: Output::Print(_u("knopBlock")); PrintBlockType(scope->AsParseNodeBlock()->blockType); next = scope->AsParseNodeBlock()->pnodeNext; break; |
| case knopCatch: Output::Print(_u("knopCatch")); next = scope->AsParseNodeCatch()->pnodeNext; break; |
| case knopWith: Output::Print(_u("knopWith")); next = scope->AsParseNodeWith()->pnodeNext; break; |
| default: Output::Print(_u("unknown")); break; |
| } |
| if (firstOnly) { |
| next = nullptr; |
| syntheticBlock = scope; |
| } |
| if (scope->grfpn & fpnSyntheticNode) { |
| Output::Print(_u(" synthetic")); |
| if (scope->nop == knopBlock) |
| syntheticBlock = scope; |
| } |
| Output::Print(_u(" (%d-%d)"), scope->ichMin, scope->ichLim); |
| if (next) Output::Print(_u(", ")); |
| scope = next; |
| } |
| Output::Print(_u("\n")); |
| if (syntheticBlock || firstOnly) { |
| PrintScopesWIndent(syntheticBlock, indentAmt + INDENT_SIZE); |
| } |
| } |
| } |
| |
| void PrintPnodeWIndent(ParseNode *pnode, int indentAmt) { |
| if (pnode == NULL) |
| return; |
| |
| Output::Print(_u("[%4d, %4d): "), pnode->ichMin, pnode->ichLim); |
| switch (pnode->nop) { |
| //PTNODE(knopName , "name" ,None ,Pid ,fnopLeaf) |
| case knopName: |
| Indent(indentAmt); |
| if (pnode->AsParseNodeName()->pid != NULL) { |
| Output::Print(_u("id: %s\n"), pnode->AsParseNodeName()->pid->Psz()); |
| } |
| else { |
| Output::Print(_u("name node\n")); |
| } |
| break; |
| //PTNODE(knopInt , "int const" ,None ,Int ,fnopLeaf|fnopConst) |
| case knopInt: |
| Indent(indentAmt); |
| Output::Print(_u("%d\n"), pnode->AsParseNodeInt()->lw); |
| break; |
| //PTNODE(knopInt , "int const" ,None ,Int ,fnopLeaf|fnopConst) |
| case knopBigInt: |
| Indent(indentAmt); |
| Output::Print(_u("%s%s\n"), pnode->AsParseNodeBigInt()->isNegative? "-" : "", pnode->AsParseNodeBigInt()->pid->Psz()); |
| break; |
| //PTNODE(knopFlt , "flt const" ,None ,Flt ,fnopLeaf|fnopConst) |
| case knopFlt: |
| Indent(indentAmt); |
| Output::Print(_u("%lf\n"), pnode->AsParseNodeFloat()->dbl); |
| break; |
| //PTNODE(knopStr , "str const" ,None ,Pid ,fnopLeaf|fnopConst) |
| case knopStr: |
| Indent(indentAmt); |
| Output::Print(_u("\"%s\"\n"), pnode->AsParseNodeStr()->pid->Psz()); |
| break; |
| //PTNODE(knopRegExp , "reg expr" ,None ,Pid ,fnopLeaf|fnopConst) |
| case knopRegExp: |
| Indent(indentAmt); |
| Output::Print(_u("/%x/\n"), pnode->AsParseNodeRegExp()->regexPattern); |
| break; |
| //PTNODE(knopNull , "null" ,Null ,None ,fnopLeaf) |
| case knopNull: |
| Indent(indentAmt); |
| Output::Print(_u("null\n")); |
| break; |
| //PTNODE(knopFalse , "false" ,False ,None ,fnopLeaf) |
| case knopFalse: |
| Indent(indentAmt); |
| Output::Print(_u("false\n")); |
| break; |
| //PTNODE(knopTrue , "true" ,True ,None ,fnopLeaf) |
| case knopTrue: |
| Indent(indentAmt); |
| Output::Print(_u("true\n")); |
| break; |
| //PTNODE(knopEmpty , "empty" ,Empty ,None ,fnopLeaf) |
| case knopEmpty: |
| Indent(indentAmt); |
| Output::Print(_u("empty\n")); |
| break; |
| // Unary operators. |
| //PTNODE(knopNot , "~" ,BitNot ,Uni ,fnopUni) |
| case knopNot: |
| Indent(indentAmt); |
| Output::Print(_u("~\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopNeg , "unary -" ,Neg ,Uni ,fnopUni) |
| case knopNeg: |
| Indent(indentAmt); |
| Output::Print(_u("U-\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopPos , "unary +" ,Pos ,Uni ,fnopUni) |
| case knopPos: |
| Indent(indentAmt); |
| Output::Print(_u("U+\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopLogNot , "!" ,LogNot ,Uni ,fnopUni) |
| case knopLogNot: |
| Indent(indentAmt); |
| Output::Print(_u("!\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopEllipsis , "..." ,Spread ,Uni , fnopUni) |
| case knopEllipsis: |
| Indent(indentAmt); |
| Output::Print(_u("...<expr>\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopIncPost , "++ post" ,Inc ,Uni ,fnopUni|fnopAsg) |
| case knopIncPost: |
| Indent(indentAmt); |
| Output::Print(_u("<expr>++\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopDecPost , "-- post" ,Dec ,Uni ,fnopUni|fnopAsg) |
| case knopDecPost: |
| Indent(indentAmt); |
| Output::Print(_u("<expr>--\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopIncPre , "++ pre" ,Inc ,Uni ,fnopUni|fnopAsg) |
| case knopIncPre: |
| Indent(indentAmt); |
| Output::Print(_u("++<expr>\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopDecPre , "-- pre" ,Dec ,Uni ,fnopUni|fnopAsg) |
| case knopDecPre: |
| Indent(indentAmt); |
| Output::Print(_u("--<expr>\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopTypeof , "typeof" ,None ,Uni ,fnopUni) |
| case knopTypeof: |
| Indent(indentAmt); |
| Output::Print(_u("typeof\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopVoid , "void" ,Void ,Uni ,fnopUni) |
| case knopVoid: |
| Indent(indentAmt); |
| Output::Print(_u("void\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopDelete , "delete" ,None ,Uni ,fnopUni) |
| case knopDelete: |
| Indent(indentAmt); |
| Output::Print(_u("delete\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopArray , "arr cnst" ,None ,Uni ,fnopUni) |
| |
| case knopArrayPattern: |
| Indent(indentAmt); |
| Output::Print(_u("Array Pattern\n")); |
| PrintPnodeListWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); |
| break; |
| |
| case knopObjectPattern: |
| Indent(indentAmt); |
| Output::Print(_u("Object Pattern\n")); |
| PrintPnodeListWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); |
| break; |
| |
| case knopArray: |
| Indent(indentAmt); |
| Output::Print(_u("Array Literal\n")); |
| PrintPnodeListWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopObject , "obj cnst" ,None ,Uni ,fnopUni) |
| case knopObject: |
| Indent(indentAmt); |
| Output::Print(_u("Object Literal\n")); |
| PrintPnodeListWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); |
| break; |
| // Binary and Ternary Operators |
| //PTNODE(knopAdd , "+" ,Add ,Bin ,fnopBin) |
| case knopAdd: |
| Indent(indentAmt); |
| Output::Print(_u("+\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopSub , "-" ,Sub ,Bin ,fnopBin) |
| case knopSub: |
| Indent(indentAmt); |
| Output::Print(_u("-\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopMul , "*" ,Mul ,Bin ,fnopBin) |
| case knopMul: |
| Indent(indentAmt); |
| Output::Print(_u("*\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopDiv , "/" ,Div ,Bin ,fnopBin) |
| case knopExpo: |
| Indent(indentAmt); |
| Output::Print(_u("**\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopExpo , "**" ,Expo ,Bin ,fnopBin) |
| |
| case knopDiv: |
| Indent(indentAmt); |
| Output::Print(_u("/\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopMod , "%" ,Mod ,Bin ,fnopBin) |
| case knopMod: |
| Indent(indentAmt); |
| Output::Print(_u("%%\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopOr , "|" ,BitOr ,Bin ,fnopBin) |
| case knopOr: |
| Indent(indentAmt); |
| Output::Print(_u("|\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopXor , "^" ,BitXor ,Bin ,fnopBin) |
| case knopXor: |
| Indent(indentAmt); |
| Output::Print(_u("^\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopAnd , "&" ,BitAnd ,Bin ,fnopBin) |
| case knopAnd: |
| Indent(indentAmt); |
| Output::Print(_u("&\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopEq , "==" ,EQ ,Bin ,fnopBin|fnopRel) |
| case knopEq: |
| Indent(indentAmt); |
| Output::Print(_u("==\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopNe , "!=" ,NE ,Bin ,fnopBin|fnopRel) |
| case knopNe: |
| Indent(indentAmt); |
| Output::Print(_u("!=\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopLt , "<" ,LT ,Bin ,fnopBin|fnopRel) |
| case knopLt: |
| Indent(indentAmt); |
| Output::Print(_u("<\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopLe , "<=" ,LE ,Bin ,fnopBin|fnopRel) |
| case knopLe: |
| Indent(indentAmt); |
| Output::Print(_u("<=\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopGe , ">=" ,GE ,Bin ,fnopBin|fnopRel) |
| case knopGe: |
| Indent(indentAmt); |
| Output::Print(_u(">=\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopGt , ">" ,GT ,Bin ,fnopBin|fnopRel) |
| case knopGt: |
| Indent(indentAmt); |
| Output::Print(_u(">\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopCall , "()" ,None ,Bin ,fnopBin) |
| case knopCall: |
| Indent(indentAmt); |
| Output::Print(_u("Call\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeCall()->pnodeTarget, indentAmt + INDENT_SIZE); |
| PrintPnodeListWIndent(pnode->AsParseNodeCall()->pnodeArgs, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopDot , "." ,None ,Bin ,fnopBin) |
| case knopDot: |
| Indent(indentAmt); |
| Output::Print(_u(".\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopAsg , "=" ,None ,Bin ,fnopBin|fnopAsg) |
| case knopAsg: |
| Indent(indentAmt); |
| Output::Print(_u("=\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopInstOf , "instanceof",InstOf ,Bin ,fnopBin|fnopRel) |
| case knopInstOf: |
| Indent(indentAmt); |
| Output::Print(_u("instanceof\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopIn , "in" ,In ,Bin ,fnopBin|fnopRel) |
| case knopIn: |
| Indent(indentAmt); |
| Output::Print(_u("in\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopEqv , "===" ,Eqv ,Bin ,fnopBin|fnopRel) |
| case knopEqv: |
| Indent(indentAmt); |
| Output::Print(_u("===\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopNEqv , "!==" ,NEqv ,Bin ,fnopBin|fnopRel) |
| case knopNEqv: |
| Indent(indentAmt); |
| Output::Print(_u("!==\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopComma , "," ,None ,Bin ,fnopBin) |
| case knopComma: |
| Indent(indentAmt); |
| Output::Print(_u(",\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopLogOr , "||" ,None ,Bin ,fnopBin) |
| case knopLogOr: |
| Indent(indentAmt); |
| Output::Print(_u("||\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopLogAnd , "&&" ,None ,Bin ,fnopBin) |
| case knopLogAnd: |
| Indent(indentAmt); |
| Output::Print(_u("&&\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopLsh , "<<" ,Lsh ,Bin ,fnopBin) |
| case knopLsh: |
| Indent(indentAmt); |
| Output::Print(_u("<<\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopRsh , ">>" ,Rsh ,Bin ,fnopBin) |
| case knopRsh: |
| Indent(indentAmt); |
| Output::Print(_u(">>\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopRs2 , ">>>" ,Rs2 ,Bin ,fnopBin) |
| case knopRs2: |
| Indent(indentAmt); |
| Output::Print(_u(">>>\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopNew , "new" ,None ,Bin ,fnopBin) |
| case knopNew: |
| Indent(indentAmt); |
| Output::Print(_u("new\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeCall()->pnodeTarget, indentAmt + INDENT_SIZE); |
| PrintPnodeListWIndent(pnode->AsParseNodeCall()->pnodeArgs, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopIndex , "[]" ,None ,Bin ,fnopBin) |
| case knopIndex: |
| Indent(indentAmt); |
| Output::Print(_u("[]\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeListWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopQmark , "?" ,None ,Tri ,fnopBin) |
| case knopQmark: |
| Indent(indentAmt); |
| Output::Print(_u("?:\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeTri()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeTri()->pnode2, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeTri()->pnode3, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopAsgAdd , "+=" ,Add ,Bin ,fnopBin|fnopAsg) |
| case knopAsgAdd: |
| Indent(indentAmt); |
| Output::Print(_u("+=\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopAsgSub , "-=" ,Sub ,Bin ,fnopBin|fnopAsg) |
| case knopAsgSub: |
| Indent(indentAmt); |
| Output::Print(_u("-=\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopAsgMul , "*=" ,Mul ,Bin ,fnopBin|fnopAsg) |
| case knopAsgMul: |
| Indent(indentAmt); |
| Output::Print(_u("*=\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopAsgDiv , "/=" ,Div ,Bin ,fnopBin|fnopAsg) |
| case knopAsgExpo: |
| Indent(indentAmt); |
| Output::Print(_u("**=\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopAsgExpo , "**=" ,Expo ,Bin ,fnopBin|fnopAsg) |
| |
| case knopAsgDiv: |
| Indent(indentAmt); |
| Output::Print(_u("/=\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopAsgMod , "%=" ,Mod ,Bin ,fnopBin|fnopAsg) |
| case knopAsgMod: |
| Indent(indentAmt); |
| Output::Print(_u("%=\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopAsgAnd , "&=" ,BitAnd ,Bin ,fnopBin|fnopAsg) |
| case knopAsgAnd: |
| Indent(indentAmt); |
| Output::Print(_u("&=\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopAsgXor , "^=" ,BitXor ,Bin ,fnopBin|fnopAsg) |
| case knopAsgXor: |
| Indent(indentAmt); |
| Output::Print(_u("^=\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopAsgOr , "|=" ,BitOr ,Bin ,fnopBin|fnopAsg) |
| case knopAsgOr: |
| Indent(indentAmt); |
| Output::Print(_u("|=\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopAsgLsh , "<<=" ,Lsh ,Bin ,fnopBin|fnopAsg) |
| case knopAsgLsh: |
| Indent(indentAmt); |
| Output::Print(_u("<<=\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopAsgRsh , ">>=" ,Rsh ,Bin ,fnopBin|fnopAsg) |
| case knopAsgRsh: |
| Indent(indentAmt); |
| Output::Print(_u(">>=\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopAsgRs2 , ">>>=" ,Rs2 ,Bin ,fnopBin|fnopAsg) |
| case knopAsgRs2: |
| Indent(indentAmt); |
| Output::Print(_u(">>>=\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| |
| case knopComputedName: |
| Indent(indentAmt); |
| Output::Print(_u("ComputedProperty\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); |
| break; |
| case knopParamPattern: |
| PrintPnodeWIndent(pnode->AsParseNodeParamPattern()->pnode1, indentAmt); |
| break; |
| //PTNODE(knopMember , ":" ,None ,Bin ,fnopBin) |
| case knopMember: |
| case knopMemberShort: |
| case knopObjectPatternMember: |
| Indent(indentAmt); |
| Output::Print(_u(":\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); |
| break; |
| // General nodes. |
| //PTNODE(knopList , "<list>" ,None ,Bin ,fnopNone) |
| case knopList: |
| Indent(indentAmt); |
| Output::Print(_u("List\n")); |
| PrintPnodeListWIndent(pnode, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopVarDecl , "varDcl" ,None ,Var ,fnopNone) |
| case knopVarDecl: |
| Indent(indentAmt); |
| Output::Print(_u("var %s\n"), pnode->AsParseNodeVar()->pid->Psz()); |
| if (pnode->AsParseNodeVar()->pnodeInit != NULL) |
| PrintPnodeWIndent(pnode->AsParseNodeVar()->pnodeInit, indentAmt + INDENT_SIZE); |
| break; |
| case knopConstDecl: |
| Indent(indentAmt); |
| Output::Print(_u("const %s\n"), pnode->AsParseNodeVar()->pid->Psz()); |
| if (pnode->AsParseNodeVar()->pnodeInit != NULL) |
| PrintPnodeWIndent(pnode->AsParseNodeVar()->pnodeInit, indentAmt + INDENT_SIZE); |
| break; |
| case knopLetDecl: |
| Indent(indentAmt); |
| Output::Print(_u("let %s\n"), pnode->AsParseNodeVar()->pid->Psz()); |
| if (pnode->AsParseNodeVar()->pnodeInit != NULL) |
| PrintPnodeWIndent(pnode->AsParseNodeVar()->pnodeInit, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopFncDecl , "fncDcl" ,None ,Fnc ,fnopLeaf) |
| case knopFncDecl: |
| Indent(indentAmt); |
| if (pnode->AsParseNodeFnc()->pid != NULL) |
| { |
| Output::Print(_u("fn decl %d nested %d name %s (%d-%d)\n"), pnode->AsParseNodeFnc()->IsDeclaration(), pnode->AsParseNodeFnc()->IsNested(), |
| pnode->AsParseNodeFnc()->pid->Psz(), pnode->ichMin, pnode->ichLim); |
| } |
| else |
| { |
| Output::Print(_u("fn decl %d nested %d anonymous (%d-%d)\n"), pnode->AsParseNodeFnc()->IsDeclaration(), pnode->AsParseNodeFnc()->IsNested(), pnode->ichMin, pnode->ichLim); |
| } |
| PrintScopesWIndent(pnode, indentAmt + INDENT_SIZE); |
| PrintFormalsWIndent(pnode->AsParseNodeFnc()->pnodeParams, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeFnc()->pnodeRest, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeFnc()->pnodeBody, indentAmt + INDENT_SIZE); |
| if (pnode->AsParseNodeFnc()->pnodeBody == nullptr) |
| { |
| Output::Print(_u("[%4d, %4d): "), pnode->ichMin, pnode->ichLim); |
| Indent(indentAmt + INDENT_SIZE); |
| Output::Print(_u("<parse deferred body>\n")); |
| } |
| break; |
| //PTNODE(knopProg , "program" ,None ,Fnc ,fnopNone) |
| case knopProg: |
| Indent(indentAmt); |
| Output::Print(_u("program\n")); |
| PrintScopesWIndent(pnode, indentAmt + INDENT_SIZE); |
| PrintPnodeListWIndent(pnode->AsParseNodeFnc()->pnodeBody, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopEndCode , "<endcode>" ,None ,None ,fnopNone) |
| case knopEndCode: |
| Indent(indentAmt); |
| Output::Print(_u("<endcode>\n")); |
| break; |
| //PTNODE(knopDebugger , "debugger" ,None ,None ,fnopNone) |
| case knopDebugger: |
| Indent(indentAmt); |
| Output::Print(_u("<debugger>\n")); |
| break; |
| //PTNODE(knopFor , "for" ,None ,For ,fnopBreak|fnopContinue) |
| case knopFor: |
| Indent(indentAmt); |
| Output::Print(_u("for\n")); |
| PrintScopesWIndent(pnode, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeFor()->pnodeInit, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeFor()->pnodeCond, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeFor()->pnodeIncr, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeFor()->pnodeBody, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopIf , "if" ,None ,If ,fnopNone) |
| case knopIf: |
| Indent(indentAmt); |
| Output::Print(_u("if\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeIf()->pnodeCond, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeIf()->pnodeTrue, indentAmt + INDENT_SIZE); |
| if (pnode->AsParseNodeIf()->pnodeFalse != NULL) |
| PrintPnodeWIndent(pnode->AsParseNodeIf()->pnodeFalse, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopWhile , "while" ,None ,While,fnopBreak|fnopContinue) |
| case knopWhile: |
| Indent(indentAmt); |
| Output::Print(_u("while\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeWhile()->pnodeCond, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeWhile()->pnodeBody, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopDoWhile , "do-while" ,None ,While,fnopBreak|fnopContinue) |
| case knopDoWhile: |
| Indent(indentAmt); |
| Output::Print(_u("do\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeWhile()->pnodeCond, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeWhile()->pnodeBody, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopForIn , "for in" ,None ,ForIn,fnopBreak|fnopContinue|fnopCleanup) |
| case knopForIn: |
| Indent(indentAmt); |
| Output::Print(_u("forIn\n")); |
| PrintScopesWIndent(pnode, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeForInOrForOf()->pnodeLval, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeForInOrForOf()->pnodeObj, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeForInOrForOf()->pnodeBody, indentAmt + INDENT_SIZE); |
| break; |
| case knopForOf: |
| Indent(indentAmt); |
| Output::Print(_u("forOf\n")); |
| PrintScopesWIndent(pnode, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeForInOrForOf()->pnodeLval, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeForInOrForOf()->pnodeObj, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeForInOrForOf()->pnodeBody, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopReturn , "return" ,None ,Uni ,fnopNone) |
| case knopReturn: |
| Indent(indentAmt); |
| Output::Print(_u("return\n")); |
| if (pnode->AsParseNodeReturn()->pnodeExpr != NULL) |
| PrintPnodeWIndent(pnode->AsParseNodeReturn()->pnodeExpr, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopBlock , "{}" ,None ,Block,fnopNone) |
| case knopBlock: |
| Indent(indentAmt); |
| Output::Print(_u("block ")); |
| if (pnode->grfpn & fpnSyntheticNode) |
| Output::Print(_u("synthetic ")); |
| PrintBlockType(pnode->AsParseNodeBlock()->blockType); |
| Output::Print(_u("(%d-%d)\n"), pnode->ichMin, pnode->ichLim); |
| PrintScopesWIndent(pnode, indentAmt + INDENT_SIZE); |
| if (pnode->AsParseNodeBlock()->pnodeStmt != NULL) |
| PrintPnodeWIndent(pnode->AsParseNodeBlock()->pnodeStmt, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopWith , "with" ,None ,With ,fnopCleanup) |
| case knopWith: |
| Indent(indentAmt); |
| Output::Print(_u("with (%d-%d)\n"), pnode->ichMin, pnode->ichLim); |
| PrintScopesWIndent(pnode, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeWith()->pnodeObj, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeWith()->pnodeBody, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopBreak , "break" ,None ,Jump ,fnopNone) |
| case knopBreak: |
| Indent(indentAmt); |
| Output::Print(_u("break\n")); |
| // TODO: some representation of target |
| break; |
| //PTNODE(knopContinue , "continue" ,None ,Jump ,fnopNone) |
| case knopContinue: |
| Indent(indentAmt); |
| Output::Print(_u("continue\n")); |
| // TODO: some representation of target |
| break; |
| //PTNODE(knopSwitch , "switch" ,None ,Switch,fnopBreak) |
| case knopSwitch: |
| Indent(indentAmt); |
| Output::Print(_u("switch\n")); |
| PrintScopesWIndent(pnode, indentAmt + INDENT_SIZE); |
| for (ParseNodeCase *pnodeT = pnode->AsParseNodeSwitch()->pnodeCases; NULL != pnodeT; pnodeT = pnodeT->pnodeNext) { |
| PrintPnodeWIndent(pnodeT, indentAmt + 2); |
| } |
| break; |
| //PTNODE(knopCase , "case" ,None ,Case ,fnopNone) |
| case knopCase: |
| Indent(indentAmt); |
| Output::Print(_u("case\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeCase()->pnodeExpr, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeCase()->pnodeBody, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopTryFinally,"try-finally",None,TryFinally,fnopCleanup) |
| case knopTryFinally: |
| PrintPnodeWIndent(pnode->AsParseNodeTryFinally()->pnodeTry, indentAmt); |
| PrintPnodeWIndent(pnode->AsParseNodeTryFinally()->pnodeFinally, indentAmt); |
| break; |
| case knopFinally: |
| Indent(indentAmt); |
| Output::Print(_u("finally\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeFinally()->pnodeBody, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopCatch , "catch" ,None ,Catch,fnopNone) |
| case knopCatch: |
| Indent(indentAmt); |
| Output::Print(_u("catch (%d-%d)\n"), pnode->ichMin, pnode->ichLim); |
| PrintScopesWIndent(pnode, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeCatch()->GetParam(), indentAmt + INDENT_SIZE); |
| // if (pnode->AsParseNodeCatch()->pnodeGuard!=NULL) |
| // PrintPnodeWIndent(pnode->AsParseNodeCatch()->pnodeGuard,indentAmt+INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeCatch()->pnodeBody, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopTryCatch , "try-catch" ,None ,TryCatch ,fnopCleanup) |
| case knopTryCatch: |
| PrintPnodeWIndent(pnode->AsParseNodeTryCatch()->pnodeTry, indentAmt); |
| PrintPnodeWIndent(pnode->AsParseNodeTryCatch()->pnodeCatch, indentAmt); |
| break; |
| //PTNODE(knopTry , "try" ,None ,Try ,fnopCleanup) |
| case knopTry: |
| Indent(indentAmt); |
| Output::Print(_u("try\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeTry()->pnodeBody, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopThrow , "throw" ,None ,Uni ,fnopNone) |
| case knopThrow: |
| Indent(indentAmt); |
| Output::Print(_u("throw\n")); |
| PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); |
| break; |
| //PTNODE(knopClassDecl, "classDecl", None , Class, fnopLeaf) |
| case knopClassDecl: |
| Indent(indentAmt); |
| Output::Print(_u("class %s"), pnode->AsParseNodeClass()->pnodeName->pid->Psz()); |
| if (pnode->AsParseNodeClass()->pnodeExtends != nullptr) |
| { |
| Output::Print(_u(" extends ")); |
| PrintPnodeWIndent(pnode->AsParseNodeClass()->pnodeExtends, 0); |
| } |
| else { |
| Output::Print(_u("\n")); |
| } |
| |
| PrintPnodeWIndent(pnode->AsParseNodeClass()->pnodeConstructor, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeClass()->pnodeMembers, indentAmt + INDENT_SIZE); |
| PrintPnodeWIndent(pnode->AsParseNodeClass()->pnodeStaticMembers, indentAmt + INDENT_SIZE); |
| break; |
| case knopStrTemplate: |
| Indent(indentAmt); |
| Output::Print(_u("string template\n")); |
| PrintPnodeListWIndent(pnode->AsParseNodeStrTemplate()->pnodeSubstitutionExpressions, indentAmt + INDENT_SIZE); |
| break; |
| case knopYieldStar: |
| Indent(indentAmt); |
| Output::Print(_u("yield*\n")); |
| PrintPnodeListWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); |
| break; |
| case knopYield: |
| case knopYieldLeaf: |
| Indent(indentAmt); |
| Output::Print(_u("yield\n")); |
| PrintPnodeListWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); |
| break; |
| case knopAwait: |
| Indent(indentAmt); |
| Output::Print(_u("await\n")); |
| PrintPnodeListWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); |
| break; |
| case knopExportDefault: |
| Indent(indentAmt); |
| Output::Print(_u("export default\n")); |
| PrintPnodeListWIndent(pnode->AsParseNodeExportDefault()->pnodeExpr, indentAmt + INDENT_SIZE); |
| break; |
| default: |
| Output::Print(_u("unhandled pnode op %d\n"), pnode->nop); |
| break; |
| } |
| } |
| |
| void PrintPnodeListWIndent(ParseNode *pnode, int indentAmt) { |
| if (pnode != NULL) { |
| while (pnode->nop == knopList) { |
| PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt); |
| pnode = pnode->AsParseNodeBin()->pnode2; |
| } |
| PrintPnodeWIndent(pnode, indentAmt); |
| } |
| } |
| |
| void PrintFormalsWIndent(ParseNode *pnodeArgs, int indentAmt) |
| { |
| for (ParseNode *pnode = pnodeArgs; pnode != nullptr; pnode = pnode->GetFormalNext()) |
| { |
| PrintPnodeWIndent(pnode, indentAmt); |
| } |
| } |
| |
| void PrintPnode(ParseNode *pnode) { |
| PrintPnodeWIndent(pnode, 0); |
| } |
| |
| void ParseNode::Dump() |
| { |
| switch (nop) |
| { |
| case knopFncDecl: |
| case knopProg: |
| LPCOLESTR name = Js::Constants::AnonymousFunction; |
| if (this->AsParseNodeFnc()->pnodeName) |
| { |
| Assert(this->AsParseNodeFnc()->pnodeName->nop == knopVarDecl); |
| name = this->AsParseNodeFnc()->pnodeName->pid->Psz(); |
| } |
| |
| Output::Print(_u("%s (%d) [%d, %d]:\n"), name, this->AsParseNodeFnc()->functionId, this->AsParseNodeFnc()->lineNumber, this->AsParseNodeFnc()->columnNumber); |
| Output::Print(_u("hasArguments: %s callsEval:%s childCallsEval:%s HasReferenceableBuiltInArguments:%s ArgumentsObjectEscapes:%s HasWith:%s HasOnlyThis:%s \n"), |
| IsTrueOrFalse(this->AsParseNodeFnc()->HasHeapArguments()), |
| IsTrueOrFalse(this->AsParseNodeFnc()->CallsEval()), |
| IsTrueOrFalse(this->AsParseNodeFnc()->ChildCallsEval()), |
| IsTrueOrFalse(this->AsParseNodeFnc()->HasReferenceableBuiltInArguments()), |
| IsTrueOrFalse(this->AsParseNodeFnc()->GetArgumentsObjectEscapes()), |
| IsTrueOrFalse(this->AsParseNodeFnc()->HasWithStmt()), |
| IsTrueOrFalse(this->AsParseNodeFnc()->HasOnlyThisStmts())); |
| if (this->AsParseNodeFnc()->funcInfo) |
| { |
| this->AsParseNodeFnc()->funcInfo->Dump(); |
| } |
| break; |
| } |
| } |
| |
| void DumpCapturedNames(ParseNodeFnc* pnodeFnc, IdentPtrSet* capturedNames, ArenaAllocator* alloc) |
| { |
| auto sortedNames = JsUtil::List<IdentPtr, ArenaAllocator>::New(alloc); |
| |
| capturedNames->Map([=](const IdentPtr& pid) -> void { |
| sortedNames->Add(pid); |
| }); |
| |
| sortedNames->Sort([](void* context, const void* left, const void* right) -> int { |
| const IdentPtr leftIdentPtr = *(const IdentPtr*)(left); |
| const IdentPtr rightIdentPtr = *(const IdentPtr*)(right); |
| return ::wcscmp(leftIdentPtr->Psz(), rightIdentPtr->Psz()); |
| }, nullptr); |
| |
| sortedNames->Map([=](int index, const IdentPtr pid) -> void { |
| OUTPUT_TRACE_DEBUGONLY(Js::CreateParserStatePhase, _u(" Function %u captured name \"%s\"\n"), pnodeFnc->functionId, pid->Psz()); |
| }); |
| } |
| #endif |
| |
| void Parser::AddNestedCapturedNames(ParseNodeFnc* pnodeChildFnc) |
| { |
| if (m_currentNodeFunc && this->IsCreatingStateCache() && pnodeChildFnc->HasAnyCapturedNames()) |
| { |
| IdentPtrSet* parentCapturedNames = GetCurrentFunctionNode()->EnsureCapturedNames(&m_nodeAllocator); |
| IdentPtrSet* childCaptureNames = pnodeChildFnc->GetCapturedNames(); |
| |
| auto iter = childCaptureNames->GetIterator(); |
| |
| while (iter.IsValid()) |
| { |
| parentCapturedNames->AddNew(iter.CurrentValue()); |
| iter.MoveNext(); |
| } |
| } |
| } |
| |
| void Parser::ProcessCapturedNames(ParseNodeFnc* pnodeFnc) |
| { |
| if (this->IsCreatingStateCache() && pnodeFnc->HasAnyCapturedNames()) |
| { |
| IdentPtrSet* capturedNames = pnodeFnc->GetCapturedNames(); |
| auto iter = capturedNames->GetIteratorWithRemovalSupport(); |
| |
| while (iter.IsValid()) |
| { |
| const IdentPtr& pid = iter.CurrentValueReference(); |
| PidRefStack* ref = pid->GetTopRef(); |
| |
| // If the pid has no refs left in our function's scope after binding, we didn't capture it. |
| if (!ref || ref->GetFuncScopeId() < pnodeFnc->functionId) |
| { |
| iter.RemoveCurrent(); |
| } |
| |
| iter.MoveNext(); |
| } |
| |
| #if DBG_DUMP |
| if (Js::Configuration::Global.flags.Trace.IsEnabled(Js::CreateParserStatePhase)) |
| { |
| DumpCapturedNames(pnodeFnc, capturedNames, &this->m_nodeAllocator); |
| fflush(stdout); |
| } |
| #endif |
| } |
| } |
| |
| void Parser::ReleaseTemporaryGuestArena() |
| { |
| // In case of modules the Parser lives longer than the temporary Guest Arena. We may have already released the arena explicitly. |
| if (!m_tempGuestArenaReleased) |
| { |
| // The regex patterns list has references to the temporary Guest Arena. Reset it first. |
| m_registeredRegexPatterns.Reset(); |
| |
| if (this->m_scriptContext != nullptr) |
| { |
| this->m_scriptContext->ReleaseTemporaryGuestAllocator(m_tempGuestArena); |
| m_tempGuestArena.Unroot(); |
| } |
| |
| m_tempGuestArenaReleased = true; |
| } |
| } |
| |
| bool Parser::IsCreatingStateCache() |
| { |
| return (((this->m_grfscr & fscrCreateParserState) == fscrCreateParserState) |
| && this->m_functionBody == nullptr |
| && CONFIG_FLAG(ParserStateCache)); |
| } |
| |
| void Parser::ShiftCurrDeferredStubToChildFunction(ParseNodeFnc* pnodeFnc, ParseNodeFnc* pnodeFncParent) |
| { |
| // Goal here is to shift the current deferred stub to point to the stubs for pnodeFnc |
| // so we may continue parsing pnodeFnc using the correct set of stubs instead of the |
| // stubs for pnodeFncParent. |
| // This function assumes we are in the middle of parsing pnodeFnc which is a child |
| // nested in pnodeFncParent. |
| if (pnodeFnc->IsNested() && pnodeFncParent != nullptr && m_currDeferredStub != nullptr && pnodeFncParent->ichMin != pnodeFnc->ichMin) |
| { |
| AssertOrFailFast(pnodeFncParent->nestedCount > 0); |
| |
| DeferredFunctionStub* childStub = m_currDeferredStub + (pnodeFncParent->nestedCount - 1); |
| m_currDeferredStubCount = childStub->nestedCount; |
| m_currDeferredStub = childStub->deferredStubs; |
| } |
| } |
| |
| uint Parser::BuildDeferredStubTreeHelper(ParseNodeBlock* pnodeBlock, DeferredFunctionStub* deferredStubs, uint currentStubIndex, uint deferredStubCount, Recycler *recycler) |
| { |
| Assert(pnodeBlock != nullptr |
| && (pnodeBlock->blockType == PnodeBlockType::Function |
| || pnodeBlock->blockType == PnodeBlockType::Parameter)); |
| |
| ParseNodePtr pnodeChild = pnodeBlock->pnodeScopes; |
| |
| while (pnodeChild != nullptr) |
| { |
| if (pnodeChild->nop != knopFncDecl) |
| { |
| // We only expect to find a function body block in a parameter scope block. |
| Assert(pnodeChild->nop == knopBlock |
| && (pnodeBlock->blockType == PnodeBlockType::Parameter |
| || pnodeChild->AsParseNodeBlock()->blockType == PnodeBlockType::Function)); |
| pnodeChild = pnodeChild->AsParseNodeBlock()->pnodeNext; |
| continue; |
| } |
| |
| ParseNodeFnc* pnodeFncChild = pnodeChild->AsParseNodeFnc(); |
| AnalysisAssertOrFailFast(currentStubIndex < deferredStubCount); |
| Assert(pnodeFncChild->pnodeBody == nullptr); |
| |
| if (pnodeFncChild->IsGeneratedDefault()) |
| { |
| ++currentStubIndex; |
| pnodeChild = pnodeFncChild->pnodeNext; |
| continue; |
| } |
| |
| deferredStubs[currentStubIndex].fncFlags = pnodeFncChild->fncFlags; |
| deferredStubs[currentStubIndex].nestedCount = pnodeFncChild->nestedCount; |
| deferredStubs[currentStubIndex].restorePoint = *pnodeFncChild->pRestorePoint; |
| deferredStubs[currentStubIndex].deferredStubs = BuildDeferredStubTree(pnodeFncChild, recycler); |
| deferredStubs[currentStubIndex].ichMin = pnodeChild->ichMin; |
| |
| // Save the set of captured names onto the deferred stub. |
| // Since this set is allocated in the Parser arena, we'll have to convert these |
| // into indices in a string table which will survive when the parser goes away. |
| deferredStubs[currentStubIndex].capturedNamePointers = pnodeFncChild->GetCapturedNames(); |
| |
| ++currentStubIndex; |
| pnodeChild = pnodeFncChild->pnodeNext; |
| } |
| |
| return currentStubIndex; |
| } |
| |
| DeferredFunctionStub * Parser::BuildDeferredStubTree(ParseNodeFnc *pnodeFnc, Recycler *recycler) |
| { |
| Assert(CONFIG_FLAG(ParserStateCache)); |
| |
| uint nestedCount = pnodeFnc->nestedCount; |
| if (nestedCount == 0) |
| { |
| return nullptr; |
| } |
| |
| if (pnodeFnc->deferredStub) |
| { |
| return pnodeFnc->deferredStub; |
| } |
| |
| DeferredFunctionStub* deferredStubs = RecyclerNewArray(recycler, DeferredFunctionStub, nestedCount); |
| |
| uint currentStubIndex = BuildDeferredStubTreeHelper(pnodeFnc->pnodeScopes, deferredStubs, 0, nestedCount, recycler); |
| currentStubIndex = BuildDeferredStubTreeHelper(pnodeFnc->pnodeBodyScope, deferredStubs, currentStubIndex, nestedCount, recycler); |
| |
| Assert(currentStubIndex == nestedCount); |
| |
| pnodeFnc->deferredStub = deferredStubs; |
| return deferredStubs; |
| } |