blob: 2188c83232f3a9377baf1b60a13bb1ecebe78a04 [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.idea.blaze.base.lang.buildfile.lexer.BuildToken;
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 com.intellij.psi.tree.IElementType;
import javax.annotation.Nullable;
import java.util.EnumSet;
import java.util.Set;
/**
* Base class for BUILD file component parsers
*/
public abstract class Parsing {
// Keywords that exist in Python which we don't parse.
protected static final EnumSet<TokenKind> FORBIDDEN_KEYWORDS =
EnumSet.of(TokenKind.AS, TokenKind.ASSERT,
TokenKind.DEL, TokenKind.EXCEPT, TokenKind.FINALLY, TokenKind.FROM, TokenKind.GLOBAL,
TokenKind.IMPORT, TokenKind.IS, TokenKind.LAMBDA, TokenKind.NONLOCAL, TokenKind.RAISE,
TokenKind.TRY, TokenKind.WITH, TokenKind.WHILE, TokenKind.YIELD);
protected ParsingContext context;
protected PsiBuilder builder;
public Parsing(ParsingContext context) {
this.context = context;
this.builder = context.builder;
}
protected ExpressionParsing getExpressionParser() {
return context.expressionParser;
}
/**
* @return true if a string was parsed
*/
protected boolean parseStringLiteral(boolean alwaysConsume) {
if (currentToken() != TokenKind.STRING) {
expect(TokenKind.STRING, alwaysConsume);
return false;
}
buildTokenElement(BuildElementTypes.STRING_LITERAL);
if (currentToken() == TokenKind.STRING) {
builder.error("implicit string concatenation is forbidden; use the '+' operator");
}
return true;
}
protected void buildTokenElement(BuildElementType type) {
PsiBuilder.Marker marker = builder.mark();
builder.advanceLexer();
marker.done(type);
}
/**
* Consume tokens until we reach the first token that has a kind that is in the set of terminatingTokens.
*/
protected void syncTo(Set<TokenKind> terminatingTokens) {
// read past the problematic token
while (!atAnyOfTokens(terminatingTokens)) {
builder.advanceLexer();
}
}
/**
* Consume tokens until we consume the first token that has a kind that is in the set of terminatingTokens.
*/
protected void syncPast(Set<TokenKind> terminatingTokens) {
// read past the problematic token
while (!matchesAnyOf(terminatingTokens)) {
builder.advanceLexer();
}
}
/**
* Consumes the current token iff it's one of the expected types.<br>
* Otherwise, returns false and reports an error.
*/
protected boolean expect(TokenKind kind) {
return expect(kind, false);
}
/**
* Consumes the current token if 'alwaysConsume' is true or if it's one of the expected types.<br>
* Otherwise, returns false and reports an error.
*/
protected boolean expect(TokenKind kind, boolean alwaysConsume) {
return expect(kind, String.format("'%s' expected", kind), alwaysConsume);
}
/**
* Consumes the current token if 'alwaysConsume' is true or if it's one of the expected types.<br>
* Otherwise, returns false and reports an error.
*/
protected boolean expect(TokenKind kind, String message, boolean alwaysConsume) {
TokenKind current = currentToken();
if (current == kind || alwaysConsume) {
builder.advanceLexer();
}
if (current != kind) {
builder.error(message);
return false;
}
return true;
}
/**
* Checks if we're at the current sequence of tokens. If so, consumes them.
*/
protected boolean matchesSequence(TokenKind... kinds) {
PsiBuilder.Marker marker = builder.mark();
for (TokenKind kind : kinds) {
if (!matches(kind)) {
marker.rollbackTo();
return false;
}
}
marker.drop();
return true;
}
/**
* Consumes the current token iff it matches the expected type. Otherwise, returns false
*/
protected boolean matches(TokenKind kind) {
if (currentToken() == kind) {
builder.advanceLexer();
return true;
}
return false;
}
/**
* Consumes the current token iff it matches one of the expected types. Otherwise, returns false
*/
protected boolean matchesAnyOf(TokenKind... kinds) {
TokenKind current = currentToken();
for (TokenKind kind : kinds) {
if (kind == current) {
builder.advanceLexer();
return true;
}
}
return false;
}
/**
* Consumes the current token iff it's one of the expected types. Otherwise, returns false
*/
protected boolean matchesAnyOf(Set<TokenKind> kinds) {
if (kinds.contains(currentToken())) {
builder.advanceLexer();
return true;
}
return false;
}
/**
* Checks if the upcoming sequence of tokens match that expected. Doesn't advance the parser.
*/
protected boolean atTokenSequence(TokenKind... kinds) {
for (int i = 0; i < kinds.length; i++) {
if (kindFromElement(builder.lookAhead(i)) != kinds[i]) {
return false;
}
}
return true;
}
/**
* Checks if the current token matches the expected kind. Doesn't advance the parser.
*/
protected boolean atToken(TokenKind kind) {
return currentToken() == kind;
}
/**
* Checks if the current token matches any one of the expected kinds. Doesn't advance the parser.
*/
protected boolean atAnyOfTokens(TokenKind... kinds) {
TokenKind current = currentToken();
for (TokenKind kind : kinds) {
if (current == kind) {
return true;
}
}
return false;
}
/**
* Checks if the current token matches any one of the expected kinds. Doesn't advance the parser.
*/
protected boolean atAnyOfTokens(Set<TokenKind> kinds) {
return kinds.contains(currentToken());
}
@Nullable
protected TokenKind currentToken() {
return builder.eof() ? TokenKind.EOF : kindFromElement(builder.getTokenType());
}
@Nullable
protected TokenKind kindFromElement(IElementType type) {
if (type == null) {
return null;
}
if (!(type instanceof BuildToken)) {
throw new RuntimeException("Invalid type: " + type + " of class " + type.getClass());
}
TokenKind kind = ((BuildToken) type).kind;
checkForbiddenKeywords(kind);
return kind;
}
private void checkForbiddenKeywords(TokenKind kind) {
if (!FORBIDDEN_KEYWORDS.contains(kind)) {
return;
}
builder.error(forbiddenKeywordError(kind));
}
protected String forbiddenKeywordError(TokenKind kind) {
assert FORBIDDEN_KEYWORDS.contains(kind);
switch (kind) {
case ASSERT: return "'assert' not supported, use 'fail' instead";
case TRY: return "'try' not supported, all exceptions are fatal";
case IMPORT: return "'import' not supported, use 'load' instead";
case IS: return "'is' not supported, use '==' instead";
case LAMBDA: return "'lambda' not supported, declare a function instead";
case RAISE: return "'raise' not supported, use 'fail' instead";
case WHILE: return "'while' not supported, use 'for' instead";
default: return "keyword '" + kind + "' not supported";
}
}
}