Code Generation for Expression trees

- Ditched Gradle because I couldn't get annotation processing to work,
  switched to maven instead
This commit is contained in:
ctsk
2022-09-06 17:34:55 +02:00
parent 3857580ff0
commit e68f0edc1c
19 changed files with 242 additions and 366 deletions

View File

@@ -0,0 +1,19 @@
package xyz.ctsk.lox.codegen;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.SOURCE)
public @interface Grammar {
Root[] value();
@interface Root {
String name();
Rule[] rules();
}
@interface Rule {
String head();
String[] body();
}
}

View File

@@ -0,0 +1,118 @@
package xyz.ctsk.lox.codegen;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.Element;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.io.IOException;
import java.io.Writer;
import java.util.Set;
import static xyz.ctsk.lox.codegen.Grammar.Rule;
import static xyz.ctsk.lox.codegen.Grammar.Root;
@SupportedAnnotationTypes(
"xyz.ctsk.lox.codegen.Grammar"
)
public class GrammarProcessor extends AbstractProcessor {
private static final int INDENT = 4;
private String getVisitorName(String className, String parentName) {
return "visit%s%s".formatted(className, parentName);
}
@SuppressWarnings("SameParameterValue")
private void writeVisitor(Rule[] rules, String parentName, Writer writer, int indent) throws IOException {
writer.write("""
interface Visitor<R> {
""".indent(indent));
for (var rule : rules) {
var visitorName = getVisitorName(rule.head(), parentName);
var parameter = "%s %s".formatted(rule.head(), rule.head().toLowerCase());
writer.write("""
R %s(%s);
""".formatted(visitorName, parameter).indent(indent + INDENT));
}
writer.write("""
}
""".indent(indent));
}
@SuppressWarnings("SameParameterValue")
private void processRule(Rule rule, String parentBaseName, Writer writer, int indent) throws IOException {
var fields = String.join(", ", rule.body());
var recordName = rule.head();
var visitorName = getVisitorName(recordName, parentBaseName);
writer.write("""
record %s(%s) implements %s {
@Override
public <R> R accept(Visitor<R> visitor) {
return visitor.%s(this);
}
}
""".formatted(recordName, fields, parentBaseName, visitorName).indent(indent));
}
private void processRoot(Root root, Name packageName) {
var baseName = root.name();
var qualifiedName = "%s.%s".formatted(packageName, baseName);
try {
var file = processingEnv.getFiler().createSourceFile(qualifiedName);
try (var writer = file.openWriter()) {
writer.write("""
package %s;
interface %s {
""".formatted(packageName, baseName)
);
writeVisitor(root.rules(), baseName, writer, INDENT);
writer.write("""
<R> R accept(Visitor<R> visitor);
"""
);
for (var rule : root.rules()) {
processRule(rule, baseName, writer, INDENT);
}
writer.write("""
}
""");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void processGrammar(Element element) {
if (element instanceof PackageElement packageElement) {
for (var root : element.getAnnotation(Grammar.class).value()) {
processRoot(root, packageElement.getQualifiedName());
}
} else {
processingEnv.getMessager()
.printMessage(Diagnostic.Kind.ERROR, "@Grammar annotation can only be applied to a package");
}
}
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (var annotation : annotations) {
roundEnv.getElementsAnnotatedWith(annotation)
.forEach(this::processGrammar);
}
return true;
}
}

View File

@@ -0,0 +1 @@
xyz.ctsk.lox.codegen.GrammarProcessor