All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.shapesecurity.shift.parser.Parser Maven / Gradle / Ivy

There is a newer version: 2.2.0
Show newest version
/**
 * Copyright 2014 Shape Security, Inc. 

Licensed under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. You may obtain a * copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by * applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See * the License for the specific language governing permissions and limitations under the License. */ package com.shapesecurity.shift.parser; import com.shapesecurity.functional.Pair; import com.shapesecurity.functional.Thunk; import com.shapesecurity.functional.data.Either; import com.shapesecurity.functional.data.Maybe; import com.shapesecurity.functional.data.ImmutableList; import com.shapesecurity.functional.data.NonEmptyImmutableList; import com.shapesecurity.shift.ast.*; import com.shapesecurity.shift.ast.operators.*; import com.shapesecurity.shift.parser.token.NumericLiteralToken; import com.shapesecurity.shift.parser.token.RegularExpressionLiteralToken; import com.shapesecurity.shift.parser.token.StringLiteralToken; import com.shapesecurity.shift.parser.token.TemplateToken; import com.shapesecurity.shift.utils.D2A; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.function.BiFunction; public abstract class Parser extends Tokenizer { private boolean inFunctionBody; private boolean module; private boolean strict; private boolean allowIn = true; private boolean isBindingElement; private boolean isAssignmentTarget = true; @Nullable private JsError firstExprError; private boolean allowYieldExpression; private boolean inParameter = false; private Parser(@NotNull String source, boolean isModule) throws JsError { super(source, isModule); this.module = this.strict = isModule; } boolean eat(@NotNull TokenType subType) throws JsError { if (this.lookahead.type != subType) { return false; } this.lex(); return true; } @NotNull Token expect(@NotNull TokenType subType) throws JsError { if (this.lookahead.type != subType) { throw this.createUnexpected(this.lookahead); } return this.lex(); } protected boolean match(@NotNull TokenType subType) { return this.lookahead.type == subType; } private void consumeSemicolon() throws JsError { // Catch the very common case first: immediately a semicolon (U+003B). if (this.hasLineTerminatorBeforeNext) { return; } if (this.eat(TokenType.SEMICOLON)) { return; } if (!this.eof() && !this.match(TokenType.RBRACE)) { throw this.createUnexpected(this.lookahead); } } @NotNull protected T markLocation(@NotNull SourceLocation startLocation, @NotNull T node) { // TODO: actually mark location // node.loc = Maybe.just(new SourceSpan(Maybe.nothing(), startLocation, new SourceLocation(this.lastLine+1, this.lastIndex-this.lastLineStart, this.lastIndex))); return node; } private boolean lookaheadLexicalDeclaration() throws JsError { if (this.match(TokenType.LET) || this.match(TokenType.CONST)) { TokenizerState tokenizerState = this.saveTokenizerState(); this.lex(); if (this.match(TokenType.IDENTIFIER) || this.match(TokenType.LET) || this.match(TokenType.LBRACE) || this.match(TokenType.LBRACK) || this.match(TokenType.YIELD)) { this.restoreTokenizerState(tokenizerState); return true; } else { this.restoreTokenizerState(tokenizerState); } } return false; } @NotNull public static Script parseScript(@NotNull String text) throws JsError { return new ScriptParser(text).parse(); } @NotNull public static Module parseModule(@NotNull String text) throws JsError { return new ModuleParser(text).parse(); } protected abstract Node parse() throws JsError; @NotNull protected ImportDeclarationExportDeclarationStatement parseModuleItem() throws JsError { switch (this.lookahead.type) { case IMPORT: return this.parseImportDeclaration(); case EXPORT: return this.parseExportDeclaration(); default: return this.parseStatementListItem(); } } @FunctionalInterface private interface ExceptionalSupplier { A get() throws JsError; } @NotNull protected B parseBody(@NotNull ExceptionalSupplier parser, @NotNull BiFunction, ImmutableList, B> constructor) throws JsError { ArrayList directives = new ArrayList<>(); ArrayList statements = new ArrayList<>(); boolean parsingDirectives = true; while (true) { if (this.eof() || this.match(TokenType.RBRACE)) { break; } Token token = this.lookahead; String text = token.slice.toString(); boolean isStringLiteral = token.type == TokenType.STRING; SourceLocation directiveLocation = this.getLocation(); A stmt = parser.get(); if (parsingDirectives) { if (isStringLiteral && stmt instanceof ExpressionStatement && ((ExpressionStatement) stmt).expression instanceof LiteralStringExpression) { String rawValue = text.substring(1, text.length() - 1); if (rawValue.equals("use strict")) { this.strict = true; } directives.add(this.markLocation(directiveLocation, new Directive(rawValue))); } else { parsingDirectives = false; statements.add(stmt); } } else { statements.add(stmt); } } return constructor.apply(ImmutableList.from(directives), ImmutableList.from(statements)); } @NotNull private FunctionBody parseFunctionBody() throws JsError { SourceLocation startLocation = this.getLocation(); boolean oldInFunctionBody = this.inFunctionBody; boolean oldModule = this.module; boolean oldStrict = this.strict; this.inFunctionBody = true; this.module = false; this.strict = false; this.expect(TokenType.LBRACE); FunctionBody body = this.parseBody(this::parseStatementListItem, FunctionBody::new); this.expect(TokenType.RBRACE); this.inFunctionBody = oldInFunctionBody; this.module = oldModule; this.strict = oldStrict; return this.markLocation(startLocation, body); } @NotNull protected Statement parseStatementListItem() throws JsError { if (this.eof()) { throw this.createUnexpected(this.lookahead); } switch (this.lookahead.type) { case FUNCTION: return this.parseFunctionDeclaration(false, true); case CLASS: return this.parseClass(false); default: if (this.lookaheadLexicalDeclaration()) { SourceLocation startLocation = this.getLocation(); return this.markLocation(startLocation, this.parseVariableDeclarationStatement()); } else { return this.parseStatement(); } } } @NotNull private Statement parseVariableDeclarationStatement() throws JsError { VariableDeclaration declaration = this.parseVariableDeclaration(true); this.consumeSemicolon(); return new VariableDeclarationStatement(declaration); } @NotNull private VariableDeclaration parseVariableDeclaration(boolean bindingPatternsMustHaveInit) throws JsError { SourceLocation startLocation = this.getLocation(); Token token = this.lex(); VariableDeclarationKind kind = token.type == TokenType.VAR ? VariableDeclarationKind.Var : token.type == TokenType.CONST ? VariableDeclarationKind.Const : VariableDeclarationKind.Let; ImmutableList declarators = this.parseVariableDeclaratorList(bindingPatternsMustHaveInit); return this.markLocation(startLocation, new VariableDeclaration(kind, declarators)); } @NotNull private ImmutableList parseVariableDeclaratorList(boolean bindingPatternsMustHaveInit) throws JsError { ArrayList result = new ArrayList<>(); do { result.add(this.parseVariableDeclarator(bindingPatternsMustHaveInit)); } while (this.eat(TokenType.COMMA)); return ImmutableList.from(result); } @NotNull private VariableDeclarator parseVariableDeclarator(boolean bindingPatternsMustHaveInit) throws JsError { SourceLocation startLocation = this.getLocation(); if (this.match(TokenType.LPAREN)) { throw this.createUnexpected(this.lookahead); } Binding binding = this.parseBindingTarget(); if (bindingPatternsMustHaveInit && !(binding instanceof BindingIdentifier) && !this.match(TokenType.ASSIGN)) { this.expect(TokenType.ASSIGN); } Maybe init = Maybe.nothing(); if (this.eat(TokenType.ASSIGN)) { init = this.parseAssignmentExpression().left(); } return this.markLocation(startLocation, new VariableDeclarator(binding, init)); } @NotNull private Binding parseBindingTarget() throws JsError { switch (this.lookahead.type) { case IDENTIFIER: case LET: case YIELD: return this.parseBindingIdentifier(); case LBRACK: return this.parseArrayBinding(); case LBRACE: return this.parseObjectBinding(); } throw this.createUnexpected(this.lookahead); } @NotNull private Binding parseObjectBinding() throws JsError { SourceLocation startLocation = this.getLocation(); this.expect(TokenType.LBRACE); ArrayList properties = new ArrayList<>(); while (!this.match(TokenType.RBRACE)) { properties.add(this.parseBindingProperty()); if (!this.match(TokenType.RBRACE)) { this.expect(TokenType.COMMA); } } this.expect(TokenType.RBRACE); return this.markLocation(startLocation, new ObjectBinding(ImmutableList.from(properties))); } @NotNull private BindingProperty parseBindingProperty() throws JsError { SourceLocation startLocation = this.getLocation(); Token token = this.lookahead; Pair> fromParsePropertyName = this.parsePropertyName(); PropertyName name = fromParsePropertyName.a; Maybe binding = fromParsePropertyName.b; if ((token.type == TokenType.IDENTIFIER || token.type == TokenType.LET || token.type == TokenType.YIELD) && name instanceof StaticPropertyName) { if (!this.match(TokenType.COLON)) { Maybe defaultValue = Maybe.nothing(); if (this.eat(TokenType.ASSIGN)) { boolean previousAllowYieldExpression = this.allowYieldExpression; Either expr = this.parseAssignmentExpression(); defaultValue = expr.left(); this.allowYieldExpression = previousAllowYieldExpression; } return this.markLocation(startLocation, new BindingPropertyIdentifier((BindingIdentifier) binding.just(), defaultValue)); } } this.expect(TokenType.COLON); BindingBindingWithDefault fromParseBindingElement = this.parseBindingElement(); return this.markLocation(startLocation, new BindingPropertyProperty(name, fromParseBindingElement)); } @NotNull private Binding parseArrayBinding() throws JsError { SourceLocation startLocation = this.getLocation(); this.expect(TokenType.LBRACK); ArrayList> elements = new ArrayList<>(); Maybe restElement = Maybe.nothing(); while (true) { if (this.match(TokenType.RBRACK)) { break; } Maybe el; if (this.eat(TokenType.COMMA)) { el = Maybe.nothing(); } else { if (this.eat(TokenType.ELLIPSIS)) { restElement = Maybe.just(this.parseBindingTarget()); break; } else { el = Maybe.just(this.parseBindingElement()); } if (!this.match(TokenType.RBRACK)) { this.expect(TokenType.COMMA); } } elements.add(el); } this.expect(TokenType.RBRACK); return this.markLocation(startLocation, new ArrayBinding(ImmutableList.from(elements), restElement)); } @NotNull private BindingIdentifier parseBindingIdentifier() throws JsError { SourceLocation startLocation = this.getLocation(); return this.markLocation(startLocation, new BindingIdentifier(this.parseIdentifier())); } @NotNull private String parseIdentifier() throws JsError { if (this.match(TokenType.IDENTIFIER) || !this.allowYieldExpression && this.match(TokenType.YIELD) || this.match(TokenType.LET)) { return this.lex().toString(); } else { throw this.createUnexpected(this.lookahead); } } @NotNull private Expression parseArrowExpressionTail(ArrayList params, Maybe rest, SourceLocation startLocation) throws JsError { if (this.hasLineTerminatorBeforeNext) { throw this.createError(ErrorMessages.NEWLINE_AFTER_ARROW_PARAMS); } this.expect(TokenType.ARROW); this.isBindingElement = this.isAssignmentTarget = false; this.firstExprError = null; FormalParameters paramsNode = this.markLocation(startLocation, new FormalParameters(ImmutableList.from(params), rest)); if (this.match(TokenType.LBRACE)) { boolean previousYield = this.allowYieldExpression; this.allowYieldExpression = false; FunctionBody body = this.parseFunctionBody(); this.allowYieldExpression = previousYield; return this.markLocation(startLocation, new ArrowExpression(paramsNode, body)); } else { Either body = this.parseAssignmentExpression(); return this.markLocation(startLocation, new ArrowExpression(paramsNode, body.left().just())); } } @NotNull private Statement parseIfStatement() throws JsError { SourceLocation startLocation = this.getLocation(); this.lex(); this.expect(TokenType.LPAREN); Expression test = this.parseExpression().left().just(); this.expect(TokenType.RPAREN); Statement consequent = this.parseIfStatementChild(); Maybe alternate; if (this.eat(TokenType.ELSE)) { alternate = Maybe.just(this.parseIfStatementChild()); } else { alternate = Maybe.nothing(); } return this.markLocation(startLocation, new IfStatement(test, consequent, alternate)); } @NotNull private Statement parseIfStatementChild() throws JsError { return this.match(TokenType.FUNCTION) ? this.parseFunctionDeclaration(false, false) : this.parseStatement(); } @NotNull private Statement parseFunctionDeclaration(boolean inDefault, boolean allowGenerator) throws JsError { SourceLocation startLocation = this.getLocation(); this.lex(); boolean isGenerator = allowGenerator && this.eat(TokenType.MUL); boolean previousYield = this.allowYieldExpression; BindingIdentifier name; if (!this.match(TokenType.LPAREN)) { name = this.parseBindingIdentifier(); } else if (inDefault) { name = this.markLocation(startLocation, new BindingIdentifier("*default*")); } else { throw this.createUnexpected(this.lookahead); } this.allowYieldExpression = isGenerator; FormalParameters params = this.parseParams(); this.allowYieldExpression = isGenerator; FunctionBody body = this.parseFunctionBody(); this.allowYieldExpression = previousYield; return this.markLocation(startLocation, new FunctionDeclaration(name, isGenerator, params, body)); } @NotNull private Expression parseFunctionExpression(boolean allowGenerator) throws JsError { SourceLocation startLocation = this.getLocation(); this.lex(); Maybe name = Maybe.nothing(); boolean isGenerator = allowGenerator && this.eat(TokenType.MUL); boolean previousYield = this.allowYieldExpression; this.allowYieldExpression = isGenerator; if (!this.match(TokenType.LPAREN)) { name = Maybe.just(this.parseBindingIdentifier()); } FormalParameters params = this.parseParams(); this.allowYieldExpression = isGenerator; FunctionBody body = this.parseFunctionBody(); this.allowYieldExpression = previousYield; return this.markLocation(startLocation, new FunctionExpression(name, isGenerator, params, body)); } @NotNull private FormalParameters parseParams() throws JsError { SourceLocation paramsLocation = this.getLocation(); this.expect(TokenType.LPAREN); ArrayList items = new ArrayList<>(); BindingIdentifier rest = null; if (!this.match(TokenType.RPAREN)) { while (!this.eof()) { if (this.eat(TokenType.ELLIPSIS)) { rest = parseBindingIdentifier(); break; } items.add(this.parseParam()); if (this.match(TokenType.RPAREN)) { break; } this.expect(TokenType.COMMA); } } this.expect(TokenType.RPAREN); return this.markLocation(paramsLocation, new FormalParameters(ImmutableList.from(items), Maybe.fromNullable(rest))); } @NotNull private BindingBindingWithDefault parseParam() throws JsError { boolean previousInParameter = this.inParameter; this.inParameter = true; BindingBindingWithDefault param = this.parseBindingElement(); this.inParameter = previousInParameter; return param; } @NotNull private BindingBindingWithDefault parseBindingElement() throws JsError { SourceLocation startLocation = this.getLocation(); Binding binding = this.parseBindingTarget(); BindingBindingWithDefault bbwd = binding; if (this.eat(TokenType.ASSIGN)) { boolean previousYieldExpression = this.allowYieldExpression; Either init = this.parseAssignmentExpression(); bbwd = this.markLocation(startLocation, new BindingWithDefault(binding, init.left().just())); this.allowYieldExpression = previousYieldExpression; } return bbwd; } private boolean isValidSimpleAssignmentTarget(Node node) { return (node instanceof IdentifierExpression || node instanceof ComputedMemberExpression || node instanceof StaticMemberExpression); } @NotNull private Statement parseStatement() throws JsError { SourceLocation startLocation = this.getLocation(); Statement stmt = this.isolateCoverGrammar(this::parseStatementHelper); return this.markLocation(startLocation, stmt); } @NotNull private Statement parseStatementHelper() throws JsError { if (this.eof()) { throw this.createUnexpected(this.lookahead); } switch (this.lookahead.type) { case SEMICOLON: return this.parseEmptyStatement(); case BREAK: return this.parseBreakStatement(); case CONTINUE: return this.parseContinueStatement(); case DEBUGGER: return this.parseDebuggerStatement(); case DO: return this.parseDoWhileStatement(); case LPAREN: return this.parseExpressionStatement(); case LBRACE: return this.parseBlockStatement(); case IF: return this.parseIfStatement(); case FOR: return this.parseForStatement(); case RETURN: return this.parseReturnStatement(); case SWITCH: return this.parseSwitchStatement(); case THROW: return this.parseThrowStatement(); case WHILE: return this.parseWhileStatement(); case WITH: return this.parseWithStatement(); case TRY: return this.parseTryStatement(); case VAR: return this.parseVariableDeclarationStatement(); case FUNCTION: case CLASS: throw this.createUnexpected(this.lookahead); default: { if (this.lookaheadLexicalDeclaration()) { throw this.createUnexpected(this.lookahead); } Expression expr = this.parseExpression().left().just(); if (expr instanceof IdentifierExpression && this.eat(TokenType.COLON)) { Statement labeledBody = this.match(TokenType.FUNCTION) ? this.parseFunctionDeclaration(false, false) : this.parseStatement(); return new LabeledStatement(((IdentifierExpression) expr).getName(), labeledBody); } else { this.consumeSemicolon(); return new ExpressionStatement(expr); } } } } @NotNull private Statement parseForStatement() throws JsError { this.lex(); this.expect(TokenType.LPAREN); Maybe test = Maybe.nothing(); Maybe right = Maybe.nothing(); if (this.eat(TokenType.SEMICOLON)) { if (!this.match(TokenType.SEMICOLON)) { test = this.parseExpression().left(); } this.expect(TokenType.SEMICOLON); if (!this.match(TokenType.RPAREN)) { right = this.parseExpression().left(); } return new ForStatement(Maybe.nothing(), test, right, this.getIteratorStatementEpilogue()); } else { boolean startsWithLet = this.match(TokenType.LET); boolean isForDecl = this.lookaheadLexicalDeclaration(); SourceLocation leftLocation = this.getLocation(); if (this.match(TokenType.VAR) || isForDecl) { boolean previousAllowIn = this.allowIn; this.allowIn = false; VariableDeclarationExpression init = this.parseVariableDeclaration(false); this.allowIn = previousAllowIn; if (((VariableDeclaration) init).declarators.length == 1 && (this.match((TokenType.IN)) || this.matchContextualKeyword("of"))) { if (this.match(TokenType.IN)) { if (!(((VariableDeclaration) init).declarators.index(0).just().init).equals(Maybe.nothing())) { throw this.createError(ErrorMessages.INVALID_VAR_INIT_FOR_IN); } this.lex(); right = this.parseExpression().left(); Statement body = this.getIteratorStatementEpilogue(); return new ForInStatement((VariableDeclarationBinding) init, right.just(), body); } else { if (!(((VariableDeclaration) init).declarators.index(0).just().init).equals(Maybe.nothing())) { throw this.createError(ErrorMessages.INVALID_VAR_INIT_FOR_OF); } this.lex(); right = this.parseAssignmentExpression().left(); Statement body = this.getIteratorStatementEpilogue(); return new ForOfStatement((VariableDeclarationBinding) init, right.just(), body); } } else { this.expect(TokenType.SEMICOLON); if (!this.match(TokenType.SEMICOLON)) { test = this.parseExpression().left(); } this.expect(TokenType.SEMICOLON); if (!this.match(TokenType.RPAREN)) { right = this.parseExpression().left(); } return new ForStatement(Maybe.just(init), test, right, this.getIteratorStatementEpilogue()); } } else { boolean previousAllowIn = this.allowIn; this.allowIn = false; Either fromParseAssignmentOrBinding = this.parseAssignmentExpressionOrBindingElement(); Expression expr; if (fromParseAssignmentOrBinding.isLeft()) { expr = fromParseAssignmentOrBinding.left().just(); } else { throw this.createError(ErrorMessages.ILLEGAL_PROPERTY); } this.allowIn = previousAllowIn; if (this.isAssignmentTarget && !(expr instanceof AssignmentExpression) && (this.match(TokenType.IN) || this.matchContextualKeyword("of"))) { if (startsWithLet && this.matchContextualKeyword("of")) { throw this.createError(ErrorMessages.INVALID_LHS_IN_FOR_OF); } if (this.match(TokenType.IN)) { this.lex(); right = this.parseExpression().left(); return new ForInStatement(this.transformDestructuring(expr), right.just(), this.getIteratorStatementEpilogue()); } else { this.lex(); right = this.parseExpression().left(); return new ForOfStatement(this.transformDestructuring(expr), right.just(), this.getIteratorStatementEpilogue()); } } else { if (this.firstExprError != null) { throw this.firstExprError; } while (this.eat(TokenType.COMMA)) { Expression rhs = this.parseAssignmentExpression().left().just(); expr = this.markLocation(leftLocation, new BinaryExpression(BinaryOperator.Sequence, expr, rhs)); } if (this.match(TokenType.IN)) { throw this.createError(ErrorMessages.INVALID_LHS_IN_FOR_IN); } if (this.matchContextualKeyword("of")) { throw this.createError(ErrorMessages.INVALID_LHS_IN_FOR_OF); } this.expect(TokenType.SEMICOLON); if (!this.match(TokenType.SEMICOLON)) { test = this.parseExpression().left(); } this.expect(TokenType.SEMICOLON); if (!this.match(TokenType.RPAREN)) { right = this.parseExpression().left(); } return new ForStatement(Maybe.just(expr), test, right, this.getIteratorStatementEpilogue()); } } } } @NotNull private BindingProperty transformDestructuring(ObjectProperty objectProperty) throws JsError { if (objectProperty instanceof DataProperty) { DataProperty dataProperty = (DataProperty) objectProperty; return new BindingPropertyProperty(dataProperty.name, this.transformDestructuringWithDefault(dataProperty.expression)); } else if (objectProperty instanceof ShorthandProperty) { ShorthandProperty shorthandProperty = (ShorthandProperty) objectProperty; return new BindingPropertyIdentifier(new BindingIdentifier(shorthandProperty.name), Maybe.nothing()); } throw this.createError(ErrorMessages.INVALID_LHS_IN_ASSIGNMENT); } // TODO: preserve location information in transformDestructuring() functions by implementing copyLocation() @NotNull private Binding transformDestructuring(Expression node) throws JsError { if (node instanceof ObjectExpression) { ObjectExpression objectExpression = (ObjectExpression) node; ArrayList properties = new ArrayList<>(); for (ObjectProperty p : objectExpression.properties) { properties.add(this.transformDestructuring(p)); } return new ObjectBinding(ImmutableList.from(properties)); } else if (node instanceof ArrayExpression) { ArrayExpression arrayExpression = (ArrayExpression) node; Maybe last = Maybe.join(arrayExpression.elements.maybeLast()); ImmutableList> elements = arrayExpression.elements; ArrayList> newElements = new ArrayList<>(); if (last.isJust() && last.just() instanceof SpreadElement) { SpreadElement spreadElement = (SpreadElement) last.just(); for (Maybe maybeBbwd : ((NonEmptyImmutableList>) elements).init()) { if (maybeBbwd.isJust()) { newElements.add(Maybe.just(this.transformDestructuringWithDefault((Expression) maybeBbwd.just()))); } else { newElements.add(Maybe.nothing()); } } return new ArrayBinding(ImmutableList.from(newElements), Maybe.just(this.transformDestructuring(spreadElement.expression))); } else { for (Maybe maybeBbwd : elements) { if (maybeBbwd.isJust()) { newElements.add(Maybe.just(this.transformDestructuringWithDefault((Expression) maybeBbwd.just()))); } else { newElements.add(Maybe.nothing()); } } return new ArrayBinding(ImmutableList.from(newElements), Maybe.nothing()); } } else if (node instanceof IdentifierExpression) { return new BindingIdentifier(((IdentifierExpression) node).name); } else if (node instanceof ComputedMemberExpression || node instanceof StaticMemberExpression) { return (Binding) node; } throw this.createError(ErrorMessages.INVALID_LHS_IN_ASSIGNMENT); } @NotNull private BindingIdentifier transformDestructuring(StaticPropertyName property) { return new BindingIdentifier(property.value); } @NotNull private BindingBindingWithDefault transformDestructuringWithDefault(Expression node) throws JsError { if (node instanceof AssignmentExpression) { AssignmentExpression assignmentExpression = (AssignmentExpression) node; return new BindingWithDefault(this.transformDestructuring(assignmentExpression.binding), assignmentExpression.expression); } return this.transformDestructuring(node); } @NotNull private Binding transformDestructuring(Binding b) { return b; } private boolean matchContextualKeyword(String keyword) { return this.lookahead.type == TokenType.IDENTIFIER && keyword.equals(this.lookahead.toString()); } @NotNull private Statement parseSwitchStatement() throws JsError { this.lex(); this.expect(TokenType.LPAREN); Expression discriminant = this.parseExpression().left().just(); this.expect(TokenType.RPAREN); this.expect(TokenType.LBRACE); if (this.eat(TokenType.RBRACE)) { return new SwitchStatement(discriminant, ImmutableList.nil()); } ImmutableList cases = this.parseSwitchCases(); if (this.match(TokenType.DEFAULT)) { SwitchDefault defaultCase = this.parseSwitchDefault(); ImmutableList postDefaultCases = this.parseSwitchCases(); if (this.match(TokenType.DEFAULT)) { throw this.createError(ErrorMessages.MULTIPLE_DEFAULTS_IN_SWITCH); } this.expect(TokenType.RBRACE); return new SwitchStatementWithDefault(discriminant, cases, defaultCase, postDefaultCases); } else { this.expect(TokenType.RBRACE); return new SwitchStatement(discriminant, cases); } } @NotNull private ImmutableList parseSwitchCases() throws JsError { ArrayList result = new ArrayList<>(); while (!(this.eof() || this.match(TokenType.RBRACE) || this.match(TokenType.DEFAULT))) { result.add(this.parseSwitchCase()); } return ImmutableList.from(result); } @NotNull private SwitchCase parseSwitchCase() throws JsError { SourceLocation startLocation = this.getLocation(); this.expect(TokenType.CASE); return markLocation(startLocation, new SwitchCase(this.parseExpression().left().just(), this.parseSwitchCaseBody())); } @NotNull private ImmutableList parseSwitchCaseBody() throws JsError { this.expect(TokenType.COLON); return this.parseStatementListInSwitchCaseBody(); } @NotNull private ImmutableList parseStatementListInSwitchCaseBody() throws JsError { ArrayList result = new ArrayList<>(); while (!(this.eof() || this.match(TokenType.RBRACE) || this.match(TokenType.DEFAULT) || this.match(TokenType.CASE))) { result.add(this.parseStatementListItem()); } return ImmutableList.from(result); } @NotNull private SwitchDefault parseSwitchDefault() throws JsError { SourceLocation startLocation = this.getLocation(); this.expect(TokenType.DEFAULT); return this.markLocation(startLocation, new SwitchDefault(this.parseSwitchCaseBody())); } @NotNull private Statement parseDebuggerStatement() throws JsError { this.lex(); this.consumeSemicolon(); return new DebuggerStatement(); } @NotNull private Statement parseDoWhileStatement() throws JsError { this.lex(); Statement body = this.parseStatement(); this.expect(TokenType.WHILE); this.expect(TokenType.LPAREN); Expression test = this.parseExpression().left().just(); this.expect(TokenType.RPAREN); this.eat(TokenType.SEMICOLON); return new DoWhileStatement(test, body); } @NotNull private Statement parseContinueStatement() throws JsError { this.lex(); if (this.eat(TokenType.SEMICOLON) || this.hasLineTerminatorBeforeNext) { return new ContinueStatement(Maybe.nothing()); } Maybe label = Maybe.nothing(); if (this.match(TokenType.IDENTIFIER) || this.match(TokenType.YIELD) || this.match(TokenType.LET)) { label = Maybe.just(this.parseIdentifier()); } this.consumeSemicolon(); return new ContinueStatement(label); } @NotNull private Statement parseBreakStatement() throws JsError { this.lex(); if (this.eat(TokenType.SEMICOLON) || this.hasLineTerminatorBeforeNext) { return new BreakStatement(Maybe.nothing()); } Maybe label = Maybe.nothing(); if (this.match(TokenType.IDENTIFIER) || this.match(TokenType.YIELD) || this.match(TokenType.LET)) { label = Maybe.just(this.parseIdentifier()); } this.consumeSemicolon(); return new BreakStatement(label); } @NotNull private Statement parseTryStatement() throws JsError { this.lex(); Block body = this.parseBlock(); if (this.match(TokenType.CATCH)) { CatchClause catchClause = this.parseCatchClause(); if (this.eat(TokenType.FINALLY)) { Block finalizer = this.parseBlock(); return new TryFinallyStatement(body, Maybe.just(catchClause), finalizer); } return new TryCatchStatement(body, catchClause); } if (this.eat(TokenType.FINALLY)) { Block finalizer = this.parseBlock(); return new TryFinallyStatement(body, Maybe.nothing(), finalizer); } else { throw this.createError(ErrorMessages.NO_CATCH_OR_FINALLY); } } @NotNull private CatchClause parseCatchClause() throws JsError { SourceLocation startLocation = this.getLocation(); this.lex(); this.expect(TokenType.LPAREN); if (this.match(TokenType.RPAREN) || this.match(TokenType.LPAREN)) { throw this.createUnexpected(this.lookahead); } Binding binding = this.parseBindingTarget(); this.expect(TokenType.RPAREN); Block body = this.parseBlock(); return this.markLocation(startLocation, new CatchClause(binding, body)); } @NotNull private Statement parseThrowStatement() throws JsError { this.lex(); if (this.hasLineTerminatorBeforeNext) { throw this.createErrorWithLocation(this.getLocation(), ErrorMessages.NEWLINE_AFTER_THROW); } Expression expression = this.parseExpression().left().just(); this.consumeSemicolon(); return new ThrowStatement(expression); } @NotNull private Statement parseReturnStatement() throws JsError { if (!this.inFunctionBody) { throw this.createError(ErrorMessages.ILLEGAL_RETURN); } this.lex(); if (this.hasLineTerminatorBeforeNext) { return new ReturnStatement(Maybe.nothing()); } Maybe expression = Maybe.nothing(); if (!this.match(TokenType.SEMICOLON) && !this.match(TokenType.RBRACE) && !this.eof()) { expression = this.parseExpression().left(); } this.consumeSemicolon(); return new ReturnStatement(expression); } @NotNull private Statement parseEmptyStatement() throws JsError { this.lex(); return new EmptyStatement(); } @NotNull private Statement parseWhileStatement() throws JsError { this.lex(); this.expect(TokenType.LPAREN); Expression test = this.parseExpression().left().just(); Statement body = this.getIteratorStatementEpilogue(); return new WhileStatement(test, body); } @NotNull private Statement parseWithStatement() throws JsError { this.lex(); this.expect(TokenType.LPAREN); Expression test = this.parseExpression().left().just(); Statement body = this.getIteratorStatementEpilogue(); return new WithStatement(test, body); } @NotNull private Statement getIteratorStatementEpilogue() throws JsError { this.expect(TokenType.RPAREN); return this.parseStatement(); } @NotNull private Statement parseBlockStatement() throws JsError { return new BlockStatement(this.parseBlock()); } @NotNull private Block parseBlock() throws JsError { SourceLocation startLocation = this.getLocation(); this.expect(TokenType.LBRACE); ArrayList body = new ArrayList<>(); while (!this.match(TokenType.RBRACE)) { body.add(parseStatementListItem()); } this.expect(TokenType.RBRACE); return this.markLocation(startLocation, new Block(ImmutableList.from(body))); } @NotNull private Statement parseExpressionStatement() throws JsError { Expression expr = this.parseExpression().left().just(); this.consumeSemicolon(); return new ExpressionStatement(expr); } @NotNull private Either parseExpression() throws JsError { SourceLocation startLocation = this.getLocation(); Either left = this.parseAssignmentExpression(); if (this.match(TokenType.COMMA)) { while (!this.eof()) { if (!this.match(TokenType.COMMA)) { break; } this.lex(); Expression right = this.parseAssignmentExpression().left().just(); left = Either.left(this.markLocation(startLocation, new BinaryExpression(BinaryOperator.Sequence, left.left().just(), right))); } } return left; } @NotNull private T isolateCoverGrammar(ExceptionalSupplier parser) throws JsError { boolean oldIsBindingElement = this.isBindingElement; boolean oldIsAssignmentTarget = this.isAssignmentTarget; JsError oldFirstExprError = this.firstExprError; T result; this.isBindingElement = this.isAssignmentTarget = true; this.firstExprError = null; result = parser.get(); if (this.firstExprError != null) { throw this.firstExprError; } this.isBindingElement = oldIsBindingElement; this.isAssignmentTarget = oldIsAssignmentTarget; this.firstExprError = oldFirstExprError; return result; } @NotNull private T inheritCoverGrammar(ExceptionalSupplier parser) throws JsError { boolean oldIsBindingElement = this.isBindingElement; boolean oldIsAssignmentTarget = this.isAssignmentTarget; JsError oldFirstExprError = this.firstExprError; T result; this.isBindingElement = this.isAssignmentTarget = true; this.firstExprError = null; result = parser.get(); this.isBindingElement = this.isBindingElement && oldIsBindingElement; this.isAssignmentTarget = this.isAssignmentTarget && oldIsAssignmentTarget; this.firstExprError = oldFirstExprError != null ? oldFirstExprError : this.firstExprError; return result; } @NotNull private Either parseAssignmentExpression() throws JsError { return this.isolateCoverGrammar(this::parseAssignmentExpressionOrBindingElement); } @NotNull private Either parseAssignmentExpressionOrBindingElement() throws JsError { SourceLocation startLocation = this.getLocation(); if (this.allowYieldExpression && this.match(TokenType.YIELD)) { this.isBindingElement = this.isAssignmentTarget = false; return Either.left(this.parseYieldExpression()); } Either expr; if (this.match(TokenType.IDENTIFIER) || this.match(TokenType.YIELD) || this.match(TokenType.LET)) { expr = this.parseConditionalExpression(); if (!this.hasLineTerminatorBeforeNext && this.match(TokenType.ARROW)) { this.isBindingElement = this.isAssignmentTarget = false; this.firstExprError = null; ArrayList params = new ArrayList<>(); params.add(this.transformDestructuring(expr.left().just())); return Either.left(this.parseArrowExpressionTail(params, Maybe.nothing(), startLocation)); } } else { expr = this.parseConditionalExpression(); } boolean isAssignmentOperator = false; Token operator = this.lookahead; switch (operator.type) { case ASSIGN_BIT_OR: case ASSIGN_BIT_XOR: case ASSIGN_BIT_AND: case ASSIGN_SHL: case ASSIGN_SHR: case ASSIGN_SHR_UNSIGNED: case ASSIGN_ADD: case ASSIGN_SUB: case ASSIGN_MUL: case ASSIGN_DIV: case ASSIGN_MOD: isAssignmentOperator = true; break; } Binding assignmentTarget; if (isAssignmentOperator) { if (expr.isRight() || !this.isAssignmentTarget || !isValidSimpleAssignmentTarget(expr.left().just())) { throw this.createError(ErrorMessages.INVALID_LHS_IN_ASSIGNMENT); } assignmentTarget = this.transformDestructuring(expr.left().just()); } else if (operator.type == TokenType.ASSIGN) { if (!this.isAssignmentTarget) { throw this.createError(ErrorMessages.INVALID_LHS_IN_ASSIGNMENT); } if (expr.isLeft()) { assignmentTarget = this.transformDestructuring(expr.left().just()); } else { assignmentTarget = expr.right().just(); } } else { return expr; } this.lex(); Either rhs = this.parseAssignmentExpression(); if (rhs.isRight()) { throw this.createError(ErrorMessages.UNEXPECTED_OBJECT_BINDING); } this.firstExprError = null; if (operator.type == TokenType.ASSIGN) { return Either.left(this.markLocation(startLocation, new AssignmentExpression(assignmentTarget, rhs.left().just()))); } else { CompoundAssignmentOperator compoundAssignmentOperator = this.lookupCompoundAssignmentOperator(operator); if (compoundAssignmentOperator != null) { this.isBindingElement = this.isAssignmentTarget = false; return Either.left(this.markLocation(startLocation, new CompoundAssignmentExpression(compoundAssignmentOperator, (BindingIdentifierMemberExpression) assignmentTarget, rhs.left().just()))); } else { throw this.createError("should not be here", 0, 0, 0); } } } @NotNull private Expression parseYieldExpression() throws JsError { SourceLocation startLocation = this.getLocation(); this.lex(); if (this.hasLineTerminatorBeforeNext) { return this.markLocation(startLocation, new YieldExpression(Maybe.nothing())); } boolean isGenerator = this.eat(TokenType.MUL); Maybe expr = Maybe.nothing(); if (isGenerator || this.lookaheadAssignmentExpression()) { expr = this.parseAssignmentExpression().left(); } if (isGenerator) { return this.markLocation(startLocation, new YieldGeneratorExpression(expr.just())); } else { return this.markLocation(startLocation, new YieldExpression(expr)); } } private boolean lookaheadAssignmentExpression() { switch (this.lookahead.type) { case ADD: case ASSIGN_DIV: case CLASS: case DEC: case DIV: case FALSE_LITERAL: case FUNCTION: case IDENTIFIER: case INC: case LET: case LBRACE: case LBRACK: case LPAREN: case NEW: case NOT: case NULL_LITERAL: case NUMBER: case STRING: case SUB: case SUPER: case THIS: case TRUE_LITERAL: case YIELD: case TEMPLATE: return true; } return false; } @NotNull private Either parseConditionalExpression() throws JsError { SourceLocation startLocation = this.getLocation(); Either test = this.parseBinaryExpression(); if (this.firstExprError != null) { return test; } if (this.eat(TokenType.CONDITIONAL)) { if (test.isLeft()) { this.isBindingElement = this.isAssignmentTarget = false; boolean previousAllowIn = this.allowIn; this.allowIn = true; Expression consequent = this.isolateCoverGrammar(this::parseAssignmentExpression).left().just(); this.allowIn = previousAllowIn; this.expect(TokenType.COLON); Expression alternate = this.isolateCoverGrammar(this::parseAssignmentExpression).left().just(); return Either.left(this.markLocation(startLocation, new ConditionalExpression(test.left().just(), consequent, alternate))); } else { throw this.createError(ErrorMessages.UNEXPECTED_OBJECT_BINDING); } } return test; } @NotNull private Either parseBinaryExpression() throws JsError { SourceLocation startLocation = this.getLocation(); Either left = this.parseUnaryExpression(); BinaryOperator operator = this.lookupBinaryOperator(this.lookahead); if (operator == null) { return left; } this.isBindingElement = this.isAssignmentTarget = false; if (left.isLeft()) { this.lex(); ImmutableList stack = ImmutableList.nil(); stack = stack.cons(new ExprStackItem(startLocation, left.left().just(), operator)); startLocation = this.getLocation(); Either expr = this.isolateCoverGrammar(this::parseUnaryExpression); operator = this.lookupBinaryOperator(this.lookahead); while (operator != null) { Precedence precedence = operator.getPrecedence(); // Reduce: make a binary expression from the three topmost entries. while ((stack.isNotEmpty()) && (precedence.ordinal() <= ((NonEmptyImmutableList) stack).head.precedence)) { ExprStackItem stackItem = ((NonEmptyImmutableList) stack).head; BinaryOperator stackOperator = stackItem.operator; left = Either.left(stackItem.left); stack = ((NonEmptyImmutableList) stack).tail(); startLocation = stackItem.startLocation; expr = Either.left(this.markLocation(stackItem.startLocation, new BinaryExpression(stackOperator, left.left().just(), expr.left().just()))); } // Shift. this.lex(); stack = stack.cons(new ExprStackItem(startLocation, expr.left().just(), operator)); startLocation = this.getLocation(); expr = this.isolateCoverGrammar(this::parseUnaryExpression); operator = this.lookupBinaryOperator(this.lookahead); } // Final reduce to clean-up the stack. return Either.left(stack.foldLeft( (expr1, stackItem) -> this.markLocation( stackItem.startLocation, new BinaryExpression(stackItem.operator, stackItem.left, expr1)), expr.left().just())); } else { throw this.createError(ErrorMessages.UNEXPECTED_OBJECT_BINDING); } } @NotNull private Either parseUnaryExpression() throws JsError { if (this.lookahead.type.klass != TokenClass.Punctuator && this.lookahead.type.klass != TokenClass.Keyword) { return this.parseUpdateExpression(); } SourceLocation startLocation = this.getLocation(); Token operatorToken = this.lookahead; if (!this.isPrefixOperator(operatorToken)) { return this.parseUpdateExpression(); } this.lex(); this.isBindingElement = this.isAssignmentTarget = false; Either operand = this.isolateCoverGrammar(this::parseUnaryExpression); if (operand.isLeft()) { UpdateOperator updateOperator = this.lookupUpdateOperator(operatorToken); if (updateOperator != null) { return Either.left(createUpdateExpression(startLocation, operand.left().just(), updateOperator, true)); } UnaryOperator operator = this.lookupUnaryOperator(operatorToken); assert operator != null; return Either.left(new UnaryExpression(operator, operand.left().just())); } else { throw this.createError(ErrorMessages.UNEXPECTED_OBJECT_BINDING); } } @NotNull private Either parseUpdateExpression() throws JsError { SourceLocation startLocation = this.getLocation(); Either operand = this.parseLeftHandSideExpression(true).mapLeft(x -> (Expression) x); if (this.firstExprError != null || this.hasLineTerminatorBeforeNext) { return operand; } UpdateOperator operator = this.lookupUpdateOperator(this.lookahead); if (operator == null) { return operand; } this.lex(); if (operand.isLeft()) { this.isBindingElement = this.isAssignmentTarget = false; return Either.left(createUpdateExpression(startLocation, operand.left().just(), operator, false)); } else { throw this.createError(ErrorMessages.UNEXPECTED_OBJECT_BINDING); } } @NotNull private Expression createUpdateExpression(@NotNull SourceLocation startLocation, @NotNull Expression operand, @NotNull UpdateOperator operator, boolean isPrefix) throws JsError { BindingIdentifierMemberExpression restrictedOperand; if (operand instanceof MemberExpression) { restrictedOperand = (MemberExpression) operand; } else if (operand instanceof IdentifierExpression) { String name = ((IdentifierExpression) operand).name; restrictedOperand = new BindingIdentifier(name); } else { throw this.createError("Increment/decrement target must be an identifier or member expression"); } return this.markLocation(startLocation, new UpdateExpression(isPrefix, operator, restrictedOperand)); } @NotNull private Expression parseNumericLiteral() throws JsError { SourceLocation startLocation = this.getLocation(); Token token = this.lex(); assert token instanceof NumericLiteralToken; if (((NumericLiteralToken) token).octal && this.strict) { if (((NumericLiteralToken) token).noctal) { throw this.createErrorWithLocation(startLocation, "Unexpected noctal integer literal"); } else { throw this.createErrorWithLocation(startLocation, "Unexpected legacy octal integer literal"); } } if (Double.isInfinite(((NumericLiteralToken) token).value)) { return this.markLocation(startLocation, new LiteralInfinityExpression()); } else { return this.markLocation(startLocation, new LiteralNumericExpression(((NumericLiteralToken) token).value)); } } @NotNull private Either parseLeftHandSideExpression(boolean allowCall) throws JsError { SourceLocation startLocation = this.getLocation(); boolean previousAllowIn = this.allowIn; this.allowIn = allowCall; Either expr; Token token = this.lookahead; if (this.eat(TokenType.SUPER)) { this.isBindingElement = this.isAssignmentTarget = false; expr = Either.left(this.markLocation(startLocation, new Super())); if (this.match(TokenType.LPAREN)) { if (allowCall) { expr = Either.left(this.markLocation(startLocation, new CallExpression(expr.left().just(), this.parseArgumentList()))); } } else if (this.match(TokenType.LBRACK)) { expr = Either.left(this.markLocation(startLocation, new ComputedMemberExpression(this.parseComputedMember().left().just(), expr.left().just()))); this.isAssignmentTarget = true; } else if (this.match(TokenType.PERIOD)) { expr = Either.left(this.markLocation(startLocation, new StaticMemberExpression(this.parseStaticMember(), expr.left().just()))); this.isAssignmentTarget = true; } else { throw this.createUnexpected(token); } } else if (this.match(TokenType.NEW)) { this.isBindingElement = this.isAssignmentTarget = false; expr = Either.left(this.parseNewExpression()); } else { expr = this.parsePrimaryExpression().mapLeft(x -> (ExpressionSuper) x); if (this.firstExprError != null) { return expr; } } while (true) { if (allowCall && this.match((TokenType.LPAREN))) { this.isBindingElement = this.isAssignmentTarget = false; expr = Either.left(this.markLocation(startLocation, new CallExpression(expr.left().just(), this.parseArgumentList()))); } else if (this.match(TokenType.TEMPLATE)) { this.isBindingElement = this.isAssignmentTarget = false; expr = Either.left(this.markLocation(startLocation, new TemplateExpression(Maybe.just((Expression) expr.left().just()), this.parseTemplateElements()))); } else if (this.match(TokenType.LBRACK)) { this.isBindingElement = false; this.isAssignmentTarget = true; expr = Either.left(this.markLocation(startLocation, new ComputedMemberExpression(this.parseComputedMember().left().just(), expr.left().just()))); } else if (this.match(TokenType.PERIOD)) { this.isBindingElement = false; this.isAssignmentTarget = true; expr = Either.left(this.markLocation(startLocation, new StaticMemberExpression(this.parseStaticMember(), expr.left().just()))); } else { break; } } this.allowIn = previousAllowIn; if (expr.isLeft()) { return Either.left(expr.left().just()); } else { return Either.right(expr.right().just()); } } @NotNull private String parseStaticMember() throws JsError { this.lex(); if (!this.isIdentifierName(this.lookahead.type.klass)) { throw this.createUnexpected(this.lookahead); } else { return this.lex().toString(); } } @NotNull private Either parseComputedMember() throws JsError { this.lex(); Either expr = this.parseExpression(); this.expect(TokenType.RBRACK); return expr; } @NotNull private ImmutableList parseTemplateElements() throws JsError { SourceLocation startLocation = this.getLocation(); Token token = this.lookahead; String nonTemplatePart; ArrayList result = new ArrayList<>(); if (((TemplateToken) token).tail) { this.lex(); nonTemplatePart = token.slice.subSequence(1, token.slice.length() - 1).toString(); result.add(this.markLocation(startLocation, new TemplateElement(nonTemplatePart))); return ImmutableList.from(result); } token = this.lex(); nonTemplatePart = token.slice.subSequence(1, token.slice.length() - 2).toString(); result.add(this.markLocation(startLocation, new TemplateElement(nonTemplatePart))); while (true) { result.add(this.parseExpression().left().just()); if (!this.match(TokenType.RBRACE)) { throw this.createILLEGAL(); } this.index = this.startIndex; this.line = this.startLine; this.lineStart = this.startLineStart; this.lookahead = this.scanTemplateElement(); startLocation = this.getLocation(); token = this.lex(); if (((TemplateToken) token).tail) { nonTemplatePart = token.slice.subSequence(1, token.slice.length() - 1).toString(); result.add(this.markLocation(startLocation, new TemplateElement(nonTemplatePart))); return ImmutableList.from(result); } else { nonTemplatePart = token.slice.subSequence(1, token.slice.length() - 2).toString(); result.add(this.markLocation(startLocation, new TemplateElement(nonTemplatePart))); } } } @NotNull private Expression parseNewExpression() throws JsError { SourceLocation startLocation = this.getLocation(); this.lex(); if (this.eat(TokenType.PERIOD)) { Token ident = this.expect(TokenType.IDENTIFIER); if ((!ident.toString().equals("target"))) { throw this.createUnexpected(ident); } return this.markLocation(startLocation, new NewTargetExpression()); } Either fromParseLeftHandSideExpression = this.isolateCoverGrammar( () -> this.parseLeftHandSideExpression(false)); if (fromParseLeftHandSideExpression.isLeft()) { ExpressionSuper callee = fromParseLeftHandSideExpression.left().just(); if (!(callee instanceof Expression)) { throw this.createUnexpected(this.lookahead); } return this.markLocation(startLocation, new NewExpression((Expression) callee, this.match(TokenType.LPAREN) ? this.parseArgumentList() : ImmutableList.nil())); } else { throw this.createError(ErrorMessages.UNEXPECTED_OBJECT_BINDING); } } @NotNull private ImmutableList parseArgumentList() throws JsError { this.lex(); ImmutableList args = this.parseArguments(); this.expect(TokenType.RPAREN); return args; } @NotNull private ImmutableList parseArguments() throws JsError { ArrayList result = new ArrayList<>(); while (true) { if (this.match(TokenType.RPAREN) || this.eof()) { return ImmutableList.from(result); } SpreadElementExpression arg; if (this.eat(TokenType.ELLIPSIS)) { SourceLocation startLocation = this.getLocation(); arg = this.markLocation(startLocation, new SpreadElement(this.parseAssignmentExpression().left().just())); } else { arg = this.parseAssignmentExpression().left().just(); } result.add(arg); if (!this.eat(TokenType.COMMA)) { break; } } return ImmutableList.from(result); } @NotNull private Either parsePrimaryExpression() throws JsError { if (this.match(TokenType.LPAREN)) { return this.parseGroupExpression(); } SourceLocation startLocation = this.getLocation(); switch (this.lookahead.type) { case YIELD: if (this.allowYieldExpression) { throw this.createUnexpected(this.lookahead); } // falls through case LET: case IDENTIFIER: return Either.left(this.markLocation(startLocation, new IdentifierExpression(this.lex().toString()))); case TRUE_LITERAL: this.lex(); this.isBindingElement = this.isAssignmentTarget = false; return Either.left(this.markLocation(startLocation, new LiteralBooleanExpression(true))); case FALSE_LITERAL: this.lex(); this.isBindingElement = this.isAssignmentTarget = false; return Either.left(this.markLocation(startLocation, new LiteralBooleanExpression(false))); case NULL_LITERAL: this.lex(); this.isBindingElement = this.isAssignmentTarget = false; return Either.left(this.markLocation(startLocation, new LiteralNullExpression())); case FUNCTION: this.isBindingElement = this.isAssignmentTarget = false; return Either.left(this.markLocation(startLocation, this.parseFunctionExpression(true))); case NUMBER: this.isBindingElement = this.isAssignmentTarget = false; return Either.left(this.parseNumericLiteral()); case STRING: this.isBindingElement = this.isAssignmentTarget = false; return Either.left(this.parseStringLiteral()); case LBRACK: return this.parseArrayExpression(); case THIS: this.lex(); this.isBindingElement = this.isAssignmentTarget = false; return Either.left(new ThisExpression()); case LBRACE: return this.parseObjectExpression(); case CLASS: this.isBindingElement = this.isAssignmentTarget = false; return Either.left(this.parseClass()); case TEMPLATE: this.isBindingElement = this.isAssignmentTarget = false; return Either.left(this.markLocation(startLocation, new TemplateExpression(Maybe.nothing(), this.parseTemplateElements()))); case DIV: case ASSIGN_DIV: this.isBindingElement = this.isAssignmentTarget = false; this.lookahead = this.scanRegExp(this.match(TokenType.DIV) ? "/" : "/="); Token token = this.lex(); int lastSlash = ((RegularExpressionLiteralToken) token).getValueString().lastIndexOf("/"); String pattern = ((RegularExpressionLiteralToken) token).getValueString().substring(1, lastSlash); String flags = ((RegularExpressionLiteralToken) token).getValueString().substring(lastSlash + 1); return Either.left(this.markLocation(startLocation, new LiteralRegExpExpression(pattern, flags))); default: throw this.createUnexpected(this.lookahead); } } @NotNull private Expression parseStringLiteral() throws JsError { SourceLocation startLocation = this.getLocation(); Token token = this.lex(); assert token instanceof StringLiteralToken; if (((StringLiteralToken) token).octal != null && this.strict) { throw this.createErrorWithLocation(startLocation, "Unexpected legacy octal escape sequence: \\" + ((StringLiteralToken) token).octal); } return this.markLocation(startLocation, new LiteralStringExpression(token.getValueString().toString())); } @NotNull private Either parseArrayExpression() throws JsError { SourceLocation startLocation = this.getLocation(); this.lex(); ArrayList> exprs = new ArrayList<>(); ArrayList> bindings = new ArrayList<>(); Maybe rest = Maybe.nothing(); boolean allExpressionsSoFar = true; while (true) { if (this.match(TokenType.RBRACK)) { break; } if (this.eat(TokenType.COMMA)) { exprs.add(Maybe.nothing()); } else { SourceLocation elementLocation = this.getLocation(); if (this.eat(TokenType.ELLIPSIS)) { Either expr = this.parseAssignmentExpressionOrBindingElement().mapLeft(x -> (SpreadElementExpression) x); //TODO inherit cover grammar if (expr.isLeft()) { exprs.add(Maybe.just(this.markLocation(elementLocation, new SpreadElement((Expression) expr.left().just())))); } else { allExpressionsSoFar = false; for (Maybe e : exprs) { if (e.isNothing()) { bindings.add(Maybe.nothing()); } else { SpreadElementExpression r = e.just(); if (r instanceof SpreadElement) { throw this.createError(ErrorMessages.INVALID_REST); } bindings.add(Maybe.just(transformDestructuring((Expression) r))); } } rest = expr.right(); break; } if (!this.isAssignmentTarget && this.firstExprError != null) { throw this.firstExprError; } if (!this.match(TokenType.RBRACK)) { this.isBindingElement = this.isAssignmentTarget = false; } } else { Either expr = this.parseAssignmentExpressionOrBindingElement(); //TODO inherit cover grammar if (allExpressionsSoFar) { if (expr.isLeft()) { exprs.add(expr.left().map(x -> (SpreadElementExpression) x)); } else { allExpressionsSoFar = false; for (Maybe e : exprs) { if (e.isNothing()) { bindings.add(Maybe.nothing()); } else { SpreadElementExpression r = e.just(); if (r instanceof SpreadElement) { rest = Maybe.just(this.transformDestructuring(((SpreadElement) r).expression)); break; } else { bindings.add(Maybe.just(this.transformDestructuring((Expression) r))); } } } bindings.add(expr.right().map(x -> (BindingBindingWithDefault) x)); } } else { if (expr.isLeft()) { bindings.add(Maybe.just(this.transformDestructuring(expr.left().just()))); } else { bindings.add(expr.right().map(x -> (BindingBindingWithDefault) x)); } } if (!this.isAssignmentTarget && this.firstExprError != null) { throw this.firstExprError; } } if (!this.match(TokenType.RBRACK)) { this.expect(TokenType.COMMA); } } } this.expect(TokenType.RBRACK); if (allExpressionsSoFar) { return Either.left(this.markLocation(startLocation, new ArrayExpression(ImmutableList.from(exprs)))); } else { return Either.right(this.markLocation(startLocation, new ArrayBinding(ImmutableList.from(bindings), rest))); } } @NotNull private Either parseObjectExpression() throws JsError { SourceLocation startLocation = this.getLocation(); this.lex(); ArrayList objectProperties = new ArrayList<>(); ArrayList bindingProperties = new ArrayList<>(); boolean allExpressionsSoFar = true; while (!this.match(TokenType.RBRACE)) { Either fromParsePropertyDefinition = this.parsePropertyDefinition(); if (allExpressionsSoFar) { if (fromParsePropertyDefinition.isLeft()) { objectProperties.add(fromParsePropertyDefinition.left().just()); } else { allExpressionsSoFar = false; for (ObjectProperty objectProperty : objectProperties) { bindingProperties.add(this.transformDestructuring(objectProperty)); } bindingProperties.add(fromParsePropertyDefinition.right().just()); } } else { if (fromParsePropertyDefinition.isLeft()) { bindingProperties.add(this.transformDestructuring(fromParsePropertyDefinition.left().just())); } else { bindingProperties.add(fromParsePropertyDefinition.right().just()); } } if (!this.match(TokenType.RBRACE)) { this.expect(TokenType.COMMA); } } this.expect(TokenType.RBRACE); if (allExpressionsSoFar) { ObjectExpression toReturn = new ObjectExpression(ImmutableList.from(objectProperties)); this.markLocation(startLocation, toReturn); return Either.left(toReturn); } else { ObjectBinding toReturn = new ObjectBinding(ImmutableList.from(bindingProperties)); this.markLocation(startLocation, toReturn); return Either.right(toReturn); } } @NotNull private Either parseGroupExpression() throws JsError { SourceLocation startLocation = this.getLocation(); this.expect(TokenType.LPAREN); if (this.eat(TokenType.RPAREN)) { this.isBindingElement = this.isAssignmentTarget = false; return Either.left(this.parseArrowExpressionTail(new ArrayList<>(), Maybe.nothing(), startLocation)); } else if (this.eat(TokenType.ELLIPSIS)) { Maybe rest = Maybe.just(this.parseBindingIdentifier()); this.expect(TokenType.RPAREN); this.isBindingElement = this.isAssignmentTarget = false; return Either.left(this.parseArrowExpressionTail(new ArrayList<>(), rest, startLocation)); } Either group = this.inheritCoverGrammar(this::parseAssignmentExpressionOrBindingElement); ArrayList params = new ArrayList<>(); if (this.isBindingElement) { if (group.isLeft()) { params.add(this.transformDestructuringWithDefault(group.left().just())); } else { params.add(group.right().just()); } } boolean mustBeArrowParameterList = false; while (this.eat(TokenType.COMMA)) { this.isAssignmentTarget = false; if (this.match(TokenType.ELLIPSIS)) { if (!this.isBindingElement) { throw this.createUnexpected(this.lookahead); } this.lex(); Maybe rest = Maybe.just(this.parseBindingIdentifier()); this.expect(TokenType.RPAREN); return Either.left(this.parseArrowExpressionTail(params, rest, startLocation)); } if (mustBeArrowParameterList) { // Can be only binding elements. BindingBindingWithDefault binding = this.parseBindingElement(); params.add(binding); } else { // Can be either binding element or assignment target. Either expr = this.inheritCoverGrammar(this::parseAssignmentExpressionOrBindingElement); if (this.isBindingElement) { if (expr.isLeft()) { params.add(this.transformDestructuringWithDefault(expr.left().just())); } else { params.add(expr.right().just()); } } if (this.firstExprError == null) { group = Either.left(this.markLocation(startLocation, new BinaryExpression(BinaryOperator.Sequence, group.left().just(), expr.left().just()))); } else { mustBeArrowParameterList = true; } } } this.expect(TokenType.RPAREN); if (!this.hasLineTerminatorBeforeNext && this.match(TokenType.ARROW) || mustBeArrowParameterList) { if (!this.isBindingElement) { throw this.createErrorWithLocation(startLocation, this.match(TokenType.ASSIGN) ? ErrorMessages.INVALID_LHS_IN_ASSIGNMENT : ErrorMessages.ILLEGAL_ARROW_FUNCTION_PARAMS); } this.isBindingElement = false; return Either.left(this.parseArrowExpressionTail(params, Maybe.nothing(), startLocation)); } else { // Ensure assignment pattern: this.isBindingElement = false; return group; } } @NotNull private Either parsePropertyDefinition() throws JsError { SourceLocation startLocation = this.getLocation(); Token token = this.lookahead; Either keyOrMethod = this.parseMethodDefinition(); if (keyOrMethod.isRight()) { this.isBindingElement = this.isAssignmentTarget = false; return Either.left(keyOrMethod.right().just()); } else if (keyOrMethod.isLeft()) { PropertyName propName = keyOrMethod.left().just(); if (propName instanceof StaticPropertyName) { StaticPropertyName staticPropertyName = (StaticPropertyName) propName; if (this.eat(TokenType.ASSIGN)) { Expression init = this.isolateCoverGrammar(this::parseAssignmentExpression).left().just(); this.firstExprError = this.createErrorWithLocation(startLocation, ErrorMessages.ILLEGAL_PROPERTY); BindingPropertyIdentifier toReturn = new BindingPropertyIdentifier(this.transformDestructuring(staticPropertyName), Maybe.just(init)); this.markLocation(startLocation, toReturn); return Either.right(toReturn); } if (!this.match(TokenType.COLON)) { if (token.type != TokenType.IDENTIFIER && token.type != TokenType.YIELD && token.type != TokenType.LET) { throw this.createUnexpected(token); } ShorthandProperty toReturn = new ShorthandProperty(staticPropertyName.value); this.markLocation(startLocation, toReturn); return Either.left(toReturn); } } } this.expect(TokenType.COLON); PropertyName name = keyOrMethod.left().just(); Either val = this.parseAssignmentExpressionOrBindingElement(); return val.map( expr -> { DataProperty toReturn = new DataProperty(expr, name); // TODO the fact that this is (val, name) and BindingPropertyProperty is (name, val) is very sad. this.markLocation(startLocation, toReturn); return toReturn; }, binding -> { BindingPropertyProperty toReturn = new BindingPropertyProperty(name, binding); this.markLocation(startLocation, toReturn); return toReturn; } ); } @NotNull private Either parseMethodDefinition() throws JsError { Token token = this.lookahead; SourceLocation startLocation = this.getLocation(); boolean isGenerator = this.eat(TokenType.MUL); Pair> fromParsePropertyName = this.parsePropertyName(); PropertyName name = fromParsePropertyName.a; if (!isGenerator && token.type == TokenType.IDENTIFIER) { String tokenName = token.toString(); if (tokenName.length() == 3) { // Property Assignment: Getter and Setter. if (tokenName.equals("get") && this.lookaheadPropertyName()) { name = this.parsePropertyName().a; this.expect(TokenType.LPAREN); this.expect(TokenType.RPAREN); FunctionBody body = this.parseFunctionBody(); return Either.right(this.markLocation(startLocation, new Getter(body, name))); } else if (tokenName.equals("set") && this.lookaheadPropertyName()) { name = this.parsePropertyName().a; this.expect(TokenType.LPAREN); BindingBindingWithDefault param = this.parseBindingElement(); this.expect(TokenType.RPAREN); boolean previousYield = this.allowYieldExpression; this.allowYieldExpression = false; FunctionBody body = this.parseFunctionBody(); this.allowYieldExpression = previousYield; return Either.right(this.markLocation(startLocation, new Setter(param, body, name))); } } } if (this.match(TokenType.LPAREN)) { boolean previousYield = this.allowYieldExpression; this.allowYieldExpression = isGenerator; FormalParameters params = this.parseParams(); this.allowYieldExpression = isGenerator; FunctionBody body = this.parseFunctionBody(); this.allowYieldExpression = previousYield; return Either.right(this.markLocation(startLocation, new Method(isGenerator, params, body, name))); } if (isGenerator && this.match(TokenType.COLON)) { throw this.createUnexpected(this.lookahead); } return Either.left(name); } private boolean lookaheadPropertyName() { switch (this.lookahead.type) { case NUMBER: case STRING: case LBRACK: return true; default: return this.isIdentifierName(this.lookahead.type.klass); } } @NotNull private Pair> parsePropertyName() throws JsError { Token token = this.lookahead; SourceLocation startLocation = this.getLocation(); if (this.eof()) { throw this.createUnexpected(token); } switch (token.type) { case STRING: String stringValue = ((LiteralStringExpression) this.parseStringLiteral()).value; return new Pair<>(this.markLocation(startLocation, new StaticPropertyName(stringValue)), Maybe.nothing()); case NUMBER: Expression numLiteral = this.parseNumericLiteral(); if (numLiteral instanceof LiteralInfinityExpression) { return new Pair<>(this.markLocation(startLocation, new StaticPropertyName("Infinity")), Maybe.nothing()); } else { double value = ((LiteralNumericExpression) numLiteral).value; return new Pair<>(this.markLocation(startLocation, new StaticPropertyName(D2A.d2a(value))), Maybe.nothing()); } case LBRACK: boolean previousYield = this.allowYieldExpression; this.lex(); Expression expr = this.parseAssignmentExpression().left().just(); this.expect(TokenType.RBRACK); this.allowYieldExpression = previousYield; return new Pair<>(this.markLocation(startLocation, new ComputedPropertyName(expr)), Maybe.nothing()); } String name = this.parseIdentifierName(); Maybe maybeBinding = Maybe.just(new BindingIdentifier(name)); return new Pair<>(this.markLocation(startLocation, new StaticPropertyName(name)), maybeBinding); // TODO mark location on binding part } @NotNull private String parseIdentifierName() throws JsError { if (this.isIdentifierName(this.lookahead.type.klass)) { return this.lex().toString(); } else { throw this.createUnexpected(this.lookahead); } } private boolean isIdentifierName(TokenClass klass) { return (klass.getName().equals("Identifier") || klass.getName().equals("Keyword") || klass.getName().equals("Boolean") || klass.getName().equals("Null") || klass.getName().equals("Yield")); } @NotNull private Expression parseClass() throws JsError { SourceLocation startLocation = this.getLocation(); this.lex(); Maybe name = Maybe.nothing(); Maybe heritage = Maybe.nothing(); if (this.match(TokenType.IDENTIFIER)) { name = Maybe.just(this.parseBindingIdentifier()); } boolean previousParamYield = this.allowYieldExpression; this.allowYieldExpression = false; if (this.eat(TokenType.EXTENDS)) { Either fromParseLeftHandSideExpression = this.isolateCoverGrammar(() -> this.parseLeftHandSideExpression(true)); if (fromParseLeftHandSideExpression.isLeft()) { heritage = Maybe.just((Expression) fromParseLeftHandSideExpression.left().just()); } else { throw this.createError(ErrorMessages.UNEXPECTED_OBJECT_BINDING); } } this.expect(TokenType.LBRACE); ArrayList elements = new ArrayList<>(); while (!this.eat(TokenType.RBRACE)) { if (this.eat(TokenType.SEMICOLON)) { continue; } boolean isStatic = false; Either methodOrKey = this.parseMethodDefinition(); if (methodOrKey.isLeft() && methodOrKey.left().just() instanceof StaticPropertyName && ((StaticPropertyName) methodOrKey.left().just()).value.equals("static")) { isStatic = true; methodOrKey = this.parseMethodDefinition(); } if (methodOrKey.isRight()) { elements.add(new ClassElement(isStatic, methodOrKey.right().just())); } else { throw this.createError("Only methods are allowed in classes"); } } this.allowYieldExpression = previousParamYield; return this.markLocation(startLocation, new ClassExpression(name, heritage, ImmutableList.from(elements))); } @NotNull private ClassDeclaration parseClass(boolean inDefault) throws JsError { SourceLocation startLocation = this.getLocation(); this.lex(); Maybe name; Maybe heritage = Maybe.nothing(); if (this.match(TokenType.IDENTIFIER)) { name = Maybe.just(this.parseBindingIdentifier()); } else { if (inDefault) { name = Maybe.just(this.markLocation(startLocation, new BindingIdentifier("*default*"))); } else { throw this.createUnexpected(this.lookahead); } } boolean previousParamYield = this.allowYieldExpression; if (this.eat(TokenType.EXTENDS)) { Either fromParseLeftHandSideExpression = this.isolateCoverGrammar(() -> this.parseLeftHandSideExpression(true)); if (fromParseLeftHandSideExpression.isLeft()) { heritage = Maybe.just((Expression) fromParseLeftHandSideExpression.left().just()); } else { throw this.createError(ErrorMessages.UNEXPECTED_OBJECT_BINDING); } } this.expect(TokenType.LBRACE); ArrayList elements = new ArrayList<>(); while (!this.eat(TokenType.RBRACE)) { if (this.eat(TokenType.SEMICOLON)) { continue; } boolean isStatic = false; Either methodOrKey = this.parseMethodDefinition(); if (methodOrKey.isLeft() && ((StaticPropertyName) methodOrKey.left().just()).value.equals("static")) { isStatic = true; methodOrKey = this.parseMethodDefinition(); } if (methodOrKey.isRight()) { elements.add(new ClassElement(isStatic, methodOrKey.right().just())); } else { throw this.createError("Only methods are allowed in classes"); } } this.allowYieldExpression = previousParamYield; return this.markLocation(startLocation, new ClassDeclaration(name.just(), heritage, ImmutableList.from(elements))); } @NotNull private ImportDeclaration parseImportDeclaration() throws JsError { SourceLocation startLocation = this.getLocation(); Maybe defaultBinding = Maybe.nothing(); String moduleSpecifier; this.expect(TokenType.IMPORT); switch (this.lookahead.type) { case STRING: moduleSpecifier = this.lex().getValueString().toString(); this.consumeSemicolon(); return this.markLocation(startLocation, new Import(defaultBinding, ImmutableList.nil(), moduleSpecifier)); case IDENTIFIER: case YIELD: case LET: defaultBinding = Maybe.just(this.parseBindingIdentifier()); if (!this.eat(TokenType.COMMA)) { return this.markLocation(startLocation, new Import(defaultBinding, ImmutableList.nil(), this.parseFromClause())); } break; } if (this.match(TokenType.MUL)) { return this.markLocation(startLocation, new ImportNamespace(defaultBinding, this.parseNameSpaceBinding(), this.parseFromClause())); } else if (this.match(TokenType.LBRACE)) { return this.markLocation(startLocation, new Import(defaultBinding, this.parseNamedImports(), this.parseFromClause())); } else { throw this.createUnexpected(this.lookahead); } } @NotNull private ImmutableList parseNamedImports() throws JsError { ArrayList result = new ArrayList<>(); this.expect(TokenType.LBRACE); while (!this.eat(TokenType.RBRACE)) { result.add(this.parseImportSpecifier()); if (!this.eat(TokenType.COMMA)) { this.expect(TokenType.RBRACE); break; } } return ImmutableList.from(result); } @NotNull private ImportSpecifier parseImportSpecifier() throws JsError { SourceLocation startLocation = this.getLocation(); Maybe name = Maybe.nothing(); if (this.match(TokenType.IDENTIFIER) || this.match(TokenType.YIELD) || this.match(TokenType.LET)) { name = Maybe.just(this.parseIdentifier()); if (this.eatContextualKeyword("as") == null) { return this.markLocation(startLocation, new ImportSpecifier(Maybe.nothing(), this.markLocation(startLocation, new BindingIdentifier(name.just())))); } } else if (this.isIdentifierName(this.lookahead.type.klass)) { name = Maybe.just(this.parseIdentifierName()); this.expectContextualKeyword("as"); } return this.markLocation(startLocation, new ImportSpecifier(name, this.parseBindingIdentifier())); } @Nullable private Token eatContextualKeyword(String keyword) throws JsError { if (this.lookahead.type == TokenType.IDENTIFIER && this.lookahead.toString().equals(keyword)) { return this.lex(); } else { return null; } } @NotNull private BindingIdentifier parseNameSpaceBinding() throws JsError { this.expect(TokenType.MUL); this.expectContextualKeyword("as"); return this.parseBindingIdentifier(); } @NotNull private String parseFromClause() throws JsError { this.expectContextualKeyword("from"); String value = this.expect(TokenType.STRING).getValueString().toString(); this.consumeSemicolon(); return value; } @NotNull private Token expectContextualKeyword(String keyword) throws JsError { if (this.lookahead.type == TokenType.IDENTIFIER && this.lookahead.toString().equals(keyword)) { return this.lex(); } else { throw this.createUnexpected(this.lookahead); } } @NotNull private ExportDeclaration parseExportDeclaration() throws JsError { SourceLocation startLocation = this.getLocation(); ExportDeclaration decl; this.expect(TokenType.EXPORT); switch (this.lookahead.type) { case MUL: this.lex(); decl = new ExportAllFrom(this.parseFromClause()); break; case LBRACE: ImmutableList namedExports = this.parseExportClause(); Maybe moduleSpecifier = Maybe.nothing(); if (this.matchContextualKeyword("from")) { moduleSpecifier = Maybe.just(this.parseFromClause()); } decl = new ExportFrom(namedExports, moduleSpecifier); break; case CLASS: decl = new Export(this.parseClass(false)); break; case FUNCTION: decl = new Export((FunctionDeclaration) this.parseFunctionDeclaration(false, true)); break; case DEFAULT: this.lex(); switch (this.lookahead.type) { case FUNCTION: decl = new ExportDefault((FunctionDeclaration) this.parseFunctionDeclaration(true, true)); break; case CLASS: decl = new ExportDefault(this.parseClass(true)); break; default: decl = new ExportDefault(this.parseAssignmentExpression().left().just()); this.consumeSemicolon(); break; } break; case VAR: case LET: case CONST: decl = new Export(this.parseVariableDeclaration(true)); this.consumeSemicolon(); break; default: throw this.createUnexpected(this.lookahead); } return this.markLocation(startLocation, decl); } @NotNull private ImmutableList parseExportClause() throws JsError { this.expect(TokenType.LBRACE); ArrayList result = new ArrayList<>(); while (!this.eat(TokenType.RBRACE)) { result.add(this.parseExportSpecifier()); if (!this.eat(TokenType.COMMA)) { this.expect(TokenType.RBRACE); break; } } return ImmutableList.from(result); } @NotNull private ExportSpecifier parseExportSpecifier() throws JsError { SourceLocation startLocation = this.getLocation(); String name = this.parseIdentifierName(); if (this.eatContextualKeyword("as") != null) { String exportedName = this.parseIdentifierName(); return this.markLocation(startLocation, new ExportSpecifier(Maybe.just(name), exportedName)); } return this.markLocation(startLocation, new ExportSpecifier(Maybe.nothing(), name)); } @Nullable private CompoundAssignmentOperator lookupCompoundAssignmentOperator(@NotNull Token token) { switch (token.type) { case ASSIGN_BIT_OR: return CompoundAssignmentOperator.AssignBitOr; case ASSIGN_BIT_XOR: return CompoundAssignmentOperator.AssignBitXor; case ASSIGN_BIT_AND: return CompoundAssignmentOperator.AssignBitAnd; case ASSIGN_SHL: return CompoundAssignmentOperator.AssignLeftShift; case ASSIGN_SHR: return CompoundAssignmentOperator.AssignRightShift; case ASSIGN_SHR_UNSIGNED: return CompoundAssignmentOperator.AssignUnsignedRightShift; case ASSIGN_ADD: return CompoundAssignmentOperator.AssignPlus; case ASSIGN_SUB: return CompoundAssignmentOperator.AssignMinus; case ASSIGN_MUL: return CompoundAssignmentOperator.AssignMul; case ASSIGN_DIV: return CompoundAssignmentOperator.AssignDiv; case ASSIGN_MOD: return CompoundAssignmentOperator.AssignRem; default: return null; // should not happen } } @Nullable private BinaryOperator lookupBinaryOperator(@NotNull Token token) { switch (token.type) { case OR: return BinaryOperator.LogicalOr; case AND: return BinaryOperator.LogicalAnd; case BIT_OR: return BinaryOperator.BitwiseOr; case BIT_XOR: return BinaryOperator.BitwiseXor; case BIT_AND: return BinaryOperator.BitwiseAnd; case EQ: return BinaryOperator.Equal; case NE: return BinaryOperator.NotEqual; case EQ_STRICT: return BinaryOperator.StrictEqual; case NE_STRICT: return BinaryOperator.StrictNotEqual; case LT: return BinaryOperator.LessThan; case GT: return BinaryOperator.GreaterThan; case LTE: return BinaryOperator.LessThanEqual; case GTE: return BinaryOperator.GreaterThanEqual; case INSTANCEOF: return BinaryOperator.Instanceof; case IN: return this.allowIn ? BinaryOperator.In : null; case SHL: return BinaryOperator.Left; case SHR: return BinaryOperator.Right; case SHR_UNSIGNED: return BinaryOperator.UnsignedRight; case ADD: return BinaryOperator.Plus; case SUB: return BinaryOperator.Minus; case MUL: return BinaryOperator.Mul; case DIV: return BinaryOperator.Div; case MOD: return BinaryOperator.Rem; } return null; } private boolean isPrefixOperator(Token token) { switch (token.type) { case INC: case DEC: case ADD: case SUB: case BIT_NOT: case NOT: case DELETE: case VOID: case TYPEOF: return true; } return false; } @Nullable private UnaryOperator lookupUnaryOperator(Token token) { switch (token.type) { case ADD: return UnaryOperator.Plus; case SUB: return UnaryOperator.Minus; case BIT_NOT: return UnaryOperator.BitNot; case NOT: return UnaryOperator.LogicalNot; case DELETE: return UnaryOperator.Delete; case VOID: return UnaryOperator.Void; case TYPEOF: return UnaryOperator.Typeof; } return null; } @Nullable private UpdateOperator lookupUpdateOperator(Token token) { switch (token.type) { case INC: return UpdateOperator.Increment; case DEC: return UpdateOperator.Decrement; } return null; } private static class ExprStackItem { final SourceLocation startLocation; @NotNull final Expression left; @NotNull final BinaryOperator operator; final int precedence; ExprStackItem(@NotNull SourceLocation startLocation, @NotNull Expression left, @NotNull BinaryOperator operator) { this.startLocation = startLocation; this.left = left; this.operator = operator; this.precedence = operator.getPrecedence().ordinal(); } } public static class ScriptParser extends Parser { public ScriptParser(String text) throws JsError { super(text, false); } @NotNull @Override public Script parse() throws JsError { SourceLocation startLocation = this.getLocation(); Script node = this.parseBody(this::parseStatementListItem, Script::new); if (!this.match(TokenType.EOS)) { throw this.createUnexpected(this.lookahead); } return this.markLocation(startLocation, node); } } public static class ModuleParser extends Parser { public ModuleParser(String text) throws JsError { super(text, true); } @NotNull @Override public Module parse() throws JsError { return this.markLocation(this.getLocation(), this.parseBody(this::parseModuleItem, Module::new)); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy