unflatten closure representation

This commit is contained in:
Lulu13022002 2024-04-22 18:40:27 +02:00
parent b0d9d2d6ed
commit 8294f7dca4
No known key found for this signature in database
GPG Key ID: 491C8F0B8ACDEB01
10 changed files with 318 additions and 126 deletions

View File

@ -1,17 +1,14 @@
package io.papermc.generator.rewriter.parser;
public enum ClosureAdvanceResult {
/**
* When the pointer advance on a closure. Escaped end closure are skipped!
*/
CHANGED,
/**
* When the pointer doesn't advance and cannot find the closure.
*/
IGNORED,
/**
* When the pointer advance by skipping strong closure within weak closure
* or by reaching a weak end closure escaped by '\' char preceding it.
*/
SKIPPED
public record ClosureAdvanceResult(boolean found, boolean advanced) {
public static final ClosureAdvanceResult NONE = new ClosureAdvanceResult(false, false);
public static ClosureAdvanceResult find(boolean found) {
return new ClosureAdvanceResult(found, true);
}
public static ClosureAdvanceResult skip() {
return new ClosureAdvanceResult(false, true);
}
}

View File

@ -1,32 +0,0 @@
package io.papermc.generator.rewriter.parser;
import java.util.EnumSet;
public enum ClosureType { // todo refactor? this become bigger than initially planned
PARENTHESIS("(", ")", false),
CURLY_BRACKET("{", "}", false),
BRACKET("[", "]", false),
COMMENT("/*", "*/"),
// escape allowed
// todo could handle the extra newline needed after """
PARAGRAPH("\"\"\"", "\"\"\""), // order matter here this one must be checked before string for the strong check
STRING("\"", "\""),
CHAR("'", "'");
public final String start;
public final String end;
public final boolean strong; // once within this closure ignore all the weak ones
ClosureType(String start, String end) {
this(start, end, true);
}
ClosureType(String start, String end, boolean strong) {
this.start = start;
this.end = end;
this.strong = strong;
}
public static final EnumSet<ClosureType> ALLOW_ESCAPE = EnumSet.of(STRING, CHAR, PARAGRAPH);
}

View File

@ -0,0 +1,20 @@
package io.papermc.generator.rewriter.parser;
import io.papermc.generator.rewriter.parser.closure.ClosureType;
public enum ClosureTypeAdvanceResult {
/**
* When the pointer advance on a closure. Escaped end closure are skipped!
*/
CHANGED,
/**
* When the pointer doesn't advance and cannot find the closure.
*/
IGNORED,
/**
* When the pointer advance by reaching a leaf closure escaped by '\' char preceding it.
*
* @see ClosureType#ALLOW_ESCAPE
*/
SKIPPED
}

View File

@ -1,93 +1,143 @@
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.AnnotationSteps;
import io.papermc.generator.rewriter.parser.step.model.ImportSteps;
import io.papermc.generator.rewriter.parser.step.model.AnnotationSkipSteps;
import io.papermc.generator.rewriter.parser.step.model.ImportStatementSteps;
import org.jetbrains.annotations.ApiStatus;
import java.util.EnumSet;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@ApiStatus.Internal
public class LineParser {
private final Set<ClosureType> closures = EnumSet.noneOf(ClosureType.class);
private final StepManager stepManager = new StepManager();
private ClosureType strongClosure;
private Closure nearestClosure;
public boolean advanceEnclosure(ClosureType type, StringReader line) {
return this.advanceEnclosure0(type, line) == ClosureAdvanceResult.CHANGED;
@Nullable
public Closure getNearestClosure() {
return this.nearestClosure;
}
public ClosureAdvanceResult advanceEnclosure0(ClosureType type, StringReader line) {
boolean inside = this.closures.contains(type);
String closure = !inside ? type.start : type.end;
// skip comments, char and string inside the upper closure for weak closure type
if (!type.strong && inside) {
ClosureType skipClosure = this.strongClosure;
if (skipClosure == null) {
for (ClosureType strongClosure : ClosureType.values()) {
if (!strongClosure.strong) {
continue;
}
if (this.advanceEnclosure(strongClosure, line)) {
skipClosure = strongClosure;
break;
}
// 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);
}
if (skipClosure != null) {
ClosureAdvanceResult result;
while ((result = this.advanceEnclosure0(skipClosure, line)) != ClosureAdvanceResult.CHANGED && line.canRead()) {
if (result == ClosureAdvanceResult.IGNORED) {
line.skip();
}
}
return ClosureAdvanceResult.SKIPPED;
}
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);
}
if (line.trySkipString(closure)) { // closure has been consumed
if (!inside) {
if (!this.closures.add(type)) {
throw new ParserException("Nested same closure detected for " + type, line);
}
} else {
// skip escape closed closure
if (closure.length() == 1 && type.start.equals(type.end) && ClosureType.ALLOW_ESCAPE.contains(type)) {
if (previousChar == '\\') {
return ClosureAdvanceResult.SKIPPED;
}
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();
}
}
if (!this.closures.remove(type)) {
throw new ParserException("Missing open closure? for " + type, line);
}
this.nearestClosure.onEnd(line);
if (this.nearestClosure.parent() != null) {
this.nearestClosure = this.nearestClosure.parent();
} else {
this.nearestClosure = null;
}
if (type.strong) {
this.strongClosure = !inside ? type : null;
}
return ClosureAdvanceResult.CHANGED;
return ClosureAdvanceResult.find(directClosureFound);
}
return ClosureAdvanceResult.IGNORED;
return ClosureAdvanceResult.NONE;
}
public boolean skipComment(StringReader line) {
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.closures.contains(ClosureType.COMMENT) || this.advanceEnclosure(ClosureType.COMMENT, line)) { // open comment?
ClosureAdvanceResult result;
while ((result = this.advanceEnclosure0(ClosureType.COMMENT, line)) != ClosureAdvanceResult.CHANGED && line.canRead()) { // closed comment?
if (result == ClosureAdvanceResult.IGNORED) {
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();
}
}
@ -96,7 +146,7 @@ public class LineParser {
return false;
}
public boolean skipCommentOrWhitespace(StringReader line) {
public boolean skipCommentOrWhitespace(@NotNull StringReader line) {
boolean skipped = false;
while (this.skipComment(line) || line.skipWhitespace() > 0) {
skipped = true;
@ -104,7 +154,7 @@ public class LineParser {
return skipped;
}
public boolean trySkipCommentOrWhitespaceUntil(StringReader line, char terminator) {
public boolean trySkipCommentOrWhitespaceUntil(@NotNull StringReader line, char terminator) {
int previousCursor = line.getCursor();
boolean skipped = this.skipCommentOrWhitespace(line);
if (skipped && line.canRead() && line.peek() != terminator) {
@ -115,11 +165,11 @@ public class LineParser {
return skipped;
}
public boolean peekSingleLineComment(StringReader line) {
return line.peek() == '/' && line.canRead(2) && line.peek(1) == '/';
public boolean peekSingleLineComment(@NotNull StringReader line) {
return line.canRead(2) && line.peek() == '/' && line.peek(1) == '/';
}
public boolean consumeImports(StringReader line, ImportCollector collector) {
public boolean consumeImports(@NotNull StringReader line, @NotNull ImportCollector collector) {
outerLoop:
while (line.canRead()) {
IterativeStep step;
@ -141,13 +191,13 @@ public class LineParser {
// not commented
char c = line.peek();
if (AnnotationSteps.canStart(c)) { // handle annotation with param to avoid open curly bracket that occur in array argument
this.stepManager.enqueue(new AnnotationSteps());
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 (ImportSteps.canStart(line)) {
this.stepManager.enqueue(new ImportSteps(collector));
} else if (ImportStatementSteps.canStart(line)) {
this.stepManager.enqueue(new ImportStatementSteps(collector));
continue;
}
@ -156,6 +206,7 @@ public class LineParser {
return false;
}
@NotNull
public StepManager getSteps() {
return this.stepManager;
}

View File

@ -0,0 +1,33 @@
package io.papermc.generator.rewriter.parser.closure;
public class AbstractClosure implements Closure {
private final ClosureType type;
private Closure parent;
protected AbstractClosure(ClosureType type) {
this.type = type;
}
@Override
public ClosureType getType() {
return this.type;
}
@Override
public Closure parent() {
return this.parent;
}
@Override
public String toString() {
return "Closure[" + this.type.name() + "](start=" + this.type.start + ", end=" + this.type.end + ")";
}
public void setParent(Closure parent) {
if (this.parent != null && parent != null) {
throw new IllegalStateException("Cannot set this parent closure since it is already in a closure: " + this.parent);
}
this.parent = parent;
}
}

View File

@ -0,0 +1,52 @@
package io.papermc.generator.rewriter.parser.closure;
import io.papermc.generator.rewriter.parser.ParserException;
import io.papermc.generator.rewriter.parser.StringReader;
import org.jetbrains.annotations.Nullable;
import java.util.function.Supplier;
public interface Closure {
ClosureType getType();
@Nullable
Closure parent();
default boolean hasUpperClosure(Closure closure) {
Closure parent = this;
while (parent != null) {
if (parent == closure) {
return true;
}
parent = parent.parent();
}
return false;
}
default void onStart(StringReader line) {
}
default void onEnd(StringReader line) {
}
static Closure create(ClosureType type) {
class SpecialClosure {
public static final Supplier<Closure> PARAGRAPH = () -> new AbstractClosure(ClosureType.PARAGRAPH) {
@Override
public void onStart(StringReader line) {
if (line.canRead()) {
throw new ParserException("Paragraph closure start must be followed by a newline", line);
}
}
};
}
if (type == ClosureType.PARAGRAPH) {
return SpecialClosure.PARAGRAPH.get();
}
return new AbstractClosure(type);
}
}

View File

@ -0,0 +1,38 @@
package io.papermc.generator.rewriter.parser.closure;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
public enum ClosureType {
PARENTHESIS("(", ")"),
CURLY_BRACKET("{", "}"),
BRACKET("[", "]"),
COMMENT("/*", "*/"),
PARAGRAPH("\"\"\"", "\"\"\""),
STRING("\"", "\""),
CHAR("'", "'");
public final String start;
public final String end;
ClosureType(String start, String end) {
this.start = start;
this.end = end;
}
public static final EnumSet<ClosureType> ALLOW_ESCAPE = EnumSet.of(STRING, CHAR, PARAGRAPH);
public static final EnumSet<ClosureType> LEAFS = EnumSet.of(COMMENT, STRING, CHAR, PARAGRAPH);
private static final List<ClosureType> CHECK_FIRST = List.of(PARAGRAPH);
public static List<ClosureType> prioritySort(Set<ClosureType> types) {
return types.stream()
.sorted(Comparator.comparingInt(o -> {
int index = CHECK_FIRST.indexOf(o);
return index == -1 ? CHECK_FIRST.size() : index;
}))
.toList();
}
}

View File

@ -1,19 +1,23 @@
package io.papermc.generator.rewriter.parser.step.model;
import io.papermc.generator.rewriter.parser.ClosureAdvanceResult;
import io.papermc.generator.rewriter.parser.ClosureType;
import io.papermc.generator.rewriter.parser.LineParser;
import io.papermc.generator.rewriter.parser.ParserException;
import io.papermc.generator.rewriter.parser.ProtoTypeName;
import io.papermc.generator.rewriter.parser.ClosureAdvanceResult;
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.StepHolder;
import io.papermc.generator.rewriter.parser.StringReader;
import io.papermc.generator.utils.NamingManager;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
// start once "@" is detected unless commented
// order is: skipAtSign -> skipPartName -> checkOpenParenthesis (-> skipParentheses)
public final class AnnotationSteps implements StepHolder {
// order is: skipAtSign -> skipPartName -> checkAnnotationName -> checkOpenParenthesis (-> skipParentheses)
public final class AnnotationSkipSteps implements StepHolder {
public static boolean canStart(char currentChar) {
return currentChar == '@';
@ -22,6 +26,7 @@ public final class AnnotationSteps implements StepHolder {
private final IterativeStep skipParenthesesStep = IterativeStep.createUntil(this::skipParentheses);
private @MonotonicNonNull ProtoTypeName name;
private Closure parenthesisClosure;
public void skipAtSign(StringReader line, LineParser parser) {
line.skip(); // skip @
@ -43,20 +48,29 @@ public final class AnnotationSteps implements StepHolder {
this.name = line.getPartNameUntil('(', parser::skipCommentOrWhitespace, this.name);
if (line.canRead() && parser.peekSingleLineComment(line)) {
if (parser.peekSingleLineComment(line)) {
// ignore single line comment at the end and allow the name to continue
line.setCursor(line.getTotalLength());
}
return !line.canRead();
}
private static final List<ClosureType> IGNORE_NESTED_CLOSURES;
static {
Set<ClosureType> types = new HashSet<>(ClosureType.LEAFS.size() + 1);
types.addAll(ClosureType.LEAFS);
types.add(ClosureType.PARENTHESIS); // skip nested annotation too
IGNORE_NESTED_CLOSURES = ClosureType.prioritySort(types);
}
public boolean skipParentheses(StringReader line, LineParser parser) {
ClosureAdvanceResult result;
while ((result = parser.advanceEnclosure0(ClosureType.PARENTHESIS, line)) != ClosureAdvanceResult.CHANGED) { // closed parenthesis?
while (!(result = parser.tryAdvanceEndClosure(this.parenthesisClosure, line)).found()) {
if (!line.canRead()) { // parenthesis on another line?
return true;
}
if (result == ClosureAdvanceResult.IGNORED) {
if (!result.advanced() && !parser.trySkipNestedClosures(this.parenthesisClosure, line, IGNORE_NESTED_CLOSURES)) {
line.skip();
}
}
@ -88,7 +102,8 @@ public final class AnnotationSteps implements StepHolder {
return true;
}
if (parser.advanceEnclosure(ClosureType.PARENTHESIS, line)) { // open parenthesis?
if (parser.tryAdvanceStartClosure(ClosureType.PARENTHESIS, line)) { // open parenthesis?
this.parenthesisClosure = parser.getNearestClosure();
parser.getSteps().addPriority(this.skipParenthesesStep);
}
return false;

View File

@ -12,7 +12,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// start once "import" is detected unless commented
// order is: enforceSpace -> checkStatic (-> enforceSpace) -> getPartName (-> skipUntilSemicolonAfterStar) -> collectImport
public final class ImportSteps implements StepHolder {
public final class ImportStatementSteps implements StepHolder {
public static boolean canStart(StringReader line) {
return line.trySkipString("import");
@ -28,12 +28,12 @@ public final class ImportSteps implements StepHolder {
private boolean isStatic;
private @MonotonicNonNull ProtoTypeName name;
public ImportSteps(ImportCollector collector) {
public ImportStatementSteps(ImportCollector collector) {
this.collector = collector;
}
public void enforceSpace(StringReader line, LineParser parser) {
if (line.canRead() && parser.peekSingleLineComment(line)) {
if (parser.peekSingleLineComment(line)) {
// ignore single line comment at the end of import/static
line.setCursor(line.getTotalLength());
return;

View File

@ -1,6 +1,10 @@
package io.papermc.generator.rewriter.data.sample.parser.area;
@AnnotationTrapClass.Trapped(strings = {"trap: ) \" ) {"}, chars = {')', '{', '\''}, paragraphs = """
@AnnotationTrapClass.Trapped(strings = {"trap: ) \" ) {"}, annotation = @AnnotationTrapClass.Trapped2(strings = {"trap: ) \" ) {"}, chars = {')', '{', '\''}, paragraphs = """
)
\"\"\"
{
""", clazz = AnnotationTrapClass.Trapped.class), chars = {')', '{', '\''}, paragraphs = """
)
\"\"\"
{
@ -13,5 +17,19 @@ public class AnnotationTrapClass { // << 33
char[] chars();
String[] paragraphs();
Trapped2 annotation();
}
@interface Trapped2 {
String[] strings();
char[] chars();
String[] paragraphs();
Class<? extends Trapped> clazz();
}
}