| /* |
| * Copyright 2016 The Bazel Authors. All rights reserved. |
| * |
| * 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.google.idea.blaze.base.lang.buildfile.parser; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind; |
| import com.google.idea.blaze.base.lang.buildfile.psi.BuildElementType; |
| import com.google.idea.blaze.base.lang.buildfile.psi.BuildElementTypes; |
| import com.intellij.lang.PsiBuilder; |
| import java.util.EnumSet; |
| import java.util.List; |
| |
| /** For parsing expressions in BUILD files. */ |
| public class ExpressionParsing extends Parsing { |
| |
| private static final ImmutableSet<TokenKind> LIST_TERMINATOR_SET = |
| ImmutableSet.of(TokenKind.EOF, TokenKind.RBRACKET, TokenKind.SEMI); |
| |
| private static final ImmutableSet<TokenKind> DICT_TERMINATOR_SET = |
| ImmutableSet.of(TokenKind.EOF, TokenKind.RBRACE, TokenKind.SEMI); |
| |
| private static final ImmutableSet<TokenKind> EXPR_LIST_TERMINATOR_SET = |
| ImmutableSet.of( |
| TokenKind.EOF, |
| TokenKind.NEWLINE, |
| TokenKind.EQUALS, |
| TokenKind.RBRACE, |
| TokenKind.RBRACKET, |
| TokenKind.RPAREN, |
| TokenKind.SEMI); |
| |
| protected static final ImmutableSet<TokenKind> EXPR_TERMINATOR_SET = |
| ImmutableSet.of( |
| TokenKind.EOF, |
| TokenKind.COLON, |
| TokenKind.COMMA, |
| TokenKind.FOR, |
| TokenKind.MINUS, |
| TokenKind.PERCENT, |
| TokenKind.PLUS, |
| TokenKind.RBRACKET, |
| TokenKind.RPAREN, |
| TokenKind.SLASH); |
| |
| private static final ImmutableSet<TokenKind> FUNCALL_TERMINATOR_SET = |
| ImmutableSet.of(TokenKind.EOF, TokenKind.RPAREN, TokenKind.SEMI, TokenKind.NEWLINE); |
| |
| /** |
| * Highest precedence goes last. Based on: |
| * http://docs.python.org/2/reference/expressions.html#operator-precedence |
| */ |
| private static final List<EnumSet<TokenKind>> OPERATOR_PRECEDENCE = |
| ImmutableList.of( |
| EnumSet.of(TokenKind.OR), |
| EnumSet.of(TokenKind.AND), |
| EnumSet.of(TokenKind.NOT), |
| EnumSet.of( |
| TokenKind.EQUALS_EQUALS, |
| TokenKind.NOT_EQUALS, |
| TokenKind.LESS, |
| TokenKind.LESS_EQUALS, |
| TokenKind.GREATER, |
| TokenKind.GREATER_EQUALS, |
| TokenKind.IN, |
| TokenKind.NOT_IN), |
| EnumSet.of(TokenKind.PIPE), |
| EnumSet.of(TokenKind.MINUS, TokenKind.PLUS), |
| EnumSet.of(TokenKind.SLASH, TokenKind.STAR, TokenKind.PERCENT)); |
| |
| public ExpressionParsing(ParsingContext context) { |
| super(context); |
| } |
| |
| public void parseExpression(boolean insideParens) { |
| // handle lists without parens (e.g. 'a,b,c = 1') |
| PsiBuilder.Marker marker = insideParens ? null : builder.mark(); |
| parseNonTupleExpression(); |
| if (currentToken() == TokenKind.COMMA) { |
| parseExpressionList(); |
| if (marker != null) { |
| marker.done(BuildElementTypes.LIST_LITERAL); |
| } |
| } else if (marker != null) { |
| marker.drop(); |
| } |
| } |
| |
| // expr_list ::= ( ',' expr )* ','? |
| private void parseExpressionList() { |
| while (matches(TokenKind.COMMA)) { |
| if (atAnyOfTokens(EXPR_LIST_TERMINATOR_SET)) { |
| break; |
| } |
| parseNonTupleExpression(); |
| } |
| } |
| |
| protected void parseNonTupleExpression() { |
| parseNonTupleExpression(0); |
| // don't bother including conditional expressions for now, |
| //just include their components serially |
| if (matches(TokenKind.IF)) { |
| parseNonTupleExpression(0); |
| if (matches(TokenKind.ELSE)) { |
| parseNonTupleExpression(); |
| } |
| } |
| } |
| |
| private void parseNonTupleExpression(int prec) { |
| if (prec >= OPERATOR_PRECEDENCE.size()) { |
| parsePrimaryWithSuffix(); |
| return; |
| } |
| if (currentToken() == TokenKind.NOT && OPERATOR_PRECEDENCE.get(prec).contains(TokenKind.NOT)) { |
| // special case handling of multi-token 'NOT IN' binary operator |
| if (kindFromElement(builder.lookAhead(1)) != TokenKind.IN) { |
| // skip the 'not' -- no need for a specific 'not' expression |
| builder.advanceLexer(); |
| parseNonTupleExpression(prec + 1); |
| return; |
| } |
| } |
| parseBinOpExpression(prec); |
| } |
| |
| /** |
| * binop_expression ::= binop_expression OP binop_expression | parsePrimaryWithSuffix This |
| * function takes care of precedence between operators (see OPERATOR_PRECEDENCE for the order), |
| * and it assumes left-to-right associativity. |
| */ |
| private void parseBinOpExpression(int prec) { |
| PsiBuilder.Marker marker = builder.mark(); |
| parseNonTupleExpression(prec + 1); |
| |
| while (true) { |
| if (!atBinaryOperator(prec)) { |
| marker.drop(); |
| return; |
| } |
| parseNonTupleExpression(prec + 1); |
| marker.done(BuildElementTypes.BINARY_OP_EXPRESSION); |
| marker = marker.precede(); |
| } |
| } |
| |
| /** |
| * Consumes current token iff it's a binary operator at the given precedence level (with |
| * special-case handling of 'NOT' 'IN' double token binary operator) |
| */ |
| private boolean atBinaryOperator(int prec) { |
| if (matchesAnyOf(OPERATOR_PRECEDENCE.get(prec))) { |
| return true; |
| } |
| if (matchesSequence(TokenKind.NOT, TokenKind.IN)) { |
| return true; |
| } |
| return false; |
| } |
| |
| // primary_with_suffix ::= primary (selector_suffix | substring_suffix)* |
| private void parsePrimaryWithSuffix() { |
| PsiBuilder.Marker marker = builder.mark(); |
| parsePrimary(); |
| while (true) { |
| if (matches(TokenKind.DOT)) { |
| marker = parseSelectorSuffix(marker); |
| } else if (matches(TokenKind.LBRACKET)) { |
| marker = parseSubstringSuffix(marker); |
| } else { |
| break; |
| } |
| } |
| marker.drop(); |
| } |
| |
| // selector_suffix ::= '.' IDENTIFIER [funcall_suffix] |
| private PsiBuilder.Marker parseSelectorSuffix(PsiBuilder.Marker marker) { |
| if (!atToken(TokenKind.IDENTIFIER)) { |
| builder.error("expected identifier after dot"); |
| syncPast(EXPR_TERMINATOR_SET); |
| return marker; |
| } |
| parseTargetOrReferenceIdentifier(); |
| if (atToken(TokenKind.LPAREN)) { |
| parseFuncallSuffix(); |
| marker.done(BuildElementTypes.FUNCALL_EXPRESSION); |
| } else { |
| marker.done(BuildElementTypes.DOT_EXPRESSION); |
| } |
| return marker.precede(); |
| } |
| |
| // substring_suffix ::= '[' expression? ':' expression? ':' expression? ']' |
| private PsiBuilder.Marker parseSubstringSuffix(PsiBuilder.Marker marker) { |
| if (!atToken(TokenKind.COLON)) { |
| PsiBuilder.Marker pos = builder.mark(); |
| parseExpression(false); |
| pos.done(BuildElementTypes.POSITIONAL); |
| } |
| while (!matches(TokenKind.RBRACKET)) { |
| if (expect(TokenKind.COLON)) { |
| if (!atAnyOfTokens(TokenKind.COLON, TokenKind.RBRACKET)) { |
| parseNonTupleExpression(); |
| } |
| } else { |
| syncPast(EXPR_LIST_TERMINATOR_SET); |
| break; |
| } |
| } |
| marker.done(BuildElementTypes.FUNCALL_EXPRESSION); |
| return marker.precede(); |
| } |
| |
| private void parseTargetOrReferenceIdentifier() { |
| if (!atToken(TokenKind.IDENTIFIER)) { |
| builder.error("expected an identifier"); |
| return; |
| } |
| // TODO: handle assigning to a list of targets (e.g. "a,b = 1") |
| TokenKind next = kindFromElement(builder.lookAhead(1)); |
| if (next == TokenKind.EQUALS || next == TokenKind.IN) { |
| buildTokenElement(BuildElementTypes.TARGET_EXPRESSION); |
| } else { |
| buildTokenElement(BuildElementTypes.REFERENCE_EXPRESSION); |
| } |
| } |
| |
| private void parsePrimary() { |
| TokenKind current = currentToken(); |
| switch (current) { |
| case INT: |
| buildTokenElement(BuildElementTypes.INTEGER_LITERAL); |
| return; |
| case STRING: |
| parseStringLiteral(true); |
| return; |
| case IDENTIFIER: |
| PsiBuilder.Marker marker = builder.mark(); |
| String tokenText = builder.getTokenText(); |
| parseTargetOrReferenceIdentifier(); |
| if (atToken(TokenKind.LPAREN)) { |
| parseFuncallSuffix(); |
| marker.done(getFuncallExpressionType(tokenText)); |
| } else { |
| marker.drop(); |
| } |
| return; |
| case LBRACKET: |
| parseListMaker(); |
| return; |
| case LBRACE: |
| parseDictExpression(); |
| return; |
| case LPAREN: |
| marker = builder.mark(); |
| builder.advanceLexer(); |
| if (matches(TokenKind.RPAREN)) { |
| marker.done(BuildElementTypes.LIST_LITERAL); |
| return; |
| } |
| parseExpression(true); |
| expect(TokenKind.RPAREN, true); |
| marker.done(BuildElementTypes.LIST_LITERAL); |
| return; |
| case MINUS: |
| marker = builder.mark(); |
| builder.advanceLexer(); |
| parsePrimaryWithSuffix(); |
| marker.done(BuildElementTypes.POSITIONAL); |
| return; |
| default: |
| builder.error("expected an expression"); |
| syncPast(EXPR_TERMINATOR_SET); |
| } |
| } |
| |
| /** funcall_suffix ::= '(' arg_list? ')' arg_list ::= ((arg ',')* arg ','? )? */ |
| private void parseFuncallSuffix() { |
| PsiBuilder.Marker mark = builder.mark(); |
| expect(TokenKind.LPAREN, true); |
| if (matches(TokenKind.RPAREN)) { |
| mark.done(BuildElementTypes.ARGUMENT_LIST); |
| return; |
| } |
| parseFuncallArgument(); |
| while (!atAnyOfTokens(FUNCALL_TERMINATOR_SET)) { |
| expect(TokenKind.COMMA); |
| if (atAnyOfTokens(FUNCALL_TERMINATOR_SET)) { |
| break; |
| } |
| parseFuncallArgument(); |
| } |
| expect(TokenKind.RPAREN, true); |
| mark.done(BuildElementTypes.ARGUMENT_LIST); |
| } |
| |
| private BuildElementType getFuncallExpressionType(String functionName) { |
| if ("glob".equals(functionName)) { |
| return BuildElementTypes.GLOB_EXPRESSION; |
| } |
| return BuildElementTypes.FUNCALL_EXPRESSION; |
| } |
| |
| protected void parseFunctionParameters() { |
| if (atToken(TokenKind.RPAREN)) { |
| return; |
| } |
| parseFunctionParameter(); |
| while (!atAnyOfTokens(FUNCALL_TERMINATOR_SET)) { |
| expect(TokenKind.COMMA); |
| if (atAnyOfTokens(FUNCALL_TERMINATOR_SET)) { |
| break; |
| } |
| parseFunctionParameter(); |
| } |
| } |
| |
| // arg ::= IDENTIFIER '=' nontupleexpr |
| // | expr |
| // | *args |
| // | **kwargs |
| private void parseFuncallArgument() { |
| PsiBuilder.Marker marker = builder.mark(); |
| if (matches(TokenKind.STAR_STAR)) { |
| parseNonTupleExpression(); |
| marker.done(BuildElementTypes.STAR_STAR); |
| return; |
| } |
| if (matches(TokenKind.STAR)) { |
| parseNonTupleExpression(); |
| marker.done(BuildElementTypes.STAR); |
| return; |
| } |
| if (matchesSequence(TokenKind.IDENTIFIER, TokenKind.EQUALS)) { |
| parseNonTupleExpression(); |
| marker.done(BuildElementTypes.KEYWORD); |
| return; |
| } |
| parseNonTupleExpression(); |
| marker.done(BuildElementTypes.POSITIONAL); |
| } |
| |
| /** arg ::= IDENTIFIER ['=' nontupleexpr] */ |
| private void parseFunctionParameter() { |
| PsiBuilder.Marker marker = builder.mark(); |
| if (matches(TokenKind.STAR_STAR)) { |
| expectIdentifier("invalid parameter name"); |
| marker.done(BuildElementTypes.PARAM_STAR_STAR); |
| return; |
| } |
| if (matches(TokenKind.STAR)) { |
| if (atToken(TokenKind.IDENTIFIER)) { |
| builder.advanceLexer(); |
| } |
| marker.done(BuildElementTypes.PARAM_STAR); |
| return; |
| } |
| expectIdentifier("invalid parameter name"); |
| if (matches(TokenKind.EQUALS)) { |
| parseNonTupleExpression(); |
| marker.done(BuildElementTypes.PARAM_OPTIONAL); |
| return; |
| } |
| marker.done(BuildElementTypes.PARAM_MANDATORY); |
| } |
| |
| protected void expectIdentifier(String error) { |
| expect(TokenKind.IDENTIFIER, error, true); |
| } |
| |
| // list_maker ::= '[' ']' |
| // |'[' expr ']' |
| // |'[' expr expr_list ']' |
| // |'[' expr ('FOR' loop_variables 'IN' expr)+ ']' |
| private void parseListMaker() { |
| PsiBuilder.Marker marker = builder.mark(); |
| expect(TokenKind.LBRACKET); |
| if (matches(TokenKind.RBRACKET)) { |
| marker.done(BuildElementTypes.LIST_LITERAL); |
| return; |
| } |
| parseNonTupleExpression(); |
| switch (currentToken()) { |
| case RBRACKET: |
| builder.advanceLexer(); |
| marker.done(BuildElementTypes.LIST_LITERAL); |
| return; |
| case FOR: |
| parseComprehensionSuffix(TokenKind.RBRACKET); |
| marker.done(BuildElementTypes.LIST_COMPREHENSION_EXPR); |
| return; |
| case COMMA: |
| parseExpressionList(); |
| if (!matches(TokenKind.RBRACKET)) { |
| builder.error("expected 'for' or ']'"); |
| syncPast(LIST_TERMINATOR_SET); |
| } |
| marker.done(BuildElementTypes.LIST_LITERAL); |
| return; |
| default: |
| builder.error("expected ',', 'for' or ']'"); |
| syncPast(LIST_TERMINATOR_SET); |
| marker.done(BuildElementTypes.LIST_LITERAL); |
| } |
| } |
| |
| // dict_expression ::= '{' '}' |
| // |'{' dict_entry_list '}' |
| // |'{' dict_entry 'FOR' loop_variables 'IN' expr '}' |
| private void parseDictExpression() { |
| PsiBuilder.Marker marker = builder.mark(); |
| expect(TokenKind.LBRACE, true); |
| if (matches(TokenKind.RBRACE)) { |
| marker.done(BuildElementTypes.DICTIONARY_LITERAL); |
| return; |
| } |
| parseDictEntry(); |
| if (currentToken() == TokenKind.FOR) { |
| parseComprehensionSuffix(TokenKind.RBRACE); |
| marker.done(BuildElementTypes.LIST_COMPREHENSION_EXPR); |
| return; |
| } |
| if (matches(TokenKind.COMMA)) { |
| parseDictEntryList(); |
| } |
| expect(TokenKind.RBRACE, true); |
| marker.done(BuildElementTypes.DICTIONARY_LITERAL); |
| } |
| |
| // dict_entry_list ::= ( (dict_entry ',')* dict_entry ','? )? |
| private void parseDictEntryList() { |
| if (atAnyOfTokens(DICT_TERMINATOR_SET)) { |
| return; |
| } |
| parseDictEntry(); |
| while (matches(TokenKind.COMMA)) { |
| if (atAnyOfTokens(DICT_TERMINATOR_SET)) { |
| return; |
| } |
| parseDictEntry(); |
| } |
| } |
| |
| // dict_entry ::= nontupleexpr ':' nontupleexpr |
| private void parseDictEntry() { |
| PsiBuilder.Marker marker = builder.mark(); |
| parseNonTupleExpression(); |
| expect(TokenKind.COLON); |
| parseNonTupleExpression(); |
| marker.done(BuildElementTypes.DICTIONARY_ENTRY_LITERAL); |
| } |
| |
| // comprehension_suffix ::= 'FOR' loop_variables 'IN' expr comprehension_suffix |
| // | 'IF' expr comprehension_suffix |
| // | ']' |
| private void parseComprehensionSuffix(TokenKind closingBracket) { |
| while (true) { |
| if (matches(TokenKind.FOR)) { |
| parseForLoopVariables(); |
| expect(TokenKind.IN); |
| parseNonTupleExpression(0); |
| } else if (matches(TokenKind.IF)) { |
| parseExpression(true); |
| } else if (matches(closingBracket)) { |
| return; |
| } else { |
| builder.error("expected " + closingBracket + ", 'for' or 'if'"); |
| syncPast(EXPR_LIST_TERMINATOR_SET); |
| return; |
| } |
| } |
| } |
| |
| // Equivalent to 'exprlist' rule in Python grammar. |
| // loop_variables ::= primary_with_suffix ( ',' primary_with_suffix )* ','? |
| protected void parseForLoopVariables() { |
| PsiBuilder.Marker marker = builder.mark(); |
| parsePrimaryWithSuffix(); |
| if (currentToken() != TokenKind.COMMA) { |
| marker.drop(); |
| return; |
| } |
| while (matches(TokenKind.COMMA)) { |
| if (atAnyOfTokens(EXPR_LIST_TERMINATOR_SET)) { |
| break; |
| } |
| parsePrimaryWithSuffix(); |
| } |
| marker.done(BuildElementTypes.LIST_LITERAL); |
| } |
| } |