UNPKG

@ibyar/expressions

Version:

Aurora expression, an template expression and evaluation, An 100% spec compliant ES2022 JavaScript toolchain,

1,199 lines (1,198 loc) 105 kB
import { EmptyStatement } from '../api/statement/control/empty.js'; import { AllowLabelledFunctionStatement, FunctionBodyType, FunctionKind, functionKindForImpl, FunctionSyntaxKind, isAsyncFunction, isAsyncGeneratorFunction, isAwaitAsIdentifierDisallowed, isClassMembersInitializerFunction, isGeneratorFunction, isModule, isResumableFunction, ParseFunctionFlag, ParsingArrowHeadFlag, PropertyKind, PropertyKindInfo, PropertyPosition, SubFunctionKind, VariableDeclarationContext } from './enums.js'; import { TokenStream } from './stream.js'; import { Token } from './token.js'; import { ExpressionNodeFactory, ExpressionNodeSourcePosition } from './factory.js'; import { isSloppy, isStrict, LanguageMode } from './language.js'; export class AbstractParser { scanner; factory; acceptIN; previousAcceptIN = []; functionKind; previousFunctionKind = []; get languageMode() { return this.scanner.getLanguageMode(); } set languageMode(mode) { this.scanner.setLanguageMode(mode); } constructor(scanner, factory, acceptIN) { this.scanner = scanner; this.factory = factory; this.previousAcceptIN.push(this.acceptIN = acceptIN ?? false); this.previousFunctionKind.push(this.functionKind = FunctionKind.NormalFunction); } position() { return this.scanner.getPos(); } setAcceptIN(acceptIN) { this.previousAcceptIN.push(this.acceptIN); this.acceptIN = acceptIN; } restoreAcceptIN() { this.acceptIN = this.previousAcceptIN.pop() ?? false; } getLastFunctionKind() { return this.previousFunctionKind.at(-2) ?? FunctionKind.NormalFunction; } setFunctionKind(functionKind) { this.previousFunctionKind.push(this.functionKind); this.functionKind = functionKind; } restoreFunctionKind() { this.functionKind = this.previousFunctionKind.pop() ?? FunctionKind.NormalFunction; } setStatue(acceptIN, functionKind) { this.setFunctionKind(functionKind); this.setAcceptIN(acceptIN); } restoreStatue() { this.restoreFunctionKind(); this.restoreAcceptIN(); } current() { return this.scanner.currentToken(); } next() { return this.scanner.next(); } peek() { return this.scanner.peek(); } peekAhead() { return this.scanner.peekAhead(); } peekAheadPosition() { return this.scanner.peekAheadPosition(); } peekPosition() { return this.scanner.peekPosition(); } createRange(start) { if (Number.isNaN(start?.range?.[0])) { return; } return [start.range[0], this.scanner.getPos()]; } createRangeByStart(start) { return [start.range[0], this.scanner.getPos()]; } createStartPosition() { return [this.scanner.getPos(), -1]; } updateRangeEnd(range) { range[1] = this.scanner.getPos(); } consume(token) { const next = this.scanner.next(); if (next.isNotType(token)) { throw new Error(this.errorMessage(`Parsing ${JSON.stringify(token)}`)); } return next; } check(token) { const next = this.scanner.peek(); if (next.isType(token)) { this.scanner.next(); return true; } return false; } checkAndGetValue(token) { const next = this.scanner.peek(); if (next.isType(token)) { this.scanner.next(); return next.value; } return undefined; } checkValue(value) { const next = this.scanner.peek(); if (next.value == value) { this.scanner.next(); return true; } return false; } expect(token) { const current = this.scanner.next(); if (current.isNotType(token)) { throw new Error(this.errorMessage(`Unexpected Token: ${JSON.stringify(token)}, current is ${JSON.stringify(current)}`)); } return current; } expectAndGetValue(token) { const current = this.scanner.next(); if (current.isNotType(token)) { throw new Error(this.errorMessage(`Unexpected Token: ${JSON.stringify(token)}, current is ${JSON.stringify(current)}`)); } return current.getValue(); } checkInOrOf() { const result = this.peekInOrOf(); if (result) { this.scanner.next(); } return result; } peekInOrOf() { var next = this.peek(); if (next.isType(Token.IN)) { return 'IN'; } else if (this.factory.isIdentifier(next.value) && next.value.getName() === 'of') { return 'OF'; } return false; } isArguments(node) { return this.factory.isIdentifier(node) && node.getName() === 'arguments'; } isEval(node) { return this.factory.isIdentifier(node) && node.getName() === 'eval'; } isEvalOrArguments(node) { return this.factory.isIdentifier(node) && (node.getName() === 'eval' || node.getName() === 'arguments'); } isNextLetKeyword() { if (this.peek().isNotType(Token.LET)) { return false; } const nextNextToken = this.peekAhead().token; switch (nextNextToken) { case Token.LBRACE: case Token.LBRACK: case Token.IDENTIFIER: case Token.STATIC: case Token.LET: // `let let;` is disallowed by static semantics, but the // token must be first interpreted as a keyword in order // for those semantics to apply. This ensures that ASI is // not honored when a LineTerminator separates the // tokens. case Token.YIELD: case Token.AWAIT: case Token.GET: case Token.SET: case Token.ASYNC: return true; default: return false; } } markParenthesized(expression) { Reflect.set(expression, 'parenthesized', true); } clearParenthesized(expression) { Reflect.deleteProperty(expression, 'parenthesized'); } isParenthesized(expression) { return Reflect.get(expression, 'parenthesized') === true; } isAssignableIdentifier(expression) { // return expression instanceof AssignmentNode; if (!(this.factory.isIdentifier(expression))) { return false; } if (isStrict(this.languageMode) && this.isEvalOrArguments(expression)) { return false; } return true; } isValidReferenceExpression(expression) { return this.isAssignableIdentifier(expression) || this.factory.isPropertyOrMemberExpression(expression); } expectSemicolon() { const tok = this.peek(); if (tok.isType(Token.SEMICOLON)) { this.next(); return; } if (this.scanner.hasLineTerminatorBeforeNext() || Token.isAutoSemicolon(tok.token)) { return; } if (this.scanner.currentToken().isType(Token.AWAIT) && !isAsyncFunction(this.functionKind)) { throw new Error(this.errorMessage(`Await Not In Async Context/Function`)); } } peekAnyIdentifier() { return Token.isAnyIdentifier(this.peek().token); } expectContextualKeyword(keyword) { const current = this.scanner.next(); if (!current.test((token, value) => Token.IDENTIFIER.equal(token) && keyword === value?.toString())) { throw new Error(this.errorMessage(`Unexpected Token: current Token is ${JSON.stringify(current)}`)); } return true; } checkContextualKeyword(keyword) { const next = this.scanner.peek(); if (next.test((token, value) => Token.IDENTIFIER.equal(token) && keyword === value?.toString())) { this.scanner.next(); return true; } return false; } peekContextualKeyword(keyword) { const next = this.scanner.peek(); return next.test((token, value) => Token.IDENTIFIER.equal(token) && keyword === value?.toString()); } methodKindFor(isStatic, flag) { return functionKindForImpl(isStatic ? SubFunctionKind.StaticMethod : SubFunctionKind.NonStaticMethod, flag); } isGenerator() { return isGeneratorFunction(this.functionKind); } isAsyncFunction() { return isAsyncFunction(this.functionKind); } is_async_function() { return isAsyncFunction(this.functionKind); } is_async_generator() { return isAsyncGeneratorFunction(this.functionKind); } is_resumable() { return isResumableFunction(this.functionKind); } isAwaitAllowed() { return this.isAsyncFunction() || isModule(this.functionKind); } isAwaitAsIdentifierDisallowed() { return isStrict(this.languageMode) || isAwaitAsIdentifierDisallowed(this.functionKind); } shouldBanArguments() { return isClassMembersInitializerFunction(this.functionKind); } errorMessage(message) { return this.scanner.createError(message); } reportErrorMessage(message) { console.error(this.scanner.createError(message)); } } export class JavaScriptInlineParser extends AbstractParser { static parse(source, { mode, acceptIN, factory, addLocation } = {}) { mode ??= LanguageMode.Strict; const stream = (typeof source === 'string') ? TokenStream.getTokenStream(source, mode) : Array.isArray(source) ? TokenStream.getTokenStream(source) : source; acceptIN ??= false; if (factory == undefined) { const sourcePositionFactory = addLocation && typeof source === 'string' ? new ExpressionNodeSourcePosition(source) : undefined; factory = new ExpressionNodeFactory(sourcePositionFactory); } const parser = new JavaScriptInlineParser(stream, factory, acceptIN); return parser.scan(); } constructor(scanner, factory, acceptIN) { super(scanner, factory, acceptIN); } scan() { const start = this.scanner.getPos(); const list = this.parseStatementList(Token.EOS); if (list.length === 1) { return list[0]; } return this.factory.createExpressionStatement(list, [start, this.scanner.getPos()]); } /** * Statement :: * Block * VariableStatement * EmptyStatement * ExpressionStatement * IfStatement * IterationStatement * ContinueStatement * BreakStatement * ReturnStatement * WithStatement * LabelledStatement * SwitchStatement * ThrowStatement * TryStatement * DebuggerStatement */ parseStatement(allowFunction = AllowLabelledFunctionStatement.DisallowLabelledFunctionStatement) { switch (this.peek().token) { case Token.LBRACE: return this.parseBlock(); case Token.SEMICOLON: const semicolon = this.consume(Token.SEMICOLON); return this.factory.createEmptyStatement(semicolon.range); case Token.IF: return this.parseIfStatement(); case Token.DO: return this.parseDoWhileStatement(); case Token.WHILE: return this.parseWhileStatement(); case Token.FOR: if (this.isAwaitAllowed() && this.peekAhead().isType(Token.AWAIT)) { return this.parseForAwaitStatement(); } return this.parseForStatement(); case Token.CONTINUE: return this.parseContinueStatement(); case Token.BREAK: return this.parseBreakStatement(); case Token.RETURN: return this.parseReturnStatement(); case Token.THROW: return this.parseThrowStatement(); case Token.TRY: return this.parseTryStatement(); case Token.SWITCH: return this.parseSwitchStatement(); case Token.WITH: return this.parseWithStatement(); case Token.FUNCTION: throw new SyntaxError(this.errorMessage(`FunctionDeclaration only allowed as a StatementListItem not in an arbitrary Statement position.`)); case Token.DEBUGGER: return this.parseDebuggerStatement(); case Token.VAR: return this.parseVariableDeclarations(VariableDeclarationContext.Statement); case Token.ASYNC: if (!this.scanner.hasLineTerminatorAfterNext() && this.peekAhead().isType(Token.FUNCTION)) { throw new SyntaxError(this.errorMessage(`async function in single statement context.`)); } default: return this.parseExpressionOrLabelledStatement(allowFunction); } } parseDebuggerStatement() { const token = this.consume(Token.DEBUGGER); this.expectSemicolon(); return this.factory.createDebuggerStatement(token.range); } parseTryStatement() { // TryStatement :: // 'try' Block Catch // 'try' Block Finally // 'try' Block Catch Finally // // Catch :: // 'catch' '(' Identifier ')' Block // // Finally :: // 'finally' Block const tryToken = this.consume(Token.TRY); const tryBlock = this.parseBlock(); let range; let peek = this.peek(); if (peek.isNotType(Token.CATCH) && peek.isNotType(Token.FINALLY)) { throw new Error(this.errorMessage(`Uncaught SyntaxError: Missing catch or finally after try`)); } let catchBlock; if (this.check(Token.CATCH)) { let identifier; const hasBinding = this.check(Token.LPAREN); if (hasBinding) { if (this.peekAnyIdentifier()) { identifier = this.parseNonRestrictedIdentifier(); } else { identifier = this.parseBindingPattern(true); } this.expect(Token.RPAREN); } const block = this.parseBlock(); catchBlock = this.factory.createCatchClause(block, identifier, this.createRange(peek)); range = this.createRange(tryToken); } let finallyBlock; if (this.check(Token.FINALLY)) { finallyBlock = this.parseBlock(); range = this.createRange(tryToken); } return this.factory.createTryStatement(tryBlock, catchBlock, finallyBlock, range); } parseNonRestrictedIdentifier() { const result = this.parseIdentifier(); if (isStrict(this.languageMode) && this.isEvalOrArguments(result)) { throw new SyntaxError(this.errorMessage('Strict Eval/Arguments ')); } return result; } parseBlock() { const start = this.expect(Token.LBRACE); const statements = []; const range = this.createRangeByStart(start); const block = this.factory.createBlock(statements, range); while (this.peek().isNotType(Token.RBRACE)) { const stat = this.parseStatementListItem(); if (!stat) { return block; } else if (this.factory.isEmptyStatement(stat)) { continue; } statements.push(stat); } this.expect(Token.RBRACE); this.updateRangeEnd(range); return block; } /** * ECMA 262 6th Edition * StatementListItem[Yield, Return] : * Statement[?Yield, ?Return] * Declaration[?Yield] * // * Declaration[Yield] : * HoistableDeclaration[?Yield] * ClassDeclaration[?Yield] * LexicalDeclaration[In, ?Yield] * // * HoistableDeclaration[Yield, Default] : * FunctionDeclaration[?Yield, ?Default] * GeneratorDeclaration[?Yield, ?Default] * // * LexicalDeclaration[In, Yield] : * LetOrConst BindingList[?In, ?Yield] ; */ parseStatementListItem() { switch (this.peek().token) { case Token.FUNCTION: return this.parseHoistableDeclaration(undefined, false, this.peek()); case Token.CLASS: const classToken = this.consume(Token.CLASS); return this.parseClassDeclaration(undefined, false, classToken); case Token.VAR: case Token.CONST: return this.parseVariableDeclarations(VariableDeclarationContext.StatementListItem); case Token.LET: if (this.isNextLetKeyword()) { return this.parseVariableDeclarations(VariableDeclarationContext.StatementListItem); } break; case Token.ASYNC: if (this.peekAhead().isType(Token.FUNCTION) && !this.scanner.hasLineTerminatorAfterNext()) { const start = this.consume(Token.ASYNC); return this.parseAsyncFunctionDeclaration(undefined, false, start); } break; default: break; } return this.parseStatement(AllowLabelledFunctionStatement.AllowLabelledFunctionStatement); } parseFunctionExpression() { const start = this.consume(Token.FUNCTION); const functionKind = this.check(Token.MUL) ? FunctionKind.GeneratorFunction : FunctionKind.NormalFunction; let name; let functionSyntaxKind = FunctionSyntaxKind.AnonymousExpression; const peek = this.peek(); if (peek.isNotType(Token.LPAREN)) { name = this.parseIdentifier(this.getLastFunctionKind()); functionSyntaxKind = FunctionSyntaxKind.NamedExpression; } return this.parseFunctionLiteral(functionKind, functionSyntaxKind, name, start); } parseAsyncFunctionDeclaration(names, defaultExport, start) { // AsyncFunctionDeclaration :: // async [no LineTerminator here] function BindingIdentifier[Await] // ( FormalParameters[Await] ) { AsyncFunctionBody } if (this.scanner.hasLineTerminatorBeforeNext()) { throw new SyntaxError(this.errorMessage('Line Terminator Before `function` parsing `async function`.')); } this.consume(Token.FUNCTION); return this.parseHoistableDeclaration01(FunctionKind.AsyncFunction, names, defaultExport, start); } parseIfStatement() { const ifToken = this.consume(Token.IF); this.consume(Token.LPAREN); const condition = this.parseExpression(); this.consume(Token.RPAREN); const thenStatement = this.parseScopedStatement(); let range; let elseStatement; if (this.peek().isType(Token.ELSE)) { this.consume(Token.ELSE); elseStatement = this.parseScopedStatement(); } range = this.createRange(ifToken); return this.factory.createIfStatement(condition, thenStatement, elseStatement, range); } parseScopedStatement() { if (isStrict(this.languageMode) || this.peek().isNotType(Token.FUNCTION)) { return this.parseStatement(); } else { return this.parseFunctionDeclaration(); } } parseDoWhileStatement() { // DoStatement :: // 'do' Statement 'while' '(' Expression ')' ';' const start = this.consume(Token.DO); const body = this.parseStatement(); this.expect(Token.WHILE); this.expect(Token.LPAREN); const condition = this.parseExpression(); this.expect(Token.RPAREN); this.check(Token.SEMICOLON); return this.factory.createDoStatement(condition, body, this.createRange(start)); } parseWhileStatement() { // WhileStatement :: // 'while' '(' Expression ')' Statement const start = this.consume(Token.WHILE); this.expect(Token.LPAREN); const condition = this.parseExpression(); this.expect(Token.RPAREN); const body = this.parseStatement(); return this.factory.createWhileStatement(condition, body, this.createRange(start)); } parseThrowStatement() { // ThrowStatement :: // 'throw' Expression ';' const throwToken = this.consume(Token.THROW); if (this.scanner.hasLineTerminatorBeforeNext()) { throw new Error(this.scanner.createError(`New line After Throw`)); } const exception = this.parseExpression(); const range = this.createRange(throwToken); this.expectSemicolon(); return this.factory.createThrowStatement(exception, range); } parseSwitchStatement() { // SwitchStatement :: // 'switch' '(' Expression ')' '{' CaseClause* '}' // CaseClause :: // 'case' Expression ':' StatementList // 'default' ':' StatementList const start = this.consume(Token.SWITCH); const range = this.createRangeByStart(start); this.expect(Token.LPAREN); const tag = this.parseExpression(); this.expect(Token.RPAREN); const cases = []; const switchStatement = this.factory.createSwitchStatement(tag, cases, range); let defaultSeen = false; this.expect(Token.LBRACE); while (this.peek().isNotType(Token.RBRACE)) { const statements = []; let test; const caseStart = this.scanner.peek(); if (this.check(Token.CASE)) { test = this.parseExpression(); } else { this.expect(Token.DEFAULT); if (defaultSeen) { throw new Error(this.errorMessage(`Multiple Defaults In Switch`)); } defaultSeen = true; } const blockStart = this.expect(Token.COLON); while (this.peek().isNotType(Token.CASE) && this.peek().isNotType(Token.DEFAULT) && this.peek().isNotType(Token.RBRACE)) { const statement = this.parseStatementListItem(); if (!statement || this.factory.isEmptyStatement(statement)) { continue; } statements.push(statement); } const block = this.factory.createBlock(statements, this.createRange(blockStart)); const caseRange = this.createRange(caseStart); const clause = defaultSeen ? this.factory.createDefaultClause(block, caseRange) : this.factory.createCaseBlock(test, block, caseRange); cases.push(clause); } this.expect(Token.RBRACE); this.updateRangeEnd(range); return switchStatement; } parseForStatement() { // Either a standard for loop // for (<init>; <cond>; <next>) { ... } // or a for-each loop // for (<each> of|in <iterable>) { ... } // // We parse a declaration/expression after the 'for (' and then read the first // expression/declaration before we know if this is a for or a for-each. // typename FunctionState::LoopScope loop_scope(function_state_); const start = this.consume(Token.FOR); this.expect(Token.LPAREN); const peek = this.peek(); const starts_with_let = peek.isType(Token.LET); if (peek.isType(Token.CONST) || (starts_with_let && this.isNextLetKeyword())) { // The initializer contains lexical declarations, // so create an in-between scope. // Also record whether inner functions or evals are found inside // this loop, as this information is used to simplify the desugaring // if none are found. // typename FunctionState::FunctionOrEvalRecordingScope recording_scope(function_state_); const initializer = this.parseVariableDeclarations(VariableDeclarationContext.ForStatement); const forMode = this.checkInOrOf(); if (forMode) { return this.parseForEachStatementWithDeclarations(initializer, forMode, start); } this.expect(Token.SEMICOLON); // Parse the remaining code in the inner block scope since the declaration // above was parsed there. We'll finalize the unnecessary outer block scope // after parsing the rest of the loop. return this.parseStandardForLoopWithLexicalDeclarations(initializer, start); } let initializer; if (peek.isType(Token.VAR)) { initializer = this.parseVariableDeclarations(VariableDeclarationContext.ForStatement); // ParseVariableDeclarations(kForStatement, & for_info.parsing_result,& for_info.bound_names); // DCHECK_EQ(for_info.parsing_result.descriptor.mode, VariableMode:: kVar); // for_info.position = scanner() -> location().beg_pos; const forMode = this.checkInOrOf(); if (forMode) { return this.parseForEachStatementWithDeclarations(initializer, forMode, start); } // init = impl() -> BuildInitializationBlock(& for_info.parsing_result); } else if (this.peek().isNotType(Token.SEMICOLON)) { // The initializer does not contain declarations. this.setAcceptIN(false); initializer = this.parseExpressionCoverGrammar(); this.restoreAcceptIN(); // ExpressionParsingScope parsing_scope(impl()); // AcceptINScope scope(this, false); // `for (async of` is disallowed but `for (async.x of` is allowed, so // check if the token is ASYNC after parsing the expression. // Initializer is reference followed by in/of. const expression_is_async = this.current().isType(Token.ASYNC); const forMode = this.checkInOrOf(); if (forMode) { if (forMode === 'OF' && starts_with_let || expression_is_async) { throw new SyntaxError(this.errorMessage(starts_with_let ? 'For Of Let' : 'For Of Async')); } return this.parseForEachStatementWithoutDeclarations(initializer, forMode, start); } } this.expect(Token.SEMICOLON); return this.parseStandardForLoop(initializer, start); } parseStandardForLoopWithLexicalDeclarations(initializer, start) { // The condition and the next statement of the for loop must be parsed // in a new scope. return this.parseStandardForLoop(initializer, start); } parseForEachStatementWithDeclarations(initializer, forMode, start) { // Just one declaration followed by in/of. if (initializer.getDeclarations().length != 1) { throw new SyntaxError(this.errorMessage('For In/Of loop Multi Bindings')); } return this.parseForEachStatementWithoutDeclarations(initializer, forMode, start); } parseForEachStatementWithoutDeclarations(initializer, forMode, start) { let enumerable; if (forMode == 'OF') { this.setAcceptIN(true); enumerable = this.parseAssignmentExpression(); this.restoreAcceptIN(); } else { enumerable = this.parseExpression(); } this.expect(Token.RPAREN); const body = this.parseStatement(); const range = this.createRange(start); if (forMode === 'OF') { return this.factory.createForOfStatement(initializer, enumerable, body, range); } else if (forMode === 'IN') { return this.factory.createForInStatement(initializer, enumerable, body, range); } throw new Error(this.errorMessage(`parsing for loop: ${this.position()}`)); } parseStandardForLoop(initializer, start) { // CheckStackOverflow(); // ForStatementT loop = factory() -> NewForStatement(stmt_pos); // Target target(this, loop, labels, own_labels, Target:: TARGET_FOR_ANONYMOUS); let cond = this.factory.createEmptyStatement(); if (this.peek().isNotType(Token.SEMICOLON)) { cond = this.parseExpression(); } this.expect(Token.SEMICOLON); let next = this.factory.createEmptyStatement(); if (this.peek().isNotType(Token.RPAREN)) { next = this.parseExpression(); } this.expect(Token.RPAREN); const body = this.parseStatement(); return this.factory.createForStatement(body, initializer, cond, next, this.createRange(start)); } parseForAwaitStatement() { // for await '(' ForDeclaration of AssignmentExpression ')' // Create an in-between scope for let-bound iteration variables. // BlockState for_state(zone(), & scope_); if (!this.isAwaitAllowed()) { throw new SyntaxError(this.errorMessage('"await" is not allowed')); } const start = this.expect(Token.FOR); this.expect(Token.AWAIT); this.expect(Token.LPAREN); let hasDeclarations = false; // Scope * inner_block_scope = NewScope(BLOCK_SCOPE); let eachVariable; let peek = this.peek(); let startsWithLet = peek.isType(Token.LET); if (peek.isType(Token.VAR) || peek.isType(Token.CONST) || (startsWithLet && this.isNextLetKeyword())) { // The initializer contains declarations // 'for' 'await' '(' ForDeclaration 'of' AssignmentExpression ')' // Statement // 'for' 'await' '(' 'var' ForBinding 'of' AssignmentExpression ')' // Statement hasDeclarations = true; const initializer = this.parseVariableDeclarations(VariableDeclarationContext.ForStatement); if (initializer.getDeclarations().length != 1) { throw new SyntaxError(this.errorMessage('For In/Of Loop MultiBindings, "for-await-of"')); } eachVariable = initializer; } else { // The initializer does not contain declarations. // 'for' 'await' '(' LeftHandSideExpression 'of' AssignmentExpression ')' // Statement if (startsWithLet) { throw new SyntaxError(this.errorMessage('The initializer does not contain declarations, For Of Let, "for-await-of"')); } eachVariable = this.parseLeftHandSideExpression(); } this.expectContextualKeyword('of'); this.setAcceptIN(true); const iterable = this.parseAssignmentExpression(); this.restoreAcceptIN(); this.expect(Token.RPAREN); const body = this.parseStatement(); const range = this.createRange(start); return this.factory.createForAwaitOfStatement(eachVariable, iterable, body, range); } parseVariableDeclarations(varContext) { // VariableDeclarations :: // ('var' | 'const' | 'let') (Identifier ('=' AssignmentExpression)?)+[','] // var converted into ==> 'let' by parser let mode; const start = this.peek(); const token = start.token; switch (token) { case Token.CONST: this.consume(token); mode = 'const'; break; case Token.VAR: this.consume(token); mode = 'var'; break; case Token.LET: default: this.consume(token); mode = 'let'; break; } const variables = []; do { let name; let value; const range = this.createStartPosition(); // Check for an identifier first, so that we can elide the pattern in cases // where there is no initializer (and so no proxy needs to be created). if (Token.isAnyIdentifier(this.peek().token)) { name = this.parseAndClassifyIdentifier(this.next()); if (isStrict(this.languageMode) && this.isEvalOrArguments(name)) { throw new Error(this.errorMessage(`Strict Eval Arguments`)); } // if (this.peekInOrOf()) { // // // Assignments need the variable expression for the assignment LHS, and // // // for of/in will need it later, so create the expression now. // } } else { name = this.parseBindingPattern(true); } if (this.check(Token.ASSIGN)) { this.setAcceptIN(varContext !== VariableDeclarationContext.ForStatement); value = this.parseAssignmentExpression(); this.restoreAcceptIN(); } else if (!this.peekInOrOf()) { // ES6 'const' and binding patterns require initializers. if (mode === 'const' && (name === undefined || value === undefined)) { throw new Error(this.errorMessage(`Declaration Missing Initializer`)); } // value = undefined; } this.updateRangeEnd(range); variables.push(this.factory.createVariableDeclaration(name, value, range)); } while (this.check(Token.COMMA)); const range = this.createRange(start); return this.factory.createVariableStatement(variables, mode, range); } parseBindingPattern(isPattern) { // Pattern :: // Identifier // ArrayLiteral // ObjectLiteral const token = this.peek().token; if (Token.isAnyIdentifier(token)) { const name = this.parseAndClassifyIdentifier(this.next()); if (isStrict(this.languageMode) && this.isEvalOrArguments(name)) { throw new Error(this.errorMessage(`Strict Eval Arguments`)); } return name; } if (token == Token.LBRACK) { return this.parseArrayLiteral(isPattern); } else if (token == Token.LBRACE) { return this.parseObjectLiteral(isPattern); } throw new Error(this.errorMessage(`Unexpected Token: ${this.next().getValue()}`)); } parseAndClassifyIdentifier(next) { // Updates made here must be reflected on ClassifyPropertyIdentifier. if (Token.isInRangeIdentifierAndAsync(next.token)) { const name = next.getValue(); if (this.isArguments(name) && this.shouldBanArguments()) { throw new SyntaxError(this.errorMessage('Arguments Disallowed In Initializer And Static Block')); } return name; } if (!Token.isValidIdentifier(next.token, this.languageMode, this.isGenerator(), this.isAwaitAsIdentifierDisallowed())) { throw new SyntaxError(this.errorMessage('Invalid Identifier')); } if (next.isType(Token.AWAIT)) { return next.getValue(); } return next.getValue(); } parseContinueStatement() { // ContinueStatement :: // 'continue' ';' // Identifier? is not supported const start = this.consume(Token.CONTINUE); let label; if (!this.scanner.hasLineTerminatorBeforeNext() && !Token.isAutoSemicolon(this.peek().token)) { // ECMA allows "eval" or "arguments" as labels even in strict mode. label = this.parseIdentifier(); } const range = this.createRange(start); this.expectSemicolon(); return this.factory.createContinueStatement(label, range); } parseBreakStatement() { // BreakStatement :: // 'break' ';' // Identifier? is not supported const start = this.consume(Token.BREAK); let label; if (!this.scanner.hasLineTerminatorBeforeNext() && !Token.isAutoSemicolon(this.peek().token)) { // ECMA allows "eval" or "arguments" as labels even in strict mode. label = this.parseIdentifier(); } const range = this.createRange(start); this.expectSemicolon(); return this.factory.createBreakStatement(label, range); } parseReturnStatement() { // ReturnStatement :: // 'return' [no line terminator] Expression? ';' // Consume the return token. It is necessary to do that before // reporting any errors on it, because of the way errors are // reported (underlining). const start = this.consume(Token.RETURN); const tokenExp = this.peek(); let returnValue; // ExpressionT return_value = impl() -> NullExpression(); if (this.scanner.hasLineTerminatorBeforeNext() || Token.isAutoSemicolon(tokenExp.token)) { // check if this scope is belong to 'constructor' method to return this at the end; // if (this.isDerivedConstructor(this.functionKind)) { // returnValue = ThisNode; // } } else { returnValue = this.parseExpression(); } const range = this.createRange(start); this.expectSemicolon(); return this.factory.createReturnStatement(returnValue, range); } parseExpressionOrLabelledStatement(allowFunction) { // ExpressionStatement | LabelledStatement :: // Expression ';' // Identifier ':' Statement // // ExpressionStatement[Yield] : // [lookahead notin {{, function, class, let [}] Expression[In, ?Yield] ; switch (this.peek().token) { case Token.FUNCTION: case Token.LBRACE: throw new Error(this.errorMessage(`Unreachable state`)); case Token.CLASS: throw new Error(this.errorMessage(`Unexpected Token ${this.next().getValue().toString()}`)); case Token.LET: { const nextNext = this.peekAhead(); // "let" followed by either "[", "{" or an identifier means a lexical // declaration, which should not appear here. // However, ASI may insert a line break before an identifier or a brace. if (nextNext.isNotType(Token.LBRACK) && ((nextNext.isNotType(Token.LBRACE) && nextNext.isNotType(Token.IDENTIFIER)))) { break; } throw new Error(this.errorMessage(`Unexpected Lexical Declaration ${this.position()}`)); } default: break; } const range = this.createStartPosition(); const startsWithIdentifier = Token.isAnyIdentifier(this.peek().token); this.setAcceptIN(true); const expression = this.parseExpressionCoverGrammar(); if (this.peek().isType(Token.COLON) && startsWithIdentifier && this.factory.isIdentifier(expression)) { // The whole expression was a single identifier, and not, e.g., // something starting with an identifier or a parenthesized identifier. // Remove the "ghost" variable that turned out to be a label from the top // scope. This way, we don't try to resolve it during the scope // processing. this.consume(Token.COLON); // ES#sec-labelled-function-declarations Labelled Function Declarations if (this.peek().isType(Token.FUNCTION) && isSloppy(this.languageMode) && allowFunction == AllowLabelledFunctionStatement.AllowLabelledFunctionStatement) { const result = this.parseFunctionDeclaration(); this.restoreAcceptIN(); this.updateRangeEnd(range); return this.factory.createLabeledStatement(expression, result, range); } const result = this.parseStatement(allowFunction); this.restoreAcceptIN(); this.updateRangeEnd(range); return this.factory.createLabeledStatement(expression, result, range); } this.restoreAcceptIN(); // Parsed expression statement, followed by semicolon. this.expectSemicolon(); return expression; } parseExpression() { this.setAcceptIN(true); const result = this.parseExpressionCoverGrammar(); this.restoreAcceptIN(); return result; } parseFunctionDeclaration() { const start = this.consume(Token.FUNCTION); if (this.check(Token.MUL)) { throw new Error(this.errorMessage(`Error Generator In Single Statement Context`)); } return this.parseHoistableDeclaration01(FunctionKind.NormalFunction, undefined, false, start); } parseAsyncFunctionLiteral() { // AsyncFunctionLiteral :: // async [no LineTerminator here] function ( FormalParameters[Await] ) // { AsyncFunctionBody } // // async [no LineTerminator here] function BindingIdentifier[Await] // ( FormalParameters[Await] ) { AsyncFunctionBody } if (this.current().isNotType(Token.ASYNC)) { throw new SyntaxError(this.errorMessage('invalid token')); } const start = this.consume(Token.FUNCTION); let name; let syntaxKind = FunctionSyntaxKind.AnonymousExpression; let flags = FunctionKind.AsyncFunction; if (this.check(Token.MUL)) { flags = FunctionKind.AsyncGeneratorFunction; } if (this.peekAnyIdentifier()) { syntaxKind = FunctionSyntaxKind.NamedExpression; name = this.parseIdentifier(this.getLastFunctionKind()); } return this.parseFunctionLiteral(flags, syntaxKind, name, start); } parseHoistableDeclaration(names, defaultExport, start) { this.consume(Token.FUNCTION); let flags = FunctionKind.NormalFunction; if (this.check(Token.MUL)) { flags = FunctionKind.GeneratorFunction; } return this.parseHoistableDeclaration01(flags, names, defaultExport, start); } parseHoistableDeclaration01(flag, names, defaultExport, start) { // FunctionDeclaration :: // 'function' Identifier '(' FormalParameters ')' '{' FunctionBody '}' // 'function' '(' FormalParameters ')' '{' FunctionBody '}' // GeneratorDeclaration :: // 'function' '*' Identifier '(' FormalParameters ')' '{' FunctionBody '}' // 'function' '*' '(' FormalParameters ')' '{' FunctionBody '}' // // The anonymous forms are allowed iff [default_export] is true. // // 'function' and '*' (if present) have been consumed by the caller. // (FunctionType.ASYNC === flag || FunctionType.GENERATOR === flag); if ((flag & ParseFunctionFlag.IsAsync) != 0 && this.check(Token.MUL)) { // Async generator flag |= ParseFunctionFlag.IsGenerator; } let name; if (this.peek().isType(Token.LPAREN)) { if (defaultExport) { name = this.factory.createIdentifier('default', this.createRange(start)); } else { throw new SyntaxError(this.errorMessage('Missing Function Name')); } } else { name = this.parseIdentifier(this.getLastFunctionKind()); } names?.push(name.toString()); return this.parseFunctionLiteral(flag, FunctionSyntaxKind.Declaration, name, start); } parseIdentifier(kind) { kind ??= this.functionKind; const next = this.next(); if (!Token.isValidIdentifier(next.token, this.languageMode, isGeneratorFunction(kind), isAwaitAsIdentifierDisallowed(kind))) { throw new Error(this.errorMessage(`Unexpected Token: ${next.getValue()}`)); } if (next.isType(Token.IDENTIFIER)) { return next.getValue(); } return this.getIdentifier(); } getIdentifier() { const current = this.current(); switch (current.token) { case Token.AWAIT: return this.factory.createIdentifier('await', current.range); case Token.ASYNC: return this.factory.createIdentifier('async', current.range); case Token.IDENTIFIER: case Token.PRIVATE_NAME: return current.getValue(); default: break; } const name = current.getValue().toString(); if (name == 'constructor') { return this.factory.createIdentifier('constructor', current.range); } if (name == 'name') { return this.factory.createIdentifier('name', current.range); } if (name == 'eval') { return this.factory.createIdentifier('eval', current.range); } else if (name == 'arguments') { return this.factory.createIdentifier('arguments', current.range); } return current.getValue(); } parseFunctionLiteral(flag, functionSyntaxKind, name, start) { // Function :: // '(' FormalParameterList? ')' '{' FunctionBody '}' this.setFunctionKind(flag); const functionInfo = {}; this.expect(Token.LPAREN); const formals = this.parseFormalParameterList(functionInfo); this.expect(Token.RPAREN); const bodyStart = this.expect(Token.LBRACE); this.setAcceptIN(true); const body = this.parseFunctionBody(flag, FunctionBodyType.BLOCK, functionSyntaxKind); this.restoreAcceptIN(); this.restoreFunctionKind(); const bodyRange = this.createRange(bodyStart); const funcRange = this.createRange(start); const bodyBlock = this.factory.createBlock(body, bodyRange); if (name) { return this.factory.createFunctionDeclaration(formals, bodyBlock, isAsyncFunction(flag), isGeneratorFunction(flag), name, funcRange); } return this.factory.createFunctionExpression(formals, bodyBlock, isAsyncFunction(flag), isGeneratorFunction(flag), funcRange); } parseFunctionBody(kind, bodyType, functionSyntaxKind) { // Building the parameter initialization block declares the parameters. // TODO(verwaest): Rely on ArrowHeadParsingScope instead. let innerBody; if (bodyType == FunctionBodyType.EXPRESSION) { innerBody = this.parseAssignmentExpression(); } else { // DCHECK(accept_IN_); // DCHECK_EQ(FunctionBodyType:: kBlock, body_type); // If we are parsing the source as if it is wrapped in a function, the // source ends without a closing brace. const closing_token = functionSyntaxKind == FunctionSyntaxKind.Wrapped ? Token.EOS : Token.RBRACE; if (isAsyncGeneratorFunction(kind)) { innerBody = this.parseAndRewriteAsyncGeneratorFunctionBody(kind); } else if (isGeneratorFunction(kind)) { innerBody = this.parseAndRewriteGeneratorFunctionBody(kind); } else if (isAsyncFunction(kind)) { innerBody = this.parseAsyncFunctionBody(); } else { innerBody = this.parseStatementList(closing_token); } this.expect(closing_token); } return innerBody; } parseAndRewriteAsyncGeneratorFunctionBody(kind) { return this.parseStatementList(Token.RBRACE); } parseAndRewriteGeneratorFunctionBody(kind) { return this.parseStatementList(Token.RBRACE); } parseAsyncFunctionBody() { return this.parseStatementList(Token.RBRACE); } parseStatementList(endToken) { // StatementList :: // (StatementListItem)* <end_token> if (this.peek().test((token, value) => Token.STRING === token && 'use strict' === value.getValue())) { this.languageMode = LanguageMode.Strict; } const list = []; while (this.peek().isNotType(endToken)) { const stat = this.parseStatementListItem(); if (!stat) { break; } if (this.factory.isEmptyStatement(stat)) { continue; } list.push(stat); } return list; } parseFormalParameterList(functionInfo) { // FormalParameters[Yield] : // [empty] // FunctionRestParameter[?Yield] // FormalParameterList[?Yield] // FormalParameterList[?Yield] , // FormalParameterList[?Yield] , FunctionRestParameter[?Yield] // // FormalParameterList[Yield] : // FormalParameter[?Yield] // FormalParameterList[?Yield] , FormalParameter[?Yield] const parameters = []; if (this.peek().isNotType(Token.RPAREN)) { while (true) { const param = this.parseFormalParameter(functionInfo); parameters.push(param); if (functionInfo.rest) { if (this.peek().isType(Token.COMMA)) { throw new Error(this.errorMessage(`Param After Rest`)); } break; } if (!this.check(Token.COMMA)) break; if (this.peek().isType(Token.RPAREN)) { // allow the trailing comma break;