| /* |
| * 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.projectview.parser; |
| |
| import com.google.idea.blaze.base.lang.projectview.language.ProjectViewKeywords; |
| import com.google.idea.blaze.base.lang.projectview.lexer.ProjectViewTokenType; |
| import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewElementType; |
| import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewElementTypes; |
| import com.google.idea.blaze.base.projectview.section.ScalarSectionParser; |
| import com.intellij.lang.PsiBuilder; |
| |
| import javax.annotation.Nullable; |
| |
| /** |
| * Project view psi parser. |
| */ |
| public class ProjectViewPsiParser { |
| |
| private final PsiBuilder builder; |
| |
| public ProjectViewPsiParser(PsiBuilder builder) { |
| this.builder = builder; |
| } |
| |
| public void parseFile() { |
| builder.setDebugMode(true); |
| while (!builder.eof()) { |
| if (matches(ProjectViewTokenType.NEWLINE)) { |
| continue; |
| } |
| parseSection(); |
| } |
| } |
| |
| /** |
| * A block is one of: |
| * - scalar section |
| * - list section |
| */ |
| private void parseSection() { |
| PsiBuilder.Marker marker = builder.mark(); |
| if (matches(ProjectViewTokenType.LIST_KEYWORD)) { |
| expect(ProjectViewTokenType.COLON); |
| skipPastNewline(); |
| parseListItems(); |
| marker.done(ProjectViewElementTypes.LIST_SECTION); |
| return; |
| } |
| if (currentToken() == ProjectViewTokenType.SCALAR_KEYWORD) { |
| ScalarSectionParser parser = ProjectViewKeywords.SCALAR_KEYWORD_MAP.get(builder.getTokenText()); |
| if (parser != null) { |
| parseScalarSection(parser); |
| marker.done(ProjectViewElementTypes.SCALAR_SECTION); |
| return; |
| } |
| } |
| // handle each of the error cases |
| if (matches(ProjectViewTokenType.INDENT)) { |
| skipBlockAndError(marker, "Invalid indentation. Indented lines must be preceded by a list keyword"); |
| return; |
| } |
| if (matches(ProjectViewTokenType.COLON)) { |
| skipBlockAndError(marker, "Invalid section: lines cannot begin with a colon."); |
| return; |
| } |
| skipBlockAndError(marker, "Unrecognized keyword: " + builder.getTokenText()); |
| } |
| |
| private void parseListItems() { |
| while (!builder.eof()) { |
| if (matches(ProjectViewTokenType.NEWLINE)) { |
| continue; |
| } |
| if (!matches(ProjectViewTokenType.INDENT)) { |
| return; |
| } |
| PsiBuilder.Marker marker = builder.mark(); |
| skipToNewlineToken(); |
| marker.done(ProjectViewElementTypes.LIST_ITEM); |
| builder.advanceLexer(); |
| } |
| } |
| |
| private void parseScalarSection(ScalarSectionParser parser) { |
| boolean whitespaceDivider = builder.rawLookup(1) == ProjectViewTokenType.WHITESPACE; |
| builder.advanceLexer(); |
| |
| char divider = parser.getDivider(); |
| if (divider == ' ') { |
| if (!whitespaceDivider) { |
| builder.error("Whitespace divider expected after '" + parser.getName() + "'"); |
| builder.advanceLexer(); |
| } |
| parseScalarItem(); |
| return; |
| } |
| if (whitespaceDivider || !Character.toString(divider).equals(builder.getTokenText())) { |
| builder.error(String.format("'%s' expected", divider)); |
| } |
| if (!whitespaceDivider) { |
| builder.advanceLexer(); |
| } |
| parseScalarItem(); |
| } |
| |
| private void parseScalarItem() { |
| PsiBuilder.Marker marker = builder.mark(); |
| skipToNewlineToken(); |
| marker.done(ProjectViewElementTypes.SCALAR_ITEM); |
| builder.advanceLexer(); |
| } |
| |
| /** |
| * Consumes the current token iff it matches the expected type. Otherwise, returns false |
| */ |
| private boolean matches(ProjectViewTokenType kind) { |
| if (currentToken() == kind) { |
| builder.advanceLexer(); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Consumes the current token if it's of the expected type. Otherwise, returns false and reports an error. |
| */ |
| private boolean expect(ProjectViewTokenType kind) { |
| if (matches(kind)) { |
| return true; |
| } |
| builder.error(String.format("'%s' expected", kind)); |
| return false; |
| } |
| |
| /** |
| * Checks if the upcoming sequence of tokens match that expected. Doesn't advance the parser. |
| */ |
| private boolean atTokenSequence(ProjectViewTokenType... kinds) { |
| for (int i = 0; i < kinds.length; i++) { |
| if (builder.lookAhead(i) != kinds[i]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| @Nullable |
| private ProjectViewTokenType currentToken() { |
| return (ProjectViewTokenType) builder.getTokenType(); |
| } |
| |
| private void skipBlockAndError(PsiBuilder.Marker marker, String message) { |
| skipToNextBlock(); |
| marker.error(message); |
| } |
| |
| /** |
| * Skip to the start of the next unindented line |
| */ |
| private void skipToNextBlock() { |
| while (!builder.eof()) { |
| if (atTokenSequence(ProjectViewTokenType.NEWLINE, ProjectViewTokenType.IDENTIFIER)) { |
| builder.advanceLexer(); |
| return; |
| } |
| builder.advanceLexer(); |
| } |
| } |
| |
| /** |
| * Skip to the start of the next line |
| */ |
| private void skipPastNewline() { |
| while (!builder.eof()) { |
| if (matches(ProjectViewTokenType.NEWLINE)) { |
| return; |
| } |
| builder.advanceLexer(); |
| } |
| } |
| |
| /** |
| * Skip to the end of the current line |
| */ |
| private void skipToNewlineToken() { |
| while (!builder.eof()) { |
| if (currentToken() == ProjectViewTokenType.NEWLINE) { |
| return; |
| } |
| builder.advanceLexer(); |
| } |
| } |
| |
| private void buildTokenElement(ProjectViewElementType type) { |
| PsiBuilder.Marker marker = builder.mark(); |
| builder.advanceLexer(); |
| marker.done(type); |
| } |
| |
| } |