blob: 2dcc3ea6fd9e06c2641eb0dd4c2c4f1735b42495 [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.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);
}
}