@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
JavaScript
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;