Skip to content

Commit

Permalink
Add field processing
Browse files Browse the repository at this point in the history
  • Loading branch information
Akirathan committed Oct 10, 2024
1 parent f6bf733 commit 1870ef2
Show file tree
Hide file tree
Showing 5 changed files with 302 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.enso.runtime.parser.processor;

import javax.lang.model.element.TypeElement;

final class Field {
private final TypeElement type;

/** Name of the field (identifier). */
private final String name;

/** If the field can be {@code null}. */
private final boolean nullable;

private final boolean isChild;

Field(TypeElement type, String name, boolean nullable, boolean isChild) {
this.type = type;
this.name = name;
this.nullable = nullable;
this.isChild = isChild;
}

boolean isChild() {
return isChild;
}

boolean isNullable() {
return nullable;
}

String getName() {
return name;
}

String getSimpleTypeName() {
return type.getSimpleName().toString();
}

String getQualifiedTypeName() {
return type.getQualifiedName().toString();
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
package org.enso.runtime.parser.processor;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.SimpleElementVisitor14;
import org.enso.runtime.parser.dsl.IRChild;
import org.enso.runtime.parser.dsl.IRNode;

/**
* Representation of an interface annotated with {@link org.enso.runtime.parser.dsl.IRNode}. Takes
Expand All @@ -12,8 +19,10 @@
*/
final class IRNodeElement {
private final ProcessingEnvironment processingEnv;
private final String recordName;
private final List<Field> fields;

static final String IMPORTS =
private static final String IMPORTS =
"""
import java.util.UUID;
import java.util.function.Function;
Expand All @@ -27,9 +36,102 @@ final class IRNodeElement {
import scala.collection.immutable.List;
""";

IRNodeElement(ProcessingEnvironment processingEnv, TypeElement irNodeInterface) {
/**
* @param processingEnv
* @param irNodeInterface
* @param recordName Simple name (non-qualified) of the newly generated record.
*/
IRNodeElement(
ProcessingEnvironment processingEnv, TypeElement irNodeInterface, String recordName) {
assert !recordName.contains(".") : "Record name should be simple, not qualified";
this.processingEnv = processingEnv;
var elemsInInterface = irNodeInterface.getEnclosedElements();
this.recordName = recordName;
this.fields = getAllFields(irNodeInterface);
}

/** Returns string representation of all necessary imports. */
String imports() {
var importsForFields =
fields.stream()
.map(field -> "import " + field.getQualifiedTypeName() + ";")
.distinct()
.collect(Collectors.joining(System.lineSeparator()));
var allImports = IMPORTS + System.lineSeparator() + importsForFields;
return allImports;
}

/**
* Collects all abstract methods (with no parameters) from this interface and all the interfaces
* that are extended by this interface. Every abstract method corresponds to a single field in the
* newly generated record. Abstract methods annotated with {@link IRChild} are considered IR
* children.
*
* @param irNodeInterface Type element of the interface annotated with {@link IRNode}.
* @return List of fields
*/
private List<Field> getAllFields(TypeElement irNodeInterface) {
var fields = new ArrayList<Field>();

var elemVisitor =
new SimpleElementVisitor14<Void, Void>() {
@Override
protected Void defaultAction(Element e, Void unused) {
for (var childElem : e.getEnclosedElements()) {
childElem.accept(this, unused);
}
return null;
}

@Override
public Void visitExecutable(ExecutableElement e, Void unused) {
if (e.getParameters().isEmpty()) {
var retType = e.getReturnType();
var retTypeElem = (TypeElement) processingEnv.getTypeUtils().asElement(retType);
var name = e.getSimpleName().toString();
var childAnnot = e.getAnnotation(IRChild.class);
boolean isChild = false;
boolean isNullable = false;
if (childAnnot != null) {
ensureIsSubtypeOfIR(retTypeElem);
isChild = true;
isNullable = !childAnnot.required();
}
fields.add(new Field(retTypeElem, name, isNullable, isChild));
}
return super.visitExecutable(e, unused);
}
};
irNodeInterface.accept(elemVisitor, null);
return fields;
}

private void ensureIsSubtypeOfIR(TypeElement typeElem) {
if (!Utils.isSubtypeOfIR(typeElem.asType(), processingEnv)) {
Utils.printError(
"Method annotated with @IRChild must return a subtype of IR interface",
typeElem,
processingEnv.getMessager());
}
}

/**
* Returns string representation of record fields. Meant to be inside the generated record
* definition.
*/
String fields() {
var userDefinedFields =
fields.stream()
.map(field -> field.getSimpleTypeName() + " " + field.getName())
.collect(Collectors.joining(", " + System.lineSeparator()));
var code =
"""
$userDefinedFields,
DiagnosticStorage diagnostics,
MetadataStorage passData,
IdentifiedLocation location
"""
.replace("$userDefinedFields", userDefinedFields);
return indent(code, 2);
}

/**
Expand Down Expand Up @@ -95,7 +197,86 @@ public String showCode(int indent) {
throw new UnsupportedOperationException("unimplemented");
}
""";
var indentedCode = code.lines().map(line -> " " + line).collect(Collectors.joining("\n"));
return indentedCode;
return indent(code, 2);
}

/**
* Returns string representation of the code for the builder - that is a nested class that allows
* to build the record.
*
* @return Code of the builder
*/
String builder() {
var fieldDeclarations =
fields.stream()
.map(
field ->
"""
$fieldType $fieldName;
"""
.replace("$fieldName", field.getName())
.replace("$fieldType", field.getSimpleTypeName()))
.collect(Collectors.joining(System.lineSeparator()));

var fieldSetters =
fields.stream()
.map(
field ->
"""
public Builder $fieldName($fieldType $fieldName) {
this.$fieldName = $fieldName;
return this;
}
"""
.replace("$fieldName", field.getName())
.replace("$fieldType", field.getSimpleTypeName()))
.collect(Collectors.joining(System.lineSeparator()));

// Validation code for all non-nullable fields
var validationCode =
fields.stream()
.filter(field -> !field.isNullable())
.map(
field ->
"""
if (this.$fieldName == null) {
throw new IllegalArgumentException("$fieldName is required");
}
"""
.replace("$fieldName", field.getName()))
.collect(Collectors.joining(System.lineSeparator()));

var fieldList = fields.stream().map(Field::getName).collect(Collectors.joining(", "));

var code =
"""
public static final class Builder {
$fieldDeclarations
$fieldSetters
public $recordName build() {
validate();
// DiagnosticStorage, MetadataStorage, IdentifiedLocation are null initially.
return new $recordName($fieldList, null, null, null);
}
private void validate() {
$validationCode
}
}
"""
.replace("$fieldDeclarations", fieldDeclarations)
.replace("$fieldSetters", fieldSetters)
.replace("$recordName", recordName)
.replace("$fieldList", fieldList)
.replace("$validationCode", validationCode);
return indent(code, 2);
}

private static String indent(String code, int indentation) {
return code.lines()
.map(line -> " ".repeat(indentation) + line)
.collect(Collectors.joining(System.lineSeparator()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic.Kind;
import javax.tools.JavaFileObject;
import org.enso.runtime.parser.dsl.IRChild;
import org.enso.runtime.parser.dsl.IRNode;
Expand Down Expand Up @@ -62,23 +61,32 @@ private void processIrNode(Element irNodeElem) {
printError("Failed to create source file for IRNode", irNodeElem);
}
assert srcGen != null;
var irNodeElement = new IRNodeElement(processingEnv, irNodeTypeElem);
var irNodeElement = new IRNodeElement(processingEnv, irNodeTypeElem, newRecordName);
try {
try (var lineWriter = new PrintWriter(srcGen.openWriter())) {
var code =
"""
$imports
public record $recordName (
String field
$fields
) implements $interfaceName {
public static Builder builder() {
return new Builder();
}
$overrideIRMethods
$builder
}
"""
.replace("$imports", IRNodeElement.IMPORTS)
.replace("$imports", irNodeElement.imports())
.replace("$fields", irNodeElement.fields())
.replace("$recordName", newRecordName)
.replace("$interfaceName", irNodeInterfaceName)
.replace("$overrideIRMethods", irNodeElement.overrideIRMethods());
.replace("$overrideIRMethods", irNodeElement.overrideIRMethods())
.replace("$builder", irNodeElement.builder());
lineWriter.println(code);
lineWriter.println();
}
Expand All @@ -96,9 +104,7 @@ private String packageName(Element elem) {
}

private boolean isSubtypeOfIR(TypeMirror type) {
var irType =
processingEnv.getElementUtils().getTypeElement("org.enso.compiler.core.IR").asType();
return processingEnv.getTypeUtils().isAssignable(type, irType);
return Utils.isSubtypeOfIR(type, processingEnv);
}

private Set<Element> findChildElements(Element irNodeElem) {
Expand All @@ -108,6 +114,6 @@ private Set<Element> findChildElements(Element irNodeElem) {
}

private void printError(String msg, Element elem) {
processingEnv.getMessager().printMessage(Kind.ERROR, msg, elem);
Utils.printError(msg, elem, processingEnv.getMessager());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.enso.runtime.parser.processor;

import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic.Kind;

final class Utils {
private Utils() {}

static boolean isSubtypeOfIR(TypeMirror type, ProcessingEnvironment processingEnv) {
var irType =
processingEnv.getElementUtils().getTypeElement("org.enso.compiler.core.IR").asType();
return processingEnv.getTypeUtils().isAssignable(type, irType);
}

static void printError(String msg, Element elem, Messager messager) {
messager.printMessage(Kind.ERROR, msg, elem);
}
}
Loading

0 comments on commit 1870ef2

Please sign in to comment.