blob: c23bed929e9d2580a62f6ed153a45dc5b158769e [file] [log] [blame]
// Copyright 2019 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.devtools.build.lib.bazel.rules.ninja.parser;
import com.google.devtools.build.lib.bazel.rules.ninja.file.DeclarationConsumer;
import com.google.devtools.build.lib.bazel.rules.ninja.file.FileFragment;
import com.google.devtools.build.lib.bazel.rules.ninja.file.GenericParsingException;
import com.google.devtools.build.lib.bazel.rules.ninja.lexer.NinjaLexer;
import com.google.devtools.build.lib.bazel.rules.ninja.lexer.NinjaToken;
import com.google.devtools.build.lib.bazel.rules.ninja.parser.NinjaFileParseResult.NinjaPromise;
import com.google.devtools.build.lib.bazel.rules.ninja.pipeline.NinjaPipeline;
import com.google.devtools.build.lib.util.Pair;
import java.io.IOException;
/**
* Ninja parser: an instance will be created per a fragment of Ninja file, to avoid synchronization
* while parsing independent values.
*
* <p>Populates the {@link NinjaFileParseResult} with variables and rules.
*/
public class NinjaParser implements DeclarationConsumer {
private final NinjaPipeline pipeline;
private final NinjaFileParseResult parseResult;
private final String ninjaFileName;
private static final String UNSUPPORTED_TOKEN_MESSAGE =
" is an unsupported type of Ninja lexeme for the parser at this state. "
+ "These are unexpected lexemes at this state for the parser, and running into a "
+ "token of this lexeme hints that the parser did not progress the lexer to a "
+ "valid state during a previous parsing step.";
public NinjaParser(
NinjaPipeline pipeline, NinjaFileParseResult parseResult, String ninjaFileName) {
this.pipeline = pipeline;
this.parseResult = parseResult;
this.ninjaFileName = ninjaFileName;
}
@Override
public void declaration(FileFragment fragment) throws GenericParsingException, IOException {
long offset = fragment.getFragmentOffset();
NinjaLexer lexer = new NinjaLexer(fragment);
if (!lexer.hasNextToken()) {
throw new IllegalStateException("Empty fragment passed as declaration.");
}
NinjaToken token = lexer.nextToken();
// Skip possible leading newlines in the fragment for parsing.
while (lexer.hasNextToken() && NinjaToken.NEWLINE.equals(token)) {
token = lexer.nextToken();
}
if (!lexer.hasNextToken()) {
// If fragment contained only newlines.
return;
}
long declarationStart = offset + lexer.getLastStart();
lexer.undo();
NinjaParserStep parser =
new NinjaParserStep(lexer, pipeline.getPathFragmentInterner(), pipeline.getNameInterner());
switch (token) {
case IDENTIFIER:
Pair<String, NinjaVariableValue> variable = parser.parseVariable();
parseResult.addVariable(variable.getFirst(), declarationStart, variable.getSecond());
break;
case RULE:
NinjaRule rule = parser.parseNinjaRule();
parseResult.addRule(declarationStart, rule);
break;
case POOL:
parseResult.addPool(declarationStart, parser.parseNinjaPool());
break;
case INCLUDE:
NinjaVariableValue includeStatement = parser.parseIncludeStatement();
NinjaPromise<NinjaFileParseResult> includeFuture =
pipeline.createChildFileParsingPromise(
includeStatement, declarationStart, ninjaFileName);
parseResult.addIncludeScope(declarationStart, includeFuture);
break;
case SUBNINJA:
NinjaVariableValue subNinjaStatement = parser.parseSubNinjaStatement();
NinjaPromise<NinjaFileParseResult> subNinjaFuture =
pipeline.createChildFileParsingPromise(
subNinjaStatement, declarationStart, ninjaFileName);
parseResult.addSubNinjaScope(declarationStart, subNinjaFuture);
break;
case BUILD:
FileFragment targetFragment;
if (declarationStart == offset) {
targetFragment = fragment;
} else {
// Method subFragment accepts only the offset *inside fragment*.
// So we should subtract the offset of fragment's buffer in file
// (fragment.getFileOffset()),
// and start of fragment inside buffer (fragment.getStartIncl()).
long fragmentStart =
declarationStart - fragment.getFileOffset() - fragment.getStartIncl();
// While the absolute offset is typed as long (because of larger ninja files), the
// fragments are only at most Integer.MAX_VALUE long, so fragmentStart cannot be
// larger than that. Sanity check this here.
if (fragmentStart > Integer.MAX_VALUE) {
throw new GenericParsingException(
String.format(
"The fragmentStart value %s is not expected to be larger than max-int, "
+ "since each fragment is at most max-int long.",
fragmentStart));
}
targetFragment = fragment.subFragment((int) fragmentStart, fragment.length());
}
parseResult.addTarget(targetFragment);
break;
case DEFAULT:
// Do nothing.
break;
case ZERO:
case EOF:
return;
case COLON:
case EQUALS:
case ESCAPED_TEXT:
case INDENT:
case NEWLINE:
case PIPE:
case PIPE2:
case PIPE_AT:
case TEXT:
case VARIABLE:
throw new UnsupportedOperationException(token.name() + UNSUPPORTED_TOKEN_MESSAGE);
case ERROR:
throw new GenericParsingException(lexer.getError());
// Omit default case on purpose. Explicitly specify *all* NinjaToken enum cases above or the
// compilation will fail.
}
}
}