blob: fdc8da95a59070c52dc2f81ab5272f469182a242 [file] [log] [blame]
/*
* 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);
}
}