| /* |
| * 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.ImmutableSet; |
| import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind; |
| import com.google.idea.blaze.base.lang.buildfile.psi.BuildElementTypes; |
| import com.intellij.lang.PsiBuilder; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.psi.tree.IElementType; |
| |
| /** For parsing statements in BUILD files. */ |
| public class StatementParsing extends Parsing { |
| |
| private static final ImmutableSet<TokenKind> STATEMENT_TERMINATOR_SET = |
| ImmutableSet.of(TokenKind.EOF, TokenKind.NEWLINE, TokenKind.SEMI); |
| |
| public StatementParsing(ParsingContext context) { |
| super(context); |
| } |
| |
| /** Called at the start of parsing. Parses an entire file */ |
| public void parseFileInput() { |
| builder.setDebugMode(ApplicationManager.getApplication().isUnitTestMode()); |
| while (!builder.eof()) { |
| if (matches(TokenKind.NEWLINE)) { |
| continue; |
| } |
| parseTopLevelStatement(); |
| } |
| } |
| |
| // Unlike in Python grammar, 'load' and 'def' are only allowed as a top-level statement |
| public void parseTopLevelStatement() { |
| if (currentToken() == TokenKind.IDENTIFIER && "load".equals(builder.getTokenText())) { |
| parseLoadStatement(); |
| } else if (currentToken() == TokenKind.DEF) { |
| parseFunctionDefStatement(); |
| } else { |
| parseStatement(); |
| } |
| } |
| |
| // simple_stmt | compound_stmt |
| public void parseStatement() { |
| TokenKind current = currentToken(); |
| if (current == TokenKind.IF) { |
| parseIfStatement(); |
| } else if (current == TokenKind.FOR) { |
| parseForStatement(); |
| } else if (FORBIDDEN_KEYWORDS.contains(current)) { |
| PsiBuilder.Marker mark = builder.mark(); |
| syncTo(STATEMENT_TERMINATOR_SET); |
| mark.error(forbiddenKeywordError(current)); |
| builder.advanceLexer(); |
| } else { |
| parseSimpleStatement(); |
| } |
| } |
| |
| // func_def_stmt ::= DEF IDENTIFIER funcall_suffix ':' suite |
| private void parseFunctionDefStatement() { |
| PsiBuilder.Marker marker = builder.mark(); |
| expect(TokenKind.DEF); |
| getExpressionParser().expectIdentifier("expected a function name"); |
| PsiBuilder.Marker listMarker = builder.mark(); |
| expect(TokenKind.LPAREN); |
| getExpressionParser().parseFunctionParameters(); |
| expect(TokenKind.RPAREN, true); |
| listMarker.done(BuildElementTypes.PARAMETER_LIST); |
| expect(TokenKind.COLON); |
| parseSuite(); |
| marker.done(BuildElementTypes.FUNCTION_STATEMENT); |
| } |
| |
| // load '(' STRING (',' [IDENTIFIER '='] STRING)* [','] ')' |
| private void parseLoadStatement() { |
| PsiBuilder.Marker marker = builder.mark(); |
| expect(TokenKind.IDENTIFIER); |
| expect(TokenKind.LPAREN); |
| parseStringLiteral(false); |
| // Not implementing [IDENTIFIER EQUALS] option -- not a documented feature, |
| // so wait for users to complain |
| boolean hasSymbols = false; |
| while (!matches(TokenKind.RPAREN) && !matchesAnyOf(STATEMENT_TERMINATOR_SET)) { |
| expect(TokenKind.COMMA); |
| if (matches(TokenKind.RPAREN) || matchesAnyOf(STATEMENT_TERMINATOR_SET)) { |
| break; |
| } |
| hasSymbols |= parseLoadedSymbol(); |
| } |
| if (!hasSymbols) { |
| builder.error("'load' statements must include at least one loaded function"); |
| } |
| marker.done(BuildElementTypes.LOAD_STATEMENT); |
| } |
| |
| /** [IDENTIFIER '='] STRING */ |
| private boolean parseLoadedSymbol() { |
| PsiBuilder.Marker marker = builder.mark(); |
| if (currentToken() == TokenKind.STRING) { |
| parseStringLiteral(true); |
| marker.done(BuildElementTypes.LOADED_SYMBOL); |
| return true; |
| } |
| if (parseAlias()) { |
| marker.done(BuildElementTypes.LOADED_SYMBOL); |
| return true; |
| } |
| marker.drop(); |
| builder.error("Expected a loaded symbol or alias"); |
| syncPast(ExpressionParsing.EXPR_TERMINATOR_SET); |
| return false; |
| } |
| |
| private boolean parseAlias() { |
| if (!atTokenSequence(TokenKind.IDENTIFIER, TokenKind.EQUALS, TokenKind.STRING)) { |
| return false; |
| } |
| PsiBuilder.Marker assignment = builder.mark(); |
| buildTokenElement(BuildElementTypes.TARGET_EXPRESSION); |
| expect(TokenKind.EQUALS); |
| parseStringLiteral(true); |
| assignment.done(BuildElementTypes.ASSIGNMENT_STATEMENT); |
| return true; |
| } |
| |
| /** if_stmt ::= IF expr ':' suite (ELIF expr ':' suite)* [ELSE ':' suite] */ |
| private void parseIfStatement() { |
| PsiBuilder.Marker marker = builder.mark(); |
| parseIfStatementPart(TokenKind.IF, BuildElementTypes.IF_PART, true); |
| while (currentToken() == TokenKind.ELIF) { |
| parseIfStatementPart(TokenKind.ELIF, BuildElementTypes.ELSE_IF_PART, true); |
| } |
| if (currentToken() == TokenKind.ELSE) { |
| parseIfStatementPart(TokenKind.ELSE, BuildElementTypes.ELSE_PART, false); |
| } |
| marker.done(BuildElementTypes.IF_STATEMENT); |
| } |
| |
| // cond_stmts ::= [EL]IF expr ':' suite |
| private void parseIfStatementPart(TokenKind tokenKind, IElementType type, boolean conditional) { |
| PsiBuilder.Marker marker = builder.mark(); |
| expect(tokenKind); |
| if (conditional) { |
| getExpressionParser().parseNonTupleExpression(); |
| } |
| expect(TokenKind.COLON); |
| parseSuite(); |
| marker.done(type); |
| } |
| |
| // for_stmt ::= FOR IDENTIFIER IN expr ':' suite |
| private void parseForStatement() { |
| PsiBuilder.Marker marker = builder.mark(); |
| expect(TokenKind.FOR); |
| getExpressionParser().parseForLoopVariables(); |
| expect(TokenKind.IN); |
| getExpressionParser().parseExpression(false); |
| expect(TokenKind.COLON); |
| parseSuite(); |
| marker.done(BuildElementTypes.FOR_STATEMENT); |
| } |
| |
| // simple_stmt ::= small_stmt (';' small_stmt)* ';'? NEWLINE |
| private void parseSimpleStatement() { |
| parseSmallStatementOrPass(); |
| while (matches(TokenKind.SEMI)) { |
| if (matches(TokenKind.NEWLINE)) { |
| return; |
| } |
| parseSmallStatementOrPass(); |
| } |
| if (!builder.eof()) { |
| expect(TokenKind.NEWLINE); |
| } |
| } |
| |
| // small_stmt | 'pass' |
| private void parseSmallStatementOrPass() { |
| if (currentToken() == TokenKind.PASS) { |
| buildTokenElement(BuildElementTypes.PASS_STATMENT); |
| return; |
| } |
| parseSmallStatement(); |
| } |
| |
| private void parseSmallStatement() { |
| if (currentToken() == TokenKind.RETURN) { |
| parseReturnStatement(); |
| return; |
| } |
| if (atAnyOfTokens(TokenKind.BREAK, TokenKind.CONTINUE)) { |
| buildTokenElement(BuildElementTypes.FLOW_STATEMENT); |
| return; |
| } |
| PsiBuilder.Marker refMarker = builder.mark(); |
| getExpressionParser().parseExpression(false); |
| if (matches(TokenKind.EQUALS)) { |
| getExpressionParser().parseExpression(false); |
| refMarker.done(BuildElementTypes.ASSIGNMENT_STATEMENT); |
| } else if (matchesAnyOf(TokenKind.AUGMENTED_ASSIGNMENT_OPS)) { |
| getExpressionParser().parseExpression(false); |
| refMarker.done(BuildElementTypes.AUGMENTED_ASSIGNMENT); |
| } else { |
| refMarker.drop(); |
| } |
| } |
| |
| private void parseReturnStatement() { |
| PsiBuilder.Marker marker = builder.mark(); |
| expect(TokenKind.RETURN); |
| if (!STATEMENT_TERMINATOR_SET.contains(currentToken())) { |
| getExpressionParser().parseExpression(false); |
| } |
| marker.done(BuildElementTypes.RETURN_STATEMENT); |
| } |
| |
| // suite ::= simple_stmt | (NEWLINE INDENT stmt+ DEDENT) |
| private void parseSuite() { |
| if (!matches(TokenKind.NEWLINE)) { |
| parseSimpleStatement(); |
| return; |
| } |
| PsiBuilder.Marker marker = builder.mark(); |
| if (expect(TokenKind.INDENT)) { |
| while (!builder.eof() && !matches(TokenKind.DEDENT)) { |
| parseStatement(); |
| } |
| } |
| marker.done(BuildElementTypes.STATEMENT_LIST); |
| } |
| } |