Java tutorial
// Copyright 2012 Peter Hallam // // 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 omakase.syntax; import com.google.common.collect.ImmutableList; import omakase.syntax.tokens.IdentifierToken; import omakase.syntax.tokens.Token; import omakase.syntax.tokens.TokenKind; import omakase.syntax.trees.*; import omakase.util.ErrorReporter; import omakase.util.SourceFile; import omakase.util.SourceRange; /** * Parser for the Omakase language. * * There are 3 kinds of methods - parsing, peeking and scanning. * * parse* methods, parse a given construct returning the parsed tree. The position is advanced to the end of the parsed * construct. Parsing methods must report any parse errors encountered. * * peek* methods do simple lookahead for simple disambiguation. The return value is a boolean indicating the presence of * the peeked construct. The position in the token stream is unchanged. Peek methods must never report errors. * * scan* methods do complex disambiguation. They return enums indicating one of several alternatives, or an indication * that the lookahead was ambiguous and more lookahead will be needed to resolve the ambiguity. Scan methods leave * the token stream at the end of the scanned construct. Scanning must never report errors. */ public class Parser extends ParserBase { public Parser(ErrorReporter reporter, SourceFile file) { super(reporter, file, new Scanner(reporter, file)); } public static SourceFileTree parse(ErrorReporter reporter, SourceFile file) { return new Parser(reporter, file).parseFile(); } private SourceFileTree parseFile() { Token start = peek(); ImmutableList.Builder<ParseTree> declarations = new ImmutableList.Builder<ParseTree>(); while (peekClass()) { declarations.add(parseClass()); } eat(TokenKind.END_OF_FILE); return new SourceFileTree(getRange(start), declarations.build()); } // Parse Class Members private FormalParameterListTree parseParameterListDeclaration(boolean typesRequired) { Token start = peek(); ImmutableList.Builder<ParameterDeclarationTree> result = new ImmutableList.Builder<ParameterDeclarationTree>(); eat(TokenKind.OPEN_PAREN); if (peekParameter()) { result.add(parseParameter()); while (eatOpt(TokenKind.COMMA)) { result.add(parseParameter()); } } eat(TokenKind.CLOSE_PAREN); ImmutableList<ParameterDeclarationTree> parameters = result.build(); if (!parameters.isEmpty()) { // Types are either all specified or none. if (typesRequired || parameters.get(0).asParameterDeclaration().type != null) { for (ParameterDeclarationTree parameter : parameters) { if (parameter.type == null) { reportError(parameter.name, "Parameter type required"); } } } else { for (ParameterDeclarationTree parameter : parameters) { if (parameter.type != null) { reportError(parameter.type.start(), "Parameter type unexpected."); } } } } return new FormalParameterListTree(getRange(start), parameters); } private ParameterDeclarationTree parseParameter() { Token start = peek(); IdentifierToken name = eatId(); ParseTree type = parseColonTypeOpt(); return new ParameterDeclarationTree(getRange(start), type, name); } private ParseTree parseColonTypeOpt() { ParseTree type = null; if (eatOpt(TokenKind.COLON)) { type = parseType(); } return type; } private boolean peekParameter() { return peekParameter(0); } private boolean peekParameter(int index) { return peek(index, TokenKind.IDENTIFIER); } private boolean peekClassMember() { switch (peekKind()) { case NATIVE: case STATIC: case VAR: case IDENTIFIER: return true; } return false; } private boolean peekClass() { return peek(TokenKind.CLASS) || peek(TokenKind.EXTERN); } private ParseTree parseClass() { Token start = peek(); boolean isExtern = eatOpt(TokenKind.EXTERN); eat(TokenKind.CLASS); IdentifierToken name = eatId(); eat(TokenKind.OPEN_CURLY); ImmutableList<ParseTree> members = parseClassMembers(isExtern); eat(TokenKind.CLOSE_CURLY); return new ClassDeclarationTree(getRange(start), isExtern, name, members); } private ImmutableList<ParseTree> parseClassMembers(boolean isExtern) { ImmutableList.Builder<ParseTree> members = new ImmutableList.Builder<ParseTree>(); while (peekClassMember()) { members.add(parseClassMember(isExtern)); } return members.build(); } private ParseTree parseClassMember(boolean isExtern) { Token start = peek(); boolean isStatic = eatOpt(TokenKind.STATIC); boolean isNative = eatOpt(TokenKind.NATIVE); if (peek(TokenKind.VAR)) { if (isNative) { reportError(start, "Fields may not be native."); } return parseField(start, isExtern, isStatic); } return parseMethod(start, isExtern, isNative, isStatic); } private ParseTree parseField(Token start, boolean isExtern, boolean isStatic) { eat(TokenKind.VAR); ImmutableList<VariableDeclarationTree> declarations = parseVariableDeclarations(); if (isExtern) { for (VariableDeclarationTree declaration : declarations) { if (declaration.initializer != null) { reportError(declaration.initializer.start(), "Extern fields may not have initializers."); } } } eat(TokenKind.SEMI_COLON); return new FieldDeclarationTree(getRange(start), isStatic, declarations); } private ParseTree parseMethod(Token start, boolean isExtern, boolean isNative, boolean isStatic) { IdentifierToken name = eatId(); FormalParameterListTree formals = parseParameterListDeclaration(true); ParseTree returnType = parseColonType(); ParseTree body; if (isExtern) { body = null; eat(TokenKind.SEMI_COLON); } else { body = parseBlock(isNative); } return new MethodDeclarationTree(getRange(start), returnType, name, formals, isStatic, isNative, body); } private ParseTree parseColonType() { eat(TokenKind.COLON); return parseType(); } private ParseTree parseBlock(boolean isNative) { return isNative ? parseNativeBlock() : parseBlock(); } private ParseTree parseNativeBlock() { JavascriptParser nativeParser = new JavascriptParser(reporter, new SourceRange(this.file(), this.getPosition(), this.file().length())); ParseTree result = nativeParser.parseBlock(); this.setPosition(nativeParser.getPosition()); return result; } // Types private ParseTree parseType() { Token start = peek(); ParseTree elementType = parseElementType(); do { switch (peekKind()) { case OPEN_SQUARE: eat(TokenKind.OPEN_SQUARE); eat(TokenKind.CLOSE_SQUARE); elementType = new ArrayTypeTree(getRange(start), elementType); break; case QUESTION: eat(TokenKind.QUESTION); elementType = new NullableTypeTree(getRange(start), elementType); break; default: return elementType; } } while (true); } private ParseTree parseElementType() { switch (peekKind()) { case OPEN_PAREN: return parseFunctionType(); case IDENTIFIER: return parseNamedType(); case BOOL: case DYNAMIC: case NUMBER: case OBJECT: case STRING: case VOID: return parseKeywordType(); default: reportError(peek(), "Type expected"); return null; } } private ParseTree parseNamedType() { Token start = peek(); NamedTypeTree element = parseNamedTypeElement(start, null); while (eatOpt(TokenKind.PERIOD)) { element = parseNamedTypeElement(start, element); } return element; } private NamedTypeTree parseNamedTypeElement(Token start, NamedTypeTree element) { IdentifierToken name = eatId(); TypeArgumentListTree typeArguments = parseTypeArgumentsOpt(); return new NamedTypeTree(getRange(start), element, name, typeArguments); } private TypeArgumentListTree parseTypeArgumentsOpt() { if (peek(TokenKind.OPEN_ANGLE)) { return parseTypeArguments(); } return null; } private TypeArgumentListTree parseTypeArguments() { Token start = peek(); eat(TokenKind.OPEN_ANGLE); ImmutableList.Builder<ParseTree> typeArguments = new ImmutableList.Builder<ParseTree>(); typeArguments.add(parseType()); while (eatOpt(TokenKind.COMMA)) { typeArguments.add(parseType()); } eat(TokenKind.CLOSE_ANGLE); return new TypeArgumentListTree(getRange(start), typeArguments.build()); } private boolean peekType() { switch (peekKind()) { case BOOL: case DYNAMIC: case IDENTIFIER: case NUMBER: case OBJECT: case OPEN_PAREN: case STRING: case VOID: return true; default: return false; } } private ParseTree parseKeywordType() { Token start = peek(); nextToken(); return new KeywordTypeTree(getRange(start), start); } private ParseTree parseFunctionType() { Token start = peek(); eat(TokenKind.OPEN_PAREN); ImmutableList.Builder<ParseTree> parameterTypes = new ImmutableList.Builder<ParseTree>(); if (peekType()) { parameterTypes.add(parseType()); while (eatOpt(TokenKind.COMMA)) { parameterTypes.add(parseType()); } } eat(TokenKind.CLOSE_PAREN); eat(TokenKind.ARROW); ParseTree returnType = parseType(); return new FunctionTypeTree(getRange(start), parameterTypes.build(), returnType); } // Statements private BlockTree parseBlock() { Token start = peek(); eat(TokenKind.OPEN_CURLY); ImmutableList<ParseTree> statements = parseStatementList(); eat(TokenKind.CLOSE_CURLY); return new BlockTree(getRange(start), statements); } private ImmutableList<ParseTree> parseStatementList() { ImmutableList.Builder<ParseTree> statements = new ImmutableList.Builder<ParseTree>(); while (peekStatement()) { statements.add(parseStatement()); } return statements.build(); } private ParseTree parseStatement() { switch (peekKind()) { // expression case OPEN_PAREN: case IDENTIFIER: case VOID: case STRING: case OBJECT: case BOOL: case DYNAMIC: case NUMBER: case OPEN_SQUARE: case NULL: case THIS: case TRUE: case FALSE: case NUMBER_LITERAL: case STRING_LITERAL: case NEW: case TYPEOF: case PLUS_PLUS: case MINUS_MINUS: case PLUS: case MINUS: case BANG: case TILDE: return parseExpressionStatement(); // statements case OPEN_CURLY: return parseBlock(); case VAR: return parseVariableStatement(); case SEMI_COLON: return parseEmptyStatement(); case IF: return parseIfStatement(); case DO: return parseDoStatement(); case WHILE: return parseWhileStatement(); case FOR: return parseForStatement(); case CONTINUE: return parseContinueStatement(); case BREAK: return parseBreakStatement(); case RETURN: return parseReturnStatement(); case SWITCH: return parseSwitchStatement(); case THROW: return parseThrowStatement(); case TRY: return parseTryStatement(); case DEBUGGER: return parseDebuggerStatement(); default: throw new RuntimeException("Unexpected statement token."); } } private ParseTree parseVariableStatement() { Token start = peek(); eat(TokenKind.VAR); ImmutableList<VariableDeclarationTree> declarations = parseVariableDeclarations(); eat(TokenKind.SEMI_COLON); return new VariableStatementTree(getRange(start), declarations); } private ImmutableList<VariableDeclarationTree> parseVariableDeclarations() { return parseRemainingVariableDeclarations(parseVariableDeclaration()); } private ImmutableList<VariableDeclarationTree> parseRemainingVariableDeclarations( VariableDeclarationTree element) { ImmutableList.Builder<VariableDeclarationTree> declarations = new ImmutableList.Builder<VariableDeclarationTree>(); declarations.add(element); while (eatOpt(TokenKind.COMMA)) { declarations.add(parseVariableDeclaration()); } return declarations.build(); } private VariableDeclarationTree parseVariableDeclaration() { Token start = peek(); IdentifierToken identifier = eatId(); return parseVariableDeclaration(start, identifier); } private VariableDeclarationTree parseVariableDeclaration(Token start, IdentifierToken identifier) { ParseTree type = parseColonTypeOpt(); ParseTree initializer = null; if (eatOpt(TokenKind.EQUAL)) { initializer = parseExpression(); } return new VariableDeclarationTree(getRange(start), identifier, type, initializer); } private ParseTree parseEmptyStatement() { Token start = peek(); eat(TokenKind.SEMI_COLON); return new EmptyStatementTree(getRange(start)); } private ParseTree parseIfStatement() { Token start = peek(); eat(TokenKind.IF); eat(TokenKind.OPEN_PAREN); ParseTree expression = parseExpression(); eat(TokenKind.CLOSE_PAREN); ParseTree ifBody = parseStatement(); ParseTree elseBody = null; if (eatOpt(TokenKind.ELSE)) { elseBody = parseStatement(); } return new IfStatementTree(getRange(start), expression, ifBody, elseBody); } private ParseTree parseDoStatement() { Token start = peek(); eat(TokenKind.DO); ParseTree body = parseStatement(); eat(TokenKind.WHILE); eat(TokenKind.OPEN_PAREN); ParseTree expression = parseExpression(); eat(TokenKind.CLOSE_PAREN); eat(TokenKind.SEMI_COLON); return new DoStatementTree(getRange(start), body, expression); } private ParseTree parseWhileStatement() { Token start = peek(); eat(TokenKind.WHILE); eat(TokenKind.OPEN_PAREN); ParseTree expression = parseExpression(); eat(TokenKind.CLOSE_PAREN); return new WhileStatementTree(getRange(start), expression, parseStatement()); } private ParseTree parseForStatement() { Token start = peek(); eat(TokenKind.FOR); eat(TokenKind.OPEN_PAREN); switch (peekKind()) { case VAR: Token variableStart = peek(); eat(TokenKind.VAR); VariableDeclarationTree variableDeclaration = parseVariableDeclaration(); if (eatOpt(TokenKind.IN)) { if (variableDeclaration.initializer != null) { reportError(variableDeclaration.initializer.start(), "Variable declared in for-in statement may not have an initializer."); } return parseForIn(start, variableDeclaration); } else { return parseForStatement(start, new VariableStatementTree(getRange(variableStart), parseRemainingVariableDeclarations(variableDeclaration))); } case SEMI_COLON: return parseForStatement(start, null); default: ParseTree initializer = parseExpression(); return parseForStatement(start, initializer); } } private ParseTree parseForStatement(Token start, ParseTree initializer) { eat(TokenKind.SEMI_COLON); ParseTree condition = peek(TokenKind.SEMI_COLON) ? null : parseExpression(); eat(TokenKind.SEMI_COLON); ParseTree increment = peek(TokenKind.CLOSE_PAREN) ? null : parseExpression(); eat(TokenKind.CLOSE_PAREN); return new ForStatementTree(getRange(start), initializer, condition, increment, parseStatement()); } private ParseTree parseForIn(Token start, VariableDeclarationTree variableDeclaration) { ParseTree collection = parseExpression(); eat(TokenKind.CLOSE_PAREN); return new ForInStatementTree(getRange(start), variableDeclaration, collection, parseStatement()); } private ParseTree parseContinueStatement() { Token start = peek(); eat(TokenKind.CONTINUE); IdentifierToken label = eatIdOpt(); eat(TokenKind.SEMI_COLON); return new ContinueStatementTree(getRange(start), label); } private ParseTree parseBreakStatement() { Token start = peek(); eat(TokenKind.BREAK); IdentifierToken label = eatIdOpt(); eat(TokenKind.SEMI_COLON); return new BreakStatementTree(getRange(start), label); } private ParseTree parseReturnStatement() { Token start = peek(); eat(TokenKind.RETURN); ParseTree expression = null; if (peekExpression()) { expression = parseExpression(); } eat(TokenKind.SEMI_COLON); return new ReturnStatementTree(getRange(start), expression); } private ParseTree parseSwitchStatement() { Token start = peek(); eat(TokenKind.SWITCH); eat(TokenKind.OPEN_PAREN); ParseTree expression = parseExpression(); eat(TokenKind.CLOSE_PAREN); eat(TokenKind.OPEN_CURLY); ImmutableList.Builder<ParseTree> caseClauses = new ImmutableList.Builder<ParseTree>(); ParseTree defaultClause = null; while (peekCaseClause()) { ParseTree clause = parseCaseClause(); if (clause.isDefaultClause()) { if (defaultClause == null) { defaultClause = clause; } else { reportError(clause.start(), "Duplicate default clause in switch."); } } caseClauses.add(clause); } eat(TokenKind.CLOSE_CURLY); return new SwitchStatementTree(getRange(start), expression, caseClauses.build()); } private ParseTree parseCaseClause() { switch (peekKind()) { case CASE: return parseCase(); case DEFAULT: return parseDefault(); default: throw new RuntimeException("Unexpected case clause."); } } private ParseTree parseDefault() { Token start = peek(); eat(TokenKind.DEFAULT); eat(TokenKind.COLON); return new DefaultClauseTree(getRange(start), parseStatementList()); } private ParseTree parseCase() { Token start = peek(); eat(TokenKind.CASE); ParseTree expression = parseExpression(); eat(TokenKind.COLON); return new CaseClauseTree(getRange(start), expression, parseStatementList()); } private boolean peekCaseClause() { switch (peekKind()) { case CASE: case DEFAULT: return true; default: return false; } } private ParseTree parseThrowStatement() { Token start = peek(); eat(TokenKind.THROW); ParseTree exception = parseExpression(); eat(TokenKind.SEMI_COLON); return new ThrowStatementTree(getRange(start), exception); } private ParseTree parseTryStatement() { Token start = peek(); eat(TokenKind.TRY); BlockTree body = parseBlock(); CatchClauseTree catchClause = null; if (peek(TokenKind.CATCH)) { catchClause = parseCatchClause(); } BlockTree finallyBlock = null; if (eatOpt(TokenKind.FINALLY)) { finallyBlock = parseBlock(); } return new TryStatementTree(getRange(start), body, catchClause, finallyBlock); } private CatchClauseTree parseCatchClause() { Token start = peek(); eat(TokenKind.CATCH); eat(TokenKind.OPEN_PAREN); IdentifierToken exception = eatId(); eat(TokenKind.CLOSE_PAREN); BlockTree body = parseBlock(); return new CatchClauseTree(getRange(start), exception, body); } private ParseTree parseDebuggerStatement() { Token start = peek(); eat(TokenKind.DEBUGGER); eat(TokenKind.SEMI_COLON); return new DebuggerStatementTree(getRange(start)); } private ParseTree parseExpressionStatement() { Token start = peek(); ParseTree expression = parseExpression(); eat(TokenKind.SEMI_COLON); return new ExpressionStatementTree(getRange(start), expression); } private boolean peekStatement() { switch (peekKind()) { // expression case OPEN_PAREN: case OPEN_SQUARE: case NULL: case THIS: case TRUE: case FALSE: case IDENTIFIER: case NUMBER_LITERAL: case STRING_LITERAL: case NEW: case TYPEOF: case VOID: case PLUS_PLUS: case MINUS_MINUS: case PLUS: case MINUS: case BANG: case TILDE: return true; // statements case VAR: case SEMI_COLON: case IF: case DO: case WHILE: case FOR: case CONTINUE: case BREAK: case RETURN: case SWITCH: case THROW: case TRY: case DEBUGGER: return true; default: return false; } } // Parse Expressions // Expressions // expression: // function-expression // conditional-expression // assignment-expression private ParseTree parseExpression() { if (peekFunction()) { return parseFunction(); } ParseTree left = parseConditionalExpression(); if (!peekAssignmentOperator()) { return left; } return parseAssignmentExpression(left); } private ParseTree parseAssignmentExpression(ParseTree left) { Token operator = nextToken(); checkForLeftHandSideExpression(left, operator); ParseTree right = parseExpression(); return new BinaryExpressionTree(getRange(left.start()), left, operator, right); } private void checkForLeftHandSideExpression(ParseTree left, Token operator) { switch (left.kind) { case ARRAY_ACCESS_EXPRESSION: case CALL_EXPRESSION: case PAREN_EXPRESSION: case IDENTIFIER_EXPRESSION: case MEMBER_EXPRESSION: case NEW_EXPRESSION: case POSTFIX_EXPRESSION: case THIS_EXPRESSION: case UNARY_EXPRESSION: break; case ARRAY_LITERAL_EXPRESSION: case BINARY_EXPRESSION: case CONDITIONAL_EXPRESSION: case FUNCTION_EXPRESSION: case LITERAL_EXPRESSION: reportError(operator, "Left hand side of an assignment operator may not be conditional, function, literal or binary expressions."); break; default: throw new RuntimeException("Unexpected expression kind"); } } private ParseTree parseFunction() { Token start = peek(); FormalParameterListTree parameters = parseParameterListDeclaration(false); eat(TokenKind.ARROW); ParseTree body; if (peek(TokenKind.OPEN_CURLY)) { body = parseBlock(); } else { body = parseExpression(); } return new FunctionExpressionTree(getRange(start), parameters, body); } private boolean peekFunction() { if (!peek(TokenKind.OPEN_PAREN)) { return false; } int index = 1; if (peekParameter(index)) { index++; while (peek(index, TokenKind.COMMA)) { index++; if (peekParameter(index)) { index++; } else { return false; } } } if (!peek(index, TokenKind.CLOSE_PAREN)) { return false; } return peek(index + 1, TokenKind.ARROW); } private ParseTree parseConditionalExpression() { Token start = peek(); ParseTree condition = parseLogicalOrExpression(); if (!eatOpt(TokenKind.QUESTION)) { return condition; } ParseTree trueCase = parseExpression(); eat(TokenKind.COLON); ParseTree falseCase = parseExpression(); return new ConditionalExpressionTree(getRange(start), condition, trueCase, falseCase); } private ParseTree parseLogicalOrExpression() { Token start = peek(); ParseTree left = parseLogicalAndExpression(); while (peek(TokenKind.BAR_BAR)) { Token operator = nextToken(); ParseTree right = parseLogicalAndExpression(); left = new BinaryExpressionTree(getRange(start), left, operator, right); } return left; } private ParseTree parseLogicalAndExpression() { Token start = peek(); ParseTree left = parseBitwiseOrExpression(); while (peek(TokenKind.AMPERSAND_AMPERSAND)) { Token operator = nextToken(); ParseTree right = parseBitwiseOrExpression(); left = new BinaryExpressionTree(getRange(start), left, operator, right); } return left; } private ParseTree parseBitwiseOrExpression() { Token start = peek(); ParseTree left = parseBitwiseXorExpression(); while (peek(TokenKind.BAR)) { Token operator = nextToken(); ParseTree right = parseBitwiseXorExpression(); left = new BinaryExpressionTree(getRange(start), left, operator, right); } return left; } private ParseTree parseBitwiseXorExpression() { Token start = peek(); ParseTree left = parseBitwiseAndExpression(); while (peek(TokenKind.HAT)) { Token operator = nextToken(); ParseTree right = parseBitwiseAndExpression(); left = new BinaryExpressionTree(getRange(start), left, operator, right); } return left; } private ParseTree parseBitwiseAndExpression() { Token start = peek(); ParseTree left = parseEqualityExpression(); while (peek(TokenKind.AMPERSAND)) { Token operator = nextToken(); ParseTree right = parseEqualityExpression(); left = new BinaryExpressionTree(getRange(start), left, operator, right); } return left; } private ParseTree parseEqualityExpression() { Token start = peek(); ParseTree left = parseRelationalExpression(); while (peekEqualityOperator()) { Token operator = nextToken(); ParseTree right = parseRelationalExpression(); left = new BinaryExpressionTree(getRange(start), left, operator, right); } return left; } private ParseTree parseRelationalExpression() { Token start = peek(); ParseTree left = parseInstanceOfExpression(); while (peekRelationalOperator()) { Token operator = nextToken(); ParseTree right = parseInstanceOfExpression(); left = new BinaryExpressionTree(getRange(start), left, operator, right); } return left; } private ParseTree parseInstanceOfExpression() { Token start = peek(); ParseTree left = parseShiftExpression(); while (peek(TokenKind.INSTANCEOF)) { Token operator = nextToken(); ParseTree right = parseType(); left = new BinaryExpressionTree(getRange(start), left, operator, right); } return left; } private ParseTree parseShiftExpression() { Token start = peek(); ParseTree left = parseAdditiveExpression(); while (peekShiftOperator()) { Token operator = nextToken(); ParseTree right = parseAdditiveExpression(); left = new BinaryExpressionTree(getRange(start), left, operator, right); } return left; } private ParseTree parseAdditiveExpression() { Token start = peek(); ParseTree left = parseMultiplicativeExpression(); while (peekAdditiveOperator()) { Token operator = nextToken(); ParseTree right = parseMultiplicativeExpression(); left = new BinaryExpressionTree(getRange(start), left, operator, right); } return left; } private ParseTree parseMultiplicativeExpression() { Token start = peek(); ParseTree left = parseUnaryExpression(); while (peekMultiplicativeOperator()) { Token operator = nextToken(); ParseTree right = parseUnaryExpression(); left = new BinaryExpressionTree(getRange(start), left, operator, right); } return left; } private ParseTree parseUnaryExpression() { Token start = peek(); if (peekUnaryOperator()) { Token operator = nextToken(); return new UnaryExpressionTree(getRange(start), operator, parseUnaryExpression()); } return parsePostfixExpression(); } private boolean peekUnaryOperator() { switch (peekKind()) { case TYPEOF: case PLUS_PLUS: case MINUS_MINUS: case PLUS: case MINUS: case TILDE: case BANG: return true; default: return false; } } private boolean peekMultiplicativeOperator() { switch (peekKind()) { case STAR: case SLASH: case PERCENT: return true; default: return false; } } private boolean peekAdditiveOperator() { switch (peekKind()) { case PLUS: case MINUS: return true; default: return false; } } private boolean peekShiftOperator() { switch (peekKind()) { case SHIFT_LEFT: case SHIFT_RIGHT: return true; default: return false; } } private boolean peekRelationalOperator() { switch (peekKind()) { case OPEN_ANGLE: case CLOSE_ANGLE: case GREATER_EQUAL: case LESS_EQUAL: return true; default: return false; } } private boolean peekEqualityOperator() { switch (peekKind()) { case EQUAL_EQUAL: case NOT_EQUAL: return true; default: return false; } } private boolean peekAssignmentOperator() { switch (peekKind()) { case EQUAL: case AMPERSAND_EQUAL: case BAR_EQUAL: case STAR_EQUAL: case SLASH_EQUAL: case PERCENT_EQUAL: case PLUS_EQUAL: case MINUS_EQUAL: case LEFT_SHIFT_EQUAL: case RIGHT_SHIFT_EQUAL: case HAT_EQUAL: return true; default: return false; } } private ParseTree parsePrimaryExpression() { switch (peekKind()) { case THIS: return parseThisExpression(); case OPEN_SQUARE: return parseArrayLiteral(); case OPEN_PAREN: return parseParenExpression(); case IDENTIFIER: // TODO: Type ArgumentList opt // Note: Must disambiguate with relational expression. return parseIdentifier(); case NULL: case TRUE: case FALSE: case NUMBER_LITERAL: case STRING_LITERAL: // TODO: Regular Expression literals go here. return parseLiteral(); default: reportError(nextToken(), "Expected expression."); return null; } } private ParseTree parseArrayLiteral() { Token start = peek(); ImmutableList.Builder<ParseTree> elements = new ImmutableList.Builder<ParseTree>(); eat(TokenKind.OPEN_SQUARE); if (peekExpression()) { elements.add(parseExpression()); while (eatOpt(TokenKind.COMMA)) { elements.add(parseExpression()); } } eat(TokenKind.CLOSE_SQUARE); return new ArrayLiteralExpressionTree(getRange(start), elements.build()); } private ParseTree parseThisExpression() { Token start = eat(TokenKind.THIS); return new ThisExpressionTree(getRange(start)); } private ParseTree parseParenExpression() { Token start = peek(); eat(TokenKind.OPEN_PAREN); ParseTree expression = parseExpression(); eat(TokenKind.CLOSE_PAREN); return new ParenExpressionTree(getRange(start), expression); } private ParseTree parseIdentifier() { IdentifierToken name = eatId(); return new IdentifierExpressionTree(getRange(name), name); } private ParseTree parsePostfixExpression() { Token start = peek(); ParseTree left = parseLeftHandSideExpression(); while (peekPostfixOperator()) { left = new PostfixExpressionTree(getRange(start), left, nextToken()); } return left; } private ParseTree parseLeftHandSideExpression() { Token start = peek(); ParseTree operand = parseNewExpression(); while (peekCallSuffix()) { switch (peekKind()) { case OPEN_PAREN: operand = parseCallSuffix(start, operand); break; case OPEN_SQUARE: operand = parseArraySuffix(start, operand); break; case PERIOD: operand = parseMemberAccessSuffix(start, operand); break; default: throw new RuntimeException("Unexpected call suffix."); } } return operand; } private ParseTree parseMemberAccessSuffix(Token start, ParseTree operand) { eat(TokenKind.PERIOD); // TODO: Add Type argument list opt // Note: Must disambiguate with relational expression. return new MemberExpressionTree(getRange(start), operand, eatId()); } private ParseTree parseCallSuffix(Token start, ParseTree operand) { return new CallExpressionTree(getRange(start), operand, parseArguments()); } private ParseTree parseArraySuffix(Token start, ParseTree operand) { return new ArrayAccessExpressionTree(getRange(start), operand, parseArrayIndex()); } private boolean peekCallSuffix() { switch (peekKind()) { case OPEN_PAREN: case OPEN_SQUARE: case PERIOD: return true; default: return false; } } private ParseTree parseNewExpression() { Token start = peek(); if (eatOpt(TokenKind.NEW)) { // TODO: Should be parseTypeName. ParseTree operand = parseNewExpression(); ArgumentsTree arguments = null; if (peekArguments()) { arguments = parseArguments(); } return new NewExpressionTree(getRange(start), operand, arguments); } else { return parseMemberExpressionNoNew(); } } private ParseTree parseMemberExpressionNoNew() { Token start = peek(); ParseTree operand; operand = parsePrimaryExpression(); while (peekMemberExpressionSuffix()) { switch (peekKind()) { case PERIOD: operand = parseMemberAccessSuffix(start, operand); break; case OPEN_SQUARE: operand = parseArraySuffix(start, operand); break; default: throw new RuntimeException("Unexpected member expression suffix."); } } return operand; } private ParseTree parseArrayIndex() { eat(TokenKind.OPEN_SQUARE); ParseTree index = parseExpression(); eat(TokenKind.CLOSE_SQUARE); return index; } private boolean peekMemberExpressionSuffix() { switch (peekKind()) { case PERIOD: case OPEN_SQUARE: return true; default: return false; } } private boolean peekPostfixOperator() { switch (peekKind()) { case PLUS_PLUS: case MINUS_MINUS: return true; default: return false; } } private ParseTree parseLiteral() { Token value = nextToken(); return new LiteralExpressionTree(getRange(value), value); } private boolean peekExpression() { switch (peekKind()) { case OPEN_CURLY: case OPEN_PAREN: case OPEN_SQUARE: case NULL: case THIS: case TRUE: case FALSE: case IDENTIFIER: case NUMBER_LITERAL: case STRING_LITERAL: case NEW: case TYPEOF: case VOID: case PLUS_PLUS: case MINUS_MINUS: case PLUS: case MINUS: case BANG: case TILDE: return true; default: return false; } } private boolean peekArguments() { return peek(TokenKind.OPEN_PAREN); } private ArgumentsTree parseArguments() { Token start = peek(); eat(TokenKind.OPEN_PAREN); ImmutableList.Builder<ParseTree> arguments = new ImmutableList.Builder<ParseTree>(); if (peekExpression()) { do { arguments.add(parseExpression()); } while (eatOpt(TokenKind.COMMA)); } eat(TokenKind.CLOSE_PAREN); return new ArgumentsTree(getRange(start), arguments.build()); } private IdentifierToken eatId() { Token token = eat(TokenKind.IDENTIFIER); return token.isIdentifier() ? token.asIdentifier() : null; } private IdentifierToken eatIdOpt() { if (peek(TokenKind.IDENTIFIER)) { return eatId(); } return null; } }