Paper/paper-api-generator/src/main/java/io/papermc/generator/rewriter/parser/LineParser.java

214 lines
8.7 KiB
Java

package io.papermc.generator.rewriter.parser;
import com.google.common.base.Preconditions;
import io.papermc.generator.rewriter.context.ImportCollector;
import io.papermc.generator.rewriter.parser.closure.AbstractClosure;
import io.papermc.generator.rewriter.parser.closure.Closure;
import io.papermc.generator.rewriter.parser.closure.ClosureType;
import io.papermc.generator.rewriter.parser.step.IterativeStep;
import io.papermc.generator.rewriter.parser.step.StepManager;
import io.papermc.generator.rewriter.parser.step.model.AnnotationSkipSteps;
import io.papermc.generator.rewriter.parser.step.model.ImportStatementSteps;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@ApiStatus.Internal
public class LineParser {
private final StepManager stepManager = new StepManager();
private Closure nearestClosure;
@Nullable
public Closure getNearestClosure() {
return this.nearestClosure;
}
// internal use only or when nearestClosure = null
// doesn't support leaf closure char escape
public boolean tryAdvanceStartClosure(@NotNull ClosureType type, @NotNull StringReader line) {
if (line.trySkipString(type.start)) { // closure has been consumed
Closure previousNearestClosure = this.nearestClosure;
this.nearestClosure = Closure.create(type);
if (previousNearestClosure != null) {
if (ClosureType.LEAFS.contains(previousNearestClosure.getType())) {
throw new ParserException("Nested closure in a leaf closure is not allowed", line);
}
((AbstractClosure) this.nearestClosure).setParent(previousNearestClosure);
}
this.nearestClosure.onStart(line);
return true;
}
return false;
}
// for all closure, leaf closure type should use the other similar method after this one if possible
// ignoreNestedClosureTypes order matter here
public ClosureAdvanceResult tryAdvanceEndClosure(@NotNull Closure closure, @NotNull StringReader line) {
Preconditions.checkState(this.nearestClosure != null && this.nearestClosure.hasUpperClosure(closure), "Need to be in an upper closure of " + closure + " to find its end identifier");
boolean directClosureFound = this.nearestClosure == closure;
boolean canSearchEndClosure = this.nearestClosure == null || directClosureFound;
char previousChar = '\0';
if (line.getCursor() >= 1) {
previousChar = line.peek(-1);
}
ClosureType type = closure.getType();
if (canSearchEndClosure && line.trySkipString(type.end)) { // closure has been consumed
// skip escape closed closure
if (type.end.length() == 1 && type.start.equals(type.end) && ClosureType.ALLOW_ESCAPE.contains(type)) {
if (previousChar == '\\') {
return ClosureAdvanceResult.skip();
}
}
this.nearestClosure.onEnd(line);
if (this.nearestClosure.parent() != null) {
this.nearestClosure = this.nearestClosure.parent();
} else {
this.nearestClosure = null;
}
return ClosureAdvanceResult.find(directClosureFound);
}
return ClosureAdvanceResult.NONE;
}
public boolean trySkipNestedClosures(@NotNull Closure inClosure, @NotNull StringReader line, @NotNull List<ClosureType> computedTypes) {
boolean directClosureFound = this.nearestClosure == inClosure;
boolean isLeaf = this.nearestClosure != null && ClosureType.LEAFS.contains(this.nearestClosure.getType());
if (this.nearestClosure != null && !directClosureFound) {
final boolean advanced;
if (isLeaf) {
advanced = this.tryAdvanceEndClosure(this.nearestClosure.getType(), line) != ClosureTypeAdvanceResult.IGNORED;
} else {
advanced = this.tryAdvanceEndClosure(this.nearestClosure, line).advanced();
}
if (advanced) {
return true;
}
}
if (this.nearestClosure == null || !isLeaf) { // leaf take the priority and doesn't allow any other nested type
for (ClosureType type : computedTypes) {
if (this.tryAdvanceStartClosure(type, line)) {
return true;
}
}
}
return false;
}
// only valid for leaf closure type
public ClosureTypeAdvanceResult tryAdvanceEndClosure(@NotNull ClosureType type, @NotNull StringReader line) {
Preconditions.checkArgument(ClosureType.LEAFS.contains(type), "Only leaf closure can be advanced using its type only, for other, use the closure equivalent method to take in account nested closure");
Preconditions.checkState(this.nearestClosure != null && this.nearestClosure.getType() == type, "Need an direct upper closure of " + type);
char previousChar = '\0';
if (line.getCursor() >= 1) {
previousChar = line.peek(-1);
}
if (line.trySkipString(type.end)) { // closure has been consumed
// skip escape closed closure
if (type.end.length() == 1 && type.start.equals(type.end) && ClosureType.ALLOW_ESCAPE.contains(type)) {
if (previousChar == '\\') {
return ClosureTypeAdvanceResult.SKIPPED;
}
}
this.nearestClosure.onEnd(line);
if (this.nearestClosure.parent() != null) {
this.nearestClosure = this.nearestClosure.parent();
} else {
this.nearestClosure = null;
}
return ClosureTypeAdvanceResult.CHANGED;
}
return ClosureTypeAdvanceResult.IGNORED;
}
public boolean skipComment(@NotNull StringReader line) {
int previousCursor = line.getCursor();
if ((this.nearestClosure != null && this.nearestClosure.getType() == ClosureType.COMMENT) ||
this.tryAdvanceStartClosure(ClosureType.COMMENT, line)) { // open comment?
ClosureTypeAdvanceResult result;
while ((result = this.tryAdvanceEndClosure(ClosureType.COMMENT, line)) != ClosureTypeAdvanceResult.CHANGED && line.canRead()) { // closed comment?
if (result == ClosureTypeAdvanceResult.IGNORED) {
line.skip();
}
}
return line.getCursor() > previousCursor;
}
return false;
}
public boolean skipCommentOrWhitespace(@NotNull StringReader line) {
boolean skipped = false;
while (this.skipComment(line) || line.skipWhitespace() > 0) {
skipped = true;
}
return skipped;
}
public boolean trySkipCommentOrWhitespaceUntil(@NotNull StringReader line, char terminator) {
int previousCursor = line.getCursor();
boolean skipped = this.skipCommentOrWhitespace(line);
if (skipped && line.canRead() && line.peek() != terminator) {
line.setCursor(previousCursor);
skipped = false;
}
return skipped;
}
public boolean peekSingleLineComment(@NotNull StringReader line) {
return line.canRead(2) && line.peek() == '/' && line.peek(1) == '/';
}
public boolean consumeImports(@NotNull StringReader line, @NotNull ImportCollector collector) {
outerLoop:
while (line.canRead()) {
IterativeStep step;
while ((step = this.stepManager.getSteps().poll()) != null) {
step.run(line, this);
if (!line.canRead()) {
break outerLoop;
}
}
if (this.skipCommentOrWhitespace(line)) {
continue;
}
if (this.peekSingleLineComment(line)) {
// check single line comment only after multi line to avoid ignoring the end of multi line comment starting with // on the newline
break;
}
// not commented
char c = line.peek();
if (AnnotationSkipSteps.canStart(c)) { // handle annotation with param to avoid open curly bracket that occur in array argument
this.stepManager.enqueue(new AnnotationSkipSteps());
continue;
} else if (c == '{') {
return true;
} else if (ImportStatementSteps.canStart(line)) {
this.stepManager.enqueue(new ImportStatementSteps(collector));
continue;
}
line.skip();
}
return false;
}
@NotNull
public StepManager getSteps() {
return this.stepManager;
}
}