| /* |
| * 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"; |
| } |
| } |
| |
| } |