diff --git a/CHANGELOG.md b/CHANGELOG.md index ca12ddb34059..762a0d931bf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -210,6 +210,8 @@ - [Move Builtin Types and Methods definitions to stdlib][3363] - [Reduce boilerplate by generating BuiltinMethod nodes from simple method signatures][3444] +- [Generate boilerplate classes related to error handling and varargs in + builtins from method signatures][3454] [3227]: https://github.com/enso-org/enso/pull/3227 [3248]: https://github.com/enso-org/enso/pull/3248 @@ -229,6 +231,7 @@ [3363]: https://github.com/enso-org/enso/pull/3363 [3444]: https://github.com/enso-org/enso/pull/3444 [3453]: https://github.com/enso-org/enso/pull/3453 +[3454]: https://github.com/enso-org/enso/pull/3454 # Enso 2.0.0-alpha.18 (2021-10-12) diff --git a/build.sbt b/build.sbt index 002a7766b3e0..003a30e4c420 100644 --- a/build.sbt +++ b/build.sbt @@ -157,10 +157,11 @@ Global / onChangedBuildSource := ReloadOnSourceChanges // ============================================================================ ThisBuild / javacOptions ++= Seq( - "-encoding", // Provide explicit encoding (the next line) - "UTF-8", // Specify character encoding used by Java source files. - "-deprecation", // Shows a description of each use or override of a deprecated member or class. - "-g" // Include debugging information + "-encoding", // Provide explicit encoding (the next line) + "UTF-8", // Specify character encoding used by Java source files + "-deprecation", // Shows a description of each use or override of a deprecated member or class + "-g", // Include debugging information + "-Xlint:unchecked", // Enable additional warnings ) ThisBuild / scalacOptions ++= Seq( diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/Array.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/Array.java deleted file mode 100644 index 1133dfa4caea..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/Array.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import org.enso.interpreter.dsl.BuiltinType; -import org.enso.interpreter.node.expression.builtin.Builtin; - -@BuiltinType(name = "Standard.Base.Data.Array.Array") -public class Array extends Builtin {} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/GetAtNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/GetAtNode.java deleted file mode 100644 index 96dd228596f0..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/GetAtNode.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.Context; -import org.enso.interpreter.runtime.builtin.Builtins; -import org.enso.interpreter.runtime.data.Array; -import org.enso.interpreter.runtime.error.PanicException; - -@BuiltinMethod( - type = "Array", - name = "at", - description = "Gets an array element at the given index.") -public class GetAtNode extends Node { - - Object execute(Array _this, long index) { - try { - return _this.getItems()[(int) index]; - } catch (IndexOutOfBoundsException exception) { - Builtins builtins = Context.get(this).getBuiltins(); - throw new PanicException(builtins.error().makeInvalidArrayIndexError(_this, index), this); - } - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/GetRefNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/GetRefNode.java deleted file mode 100644 index 835a5ec254e9..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/GetRefNode.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.data.Ref; - -@BuiltinMethod(type = "Ref", name = "get", description = "Gets the value stored in the reference.") -public class GetRefNode extends Node { - - Object execute(Ref _this) { - return _this.getValue(); - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New1Node.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New1Node.java deleted file mode 100644 index 58c69d97b3b0..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New1Node.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.data.Array; - -@BuiltinMethod( - type = "Array", - name = "new_1", - description = "Creates an array with one given element.") -public class New1Node extends Node { - Object execute(Object _this, Object item_1) { - return new Array(item_1); - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New2Node.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New2Node.java deleted file mode 100644 index e999c45c459d..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New2Node.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.data.Array; - -@BuiltinMethod( - type = "Array", - name = "new_2", - description = "Creates an array with two given elements.") -public class New2Node extends Node { - - Object execute(Object _this, Object item_1, Object item_2) { - return new Array(item_1, item_2); - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New3Node.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New3Node.java deleted file mode 100644 index e2d5192e51c2..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New3Node.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.data.Array; - -@BuiltinMethod( - type = "Array", - name = "new_3", - description = "Creates an array with three given elements.") -public class New3Node extends Node { - - Object execute(Object _this, Object item_1, Object item_2, Object item_3) { - return new Array(item_1, item_2, item_3); - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New4Node.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New4Node.java deleted file mode 100644 index 6034414ba346..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/New4Node.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.data.Array; - -@BuiltinMethod( - type = "Array", - name = "new_4", - description = "Creates an array with four given elements.") -public class New4Node extends Node { - - Object execute(Object _this, Object item_1, Object item_2, Object item_3, Object item_4) { - return new Array(item_1, item_2, item_3, item_4); - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/NewRefNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/NewRefNode.java deleted file mode 100644 index df3da34c3229..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/NewRefNode.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.data.Array; -import org.enso.interpreter.runtime.data.Ref; - -@BuiltinMethod(type = "Ref", name = "new", description = "Creates a new ref.") -public class NewRefNode extends Node { - - Object execute(Object _this, Object value) { - return new Ref(value); - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/PutRefNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/PutRefNode.java deleted file mode 100644 index 86ed968c19e3..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/PutRefNode.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.data.Ref; - -@BuiltinMethod(type = "Ref", name = "put", description = "Stores a new value in the reference.") -public class PutRefNode extends Node { - - Object execute(Ref _this, Object new_value) { - Object old = _this.getValue(); - _this.setValue(new_value); - return old; - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/Ref.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/Ref.java deleted file mode 100644 index 8336cd33ad27..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/Ref.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import org.enso.interpreter.dsl.BuiltinType; -import org.enso.interpreter.node.expression.builtin.Builtin; - -@BuiltinType(name = "Standard.Base.Data.Ref.Ref") -public class Ref extends Builtin {} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/SetAtNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/SetAtNode.java deleted file mode 100644 index 841355272ac5..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/SetAtNode.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.AcceptsError; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.Context; -import org.enso.interpreter.runtime.builtin.Builtins; -import org.enso.interpreter.runtime.data.Array; -import org.enso.interpreter.runtime.error.PanicException; - -@BuiltinMethod( - type = "Array", - name = "set_at", - description = "Puts the given element in the given position in the array.") -public class SetAtNode extends Node { - - Object execute(Array _this, long index, @AcceptsError Object value) { - try { - _this.getItems()[(int) index] = value; - return _this; - } catch (IndexOutOfBoundsException exception) { - Builtins builtins = Context.get(this).getBuiltins(); - throw new PanicException(builtins.error().makeInvalidArrayIndexError(_this, index), this); - } - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java index d64938fe91c3..8955156476fe 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java @@ -8,8 +8,9 @@ import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; +import org.enso.interpreter.dsl.AcceptsError; import org.enso.interpreter.dsl.Builtin; -import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.node.expression.builtin.error.InvalidArrayIndexError; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; @@ -22,6 +23,7 @@ /** A primitve boxed array type for use in the runtime. */ @ExportLibrary(InteropLibrary.class) @ExportLibrary(MethodDispatchLibrary.class) +@Builtin(pkg = "mutable", stdlibName = "Standard.Base.Data.Array.Array") public class Array implements TruffleObject { private final Object[] items; @@ -30,6 +32,7 @@ public class Array implements TruffleObject { * * @param items the element values */ + @Builtin.Method(expandVarargs = 4, description = "Creates an array with given elements.") public Array(Object... items) { this.items = items; } @@ -39,7 +42,7 @@ public Array(Object... items) { * * @param size the size of the created array. */ - @Builtin(pkg = "mutable", description = "Creates an uninitialized array of a given size.") + @Builtin.Method(description = "Creates an uninitialized array of a given size.") public Array(long size) { this.items = new Object[(int) size]; } @@ -75,21 +78,19 @@ public Object readArrayElement(long index) throws InvalidArrayIndexException { } /** @return the size of this array */ - @Builtin(pkg = "mutable", description = "Returns the size of this array.") + @Builtin.Method(description = "Returns the size of this array.") public long length() { return this.getItems().length; } /** @return an empty array */ - @Builtin(pkg = "mutable", description = "Creates an empty Array") + @Builtin.Method(description = "Creates an empty Array") public static Object empty() { return new Array(); } /** @return an identity array */ - @Builtin( - pkg = "mutable", - description = "Identity on arrays, implemented for protocol completeness.") + @Builtin.Method(description = "Identity on arrays, implemented for protocol completeness.") public Object toArray() { return this; } @@ -104,6 +105,19 @@ long getArraySize() { return items.length; } + @Builtin.Method(name = "at", description = "Gets an array element at the given index.") + @Builtin.WrapException(from = IndexOutOfBoundsException.class, to = InvalidArrayIndexError.class) + public Object get(long index) { + return getItems()[(int) index]; + } + + @Builtin.Method(name = "setAt", description = "Gets an array element at the given index.") + @Builtin.WrapException(from = IndexOutOfBoundsException.class, to = InvalidArrayIndexError.class) + public Object set(long index, @AcceptsError Object value) { + getItems()[(int) index] = value; + return this; + } + /** * Exposes an index validity check through the polyglot API. * diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java index 4044186087f3..935a50616f61 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java @@ -6,6 +6,7 @@ import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; +import org.enso.interpreter.dsl.Builtin; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.function.Function; @@ -13,6 +14,7 @@ /** A mutable reference type. */ @ExportLibrary(MethodDispatchLibrary.class) +@Builtin(pkg = "mutable", stdlibName = "Standard.Base.Data.Ref.Ref") public class Ref implements TruffleObject { private volatile Object value; @@ -21,11 +23,13 @@ public class Ref implements TruffleObject { * * @param value the initial value to store in the reference. */ + @Builtin.Method(description = "Creates a new Ref") public Ref(Object value) { this.value = value; } /** @return the current value of the reference. */ + @Builtin.Method(name = "get", description = "Gets the value stored in the reference") public Object getValue() { return value; } @@ -35,8 +39,11 @@ public Object getValue() { * * @param value the value to store. */ - public void setValue(Object value) { + @Builtin.Method(name = "put", description = "Stores a new value in the reference") + public Object setValue(Object value) { + Object old = this.value; this.value = value; + return old; } @ExportMessage diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java index d29e2b7ec126..1cdf85022a62 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/Builtin.java @@ -1,20 +1,51 @@ package org.enso.interpreter.dsl; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import java.lang.annotation.*; /** An annotation denoting a method that will auto-generate a BuiltinMethod node. */ -@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) +@Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) public @interface Builtin { /** @return the name of the subpackage for the generated method node. */ String pkg() default ""; - /** @return a custom name, by default it uses the name of the annotated element. */ - String name() default ""; + /** @return A fully qualified name of the corresponding Enso type in standard library. */ + String stdlibName() default ""; - /** @return a short description of this method. */ - String description() default ""; + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @interface Method { + /** + * @return a custom name for the generated builting method. By default, it uses the name of the + * annotated method. + */ + String name() default ""; + + /** @return a short description for the generated builtin method. */ + String description() default ""; + + /** + * @return When applied to a method/constructor with varargs, indicates how many times vararg + * parameter should be expanded and implicitly how many builtin nodes should be generated. + * Must be zero when there are no varaargs in the parameters list. + */ + int expandVarargs() default 0; + } + + /** + * Annotation indicating that a potential exception during the execution of the method should be + * wrapped in Enso's error type that will be reported to the user. The annotation can be repeated + * leading to multiple catch clauses. + */ + @Repeatable(WrapExceptions.class) + @interface WrapException { + /** @return Class of the potential exception to be caught during the execution of the method */ + Class from(); + /** @return Class of Enso's builtin (error) type * */ + Class to(); + } + + /** Container for {@link WrapException} annotations */ + @interface WrapExceptions { + WrapException[] value() default {}; + } } diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsMetadataProcessor.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsMetadataProcessor.java index 6ca014bf19e3..d28daecd9825 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsMetadataProcessor.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsMetadataProcessor.java @@ -2,7 +2,6 @@ import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; -import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; import javax.tools.FileObject; @@ -19,7 +18,8 @@ * generating code, {@code BuiltinsMetadataProcessor} detects when the processing of the last * annotation in the round is being processed and allows for dumping any collected metadata once. */ -public abstract class BuiltinsMetadataProcessor extends AbstractProcessor { +public abstract class BuiltinsMetadataProcessor + extends AbstractProcessor { /** * Processes annotated elements, generating code for each of them, if necessary. @@ -44,7 +44,7 @@ public final boolean process(Set annotations, RoundEnviro // we read the exisitng metadata. // Deletes/renaming are still not going to work nicely but that would be the same case // if we were writing metadata information per source file anyway. - Map pastEntries; + Map pastEntries; try { FileObject existingFile = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", metadataPath()); @@ -52,7 +52,8 @@ public final boolean process(Set annotations, RoundEnviro pastEntries = new BufferedReader(new InputStreamReader(resource, StandardCharsets.UTF_8)) .lines() - .collect(Collectors.toMap(l -> l.split(":")[0], Function.identity())); + .map(l -> toMetadataEntry(l)) + .collect(Collectors.toMap(e -> e.key(), Function.identity())); } } catch (NoSuchFileException notFoundException) { // This is the first time we are generating the metadata file, ignore the exception. @@ -70,8 +71,8 @@ public final boolean process(Set annotations, RoundEnviro try { storeMetadata(writer, pastEntries); // Dump past entries, to workaround separate compilation + annotation processing issues - for (String value : pastEntries.values()) { - writer.append(value + "\n"); + for (MetadataEntry value : pastEntries.values()) { + writer.append(value.toString() + "\n"); } } finally { writer.close(); @@ -104,7 +105,7 @@ public final boolean process(Set annotations, RoundEnviro * should not be appended to {@code writer} should be removed * @throws IOException */ - protected abstract void storeMetadata(Writer writer, Map pastEntries) + protected abstract void storeMetadata(Writer writer, Map pastEntries) throws IOException; /** @@ -123,4 +124,22 @@ protected abstract boolean handleProcess( * processing is done. */ protected abstract void cleanup(); + + /** + * A common interface that all metadata entries have to provide. In the future can avoid plain + * text representation. + */ + public interface MetadataEntry { + String toString(); + + String key(); + } + + /** + * A conversion method for a single raw metadata entry. For now represented as a string. + * + * @param line raw representation of the metadata entry + * @return parsed metedata entry, specific to the given processor + */ + protected abstract T toMetadataEntry(String line); } diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java index 0693e389d071..ed5e13e58d12 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/BuiltinsProcessor.java @@ -1,19 +1,29 @@ package org.enso.interpreter.dsl; +import com.sun.tools.javac.code.Attribute; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.util.Pair; + import org.apache.commons.lang3.StringUtils; import org.openide.util.lookup.ServiceProvider; import javax.annotation.processing.*; import javax.lang.model.SourceVersion; import javax.lang.model.element.*; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; import javax.tools.Diagnostic; +import javax.tools.FileObject; import javax.tools.JavaFileObject; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Arrays; -import java.util.List; -import java.util.Set; +import javax.tools.StandardLocation; + +import java.io.*; +import java.lang.annotation.Annotation; +import java.nio.charset.StandardCharsets; +import java.util.*; import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; /** * The processor used to generate code from the methods of the runtime representations annotated @@ -23,11 +33,13 @@ * generate a corresponding @BuiltinMethod class which, in turn, will get processed by another * processor and deliver a RootNode for the method. */ -@SupportedAnnotationTypes("org.enso.interpreter.dsl.Builtin") +@SupportedAnnotationTypes({"org.enso.interpreter.dsl.Builtin", "org.enso.interpreter.dsl.Builtin.Method"}) @ServiceProvider(service = Processor.class) public class BuiltinsProcessor extends AbstractProcessor { private static final String BuiltinsPkg = "org.enso.interpreter.node.expression.builtin"; + private final WrapExceptionExtractor wrapExceptionsExtractor = + new WrapExceptionExtractor(Builtin.WrapException.class, Builtin.WrapExceptions.class); @Override public final boolean process(Set annotations, RoundEnvironment roundEnv) { @@ -37,7 +49,14 @@ public final boolean process(Set annotations, RoundEnviro if (elt.getKind() == ElementKind.METHOD || elt.getKind() == ElementKind.CONSTRUCTOR) { try { handleMethodElement(elt, roundEnv); - } catch (Exception ioe) { + } catch (Exception e) { + e.printStackTrace(); + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage()); + } + } else if (elt.getKind() == ElementKind.CLASS) { + try { + handleClassElement(elt, roundEnv); + } catch (IOException ioe) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, ioe.getMessage()); } } else { @@ -52,36 +71,137 @@ public final boolean process(Set annotations, RoundEnviro return true; } + public void handleClassElement(Element element, RoundEnvironment roundEnv) throws IOException { + TypeElement elt = (TypeElement) element; + Builtin annotation = element.getAnnotation(Builtin.class); + String clazzName = element.getSimpleName().toString(); + String builtinPkg = + annotation.pkg().isEmpty() ? BuiltinsPkg : BuiltinsPkg + "." + annotation.pkg(); + ClassName builtinType = new ClassName(builtinPkg, clazzName); + JavaFileObject gen = + processingEnv.getFiler().createSourceFile(builtinType.fullyQualifiedName()); + Optional stdLibName = annotation.stdlibName().isEmpty() ? Optional.empty() : Optional.of(annotation.stdlibName()); + generateBuiltinType(gen, builtinType, stdLibName); + } + + private void generateBuiltinType( + JavaFileObject gen, + ClassName builtinType, + Optional stdLibName) throws IOException { + try (PrintWriter out = new PrintWriter(gen.openWriter())) { + out.println("package " + builtinType.pkg() + ";"); + out.println(); + for (String importPkg : typeNecessaryImports) { + out.println("import " + importPkg + ";"); + } + out.println(); + String stdLib = stdLibName.map(n -> "(name = \"" + n + "\")").orElse(""); + out.println("@BuiltinType" + stdLib); + out.println("public class " + builtinType.name() + " extends Builtin {}"); + out.println(); + } + } + public void handleMethodElement(Element element, RoundEnvironment roundEnv) throws IOException { ExecutableElement method = (ExecutableElement) element; Element owner = element.getEnclosingElement(); + if (owner.getKind() == ElementKind.CLASS) { + Builtin ownerAnnotation = owner.getAnnotation(Builtin.class); TypeElement tpeElement = (TypeElement) owner; PackageElement pkgElement = (PackageElement) tpeElement.getEnclosingElement(); - Builtin annotation = element.getAnnotation(Builtin.class); + Builtin.Method annotation = element.getAnnotation(Builtin.Method.class); boolean isConstructor = method.getKind() == ElementKind.CONSTRUCTOR; - String methodName = - !annotation.name().isEmpty() ? annotation.name() : - isConstructor ? "new" : method.getSimpleName().toString(); String builtinPkg = - annotation.pkg().isEmpty() ? BuiltinsPkg : BuiltinsPkg + "." + annotation.pkg(); - String optConstrSuffix = isConstructor ? tpeElement.getSimpleName().toString() : ""; - ClassName builtinMethodNode = - new ClassName( - builtinPkg, - methodName.substring(0, 1).toUpperCase() + methodName.substring(1) + optConstrSuffix + "Node"); - ClassName ownerClass = - new ClassName(pkgElement.getQualifiedName(), tpeElement.getSimpleName()); - JavaFileObject gen = - processingEnv.getFiler().createSourceFile(builtinMethodNode.fullyQualifiedName()); - MethodGenerator methodGen = new MethodGenerator(method); - generateBuiltinMethodNode( - gen, methodGen, methodName, method.getSimpleName().toString(), annotation.description(), builtinMethodNode, ownerClass); + ownerAnnotation.pkg().isEmpty() ? BuiltinsPkg : BuiltinsPkg + "." + ownerAnnotation.pkg(); + + SafeWrapException[] wrapExceptions = + wrapExceptionsExtractor.extract(element); + Map parameterCounts = builtinTypesParametersCount(roundEnv); + + if (annotation.expandVarargs() != 0) { + if (annotation.expandVarargs() < 0) throw new RuntimeException("Invalid varargs value in @Builtin annotation. Must be positive"); + if (!annotation.name().isEmpty()) throw new RuntimeException("Name cannot be non-empty when varargs are used"); + + IntStream.rangeClosed(1, annotation.expandVarargs()).forEach(i -> { + String methodName = (isConstructor ? "new" : method.getSimpleName().toString()) + "_" + i; + String noUnderscoresMethodName = methodName.replaceAll("_", ""); + String optConstrSuffix = isConstructor ? tpeElement.getSimpleName().toString() : ""; + ClassName builtinMethodNode = + new ClassName( + builtinPkg, + noUnderscoresMethodName.substring(0, 1).toUpperCase() + noUnderscoresMethodName.substring(1) + optConstrSuffix + "Node"); + ClassName ownerClass = + new ClassName(pkgElement.getQualifiedName(), tpeElement.getSimpleName()); + try { + JavaFileObject gen = + processingEnv.getFiler().createSourceFile(builtinMethodNode.fullyQualifiedName()); + MethodGenerator methodGen = new MethodGenerator(method, i, wrapExceptions); + generateBuiltinMethodNode( + gen, methodGen, methodName, method.getSimpleName().toString(), annotation.description(), builtinMethodNode, ownerClass, parameterCounts); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + }); + } else { + String methodName = + !annotation.name().isEmpty() ? annotation.name() : + isConstructor ? "new" : method.getSimpleName().toString(); + + String optConstrSuffix = isConstructor ? tpeElement.getSimpleName().toString() : ""; + ClassName builtinMethodNode = + new ClassName( + builtinPkg, + methodName.substring(0, 1).toUpperCase() + methodName.substring(1) + optConstrSuffix + "Node"); + ClassName ownerClass = + new ClassName(pkgElement.getQualifiedName(), tpeElement.getSimpleName()); + JavaFileObject gen = + processingEnv.getFiler().createSourceFile(builtinMethodNode.fullyQualifiedName()); + MethodGenerator methodGen = new MethodGenerator(method, 0, wrapExceptions); + generateBuiltinMethodNode( + gen, methodGen, methodName, method.getSimpleName().toString(), annotation.description(), builtinMethodNode, ownerClass, parameterCounts); + } } else { throw new RuntimeException("@Builtin method must be owned by the class"); } } + /** + * Returns a map of builtin types and the number of their paramneters. + * Takes into account the possibility of separate compilation by reading entries from metadate, if any. + * @param roundEnv current round environment + * @return a map from a builtin type name to the number of its parameters + */ + private Map builtinTypesParametersCount(RoundEnvironment roundEnv) { + // For separate compilation we need to read that information from BuiltinTypes metadata file + Map pastEntries; + try { + FileObject existingFile = + processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", TypeProcessor.META_PATH); + + try (InputStream resource = existingFile.openInputStream()) { + pastEntries = + new BufferedReader(new InputStreamReader(resource, StandardCharsets.UTF_8)) + .lines() + .map(l -> TypeProcessor.fromStringToMetadataEntry(l)) + .collect(Collectors.toMap(e -> e.key().replaceAll("_", ""), e -> e.paramNames().length)); + } + } catch (IOException e) { + // Ignore, this is a clean run + pastEntries = new HashMap<>(); + } + + Map currentRoundEntries = + roundEnv + .getElementsAnnotatedWith(BuiltinType.class) + .stream() + .collect(Collectors.toMap(e -> e.getSimpleName().toString(), e -> e.getAnnotation(BuiltinType.class).params().length)); + + pastEntries.forEach((k, v) -> currentRoundEntries.merge(k, v, (v1, v2) -> v1)); + + return currentRoundEntries; + } + /** * Generates a Java class for @BuiltinMethod Node. * @@ -92,6 +212,7 @@ public void handleMethodElement(Element element, RoundEnvironment roundEnv) thro * @param description short description for the node * @param builtinNode class name of the target node * @param ownerClazz class name of the owner of the annotated method + * @param builtinTypesParamCount a map from builtin types to their parameters count * @throws IOException throws an exception when we cannot write the new class */ private void generateBuiltinMethodNode( @@ -101,13 +222,14 @@ private void generateBuiltinMethodNode( String ownerMethodName, String description, ClassName builtinNode, - ClassName ownerClazz) + ClassName ownerClazz, + Map builtinTypesParamCount) throws IOException { String ensoMethodName = methodName.replaceAll("([^_A-Z])([A-Z])", "$1_$2").toLowerCase(); try (PrintWriter out = new PrintWriter(gen.openWriter())) { out.println("package " + builtinNode.pkg() + ";"); out.println(); - for (String importPkg : necessaryImports) { + for (String importPkg : methodNecessaryImports) { out.println("import " + importPkg + ";"); } out.println("import " + ownerClazz.fullyQualifiedName() + ";"); @@ -123,7 +245,7 @@ private void generateBuiltinMethodNode( out.println("public class " + builtinNode.name() + " extends Node {"); out.println(); - for (String line : mgen.generateMethod(ownerMethodName, ownerClazz.name())) { + for (String line : mgen.generateMethod(ownerMethodName, ownerClazz.name(), builtinTypesParamCount)) { out.println(" " + line); } @@ -132,11 +254,26 @@ private void generateBuiltinMethodNode( } } - private final List necessaryImports = - Arrays.asList("com.oracle.truffle.api.nodes.Node", "org.enso.interpreter.dsl.BuiltinMethod"); + private final List typeNecessaryImports = + Arrays.asList("org.enso.interpreter.dsl.BuiltinType", + "org.enso.interpreter.node.expression.builtin.Builtin"); + private final List methodNecessaryImports = + Arrays.asList( + "com.oracle.truffle.api.nodes.Node", + "org.enso.interpreter.dsl.BuiltinMethod", + "org.enso.interpreter.runtime.builtin.Builtins", + "org.enso.interpreter.runtime.Context", + "org.enso.interpreter.runtime.error.PanicException"); - private MethodParameter fromVariableElementToMethodParameter(VariableElement v) { - return new MethodParameter(v.getSimpleName().toString(), v.asType().toString()); + /** + * Convert annotated method's variable to MethodParameter + * @param i position of the variable representing the parameter + * @param v variable element representing the parameter + * @return MethodParameter encapsulating the method's parameter info + */ + private MethodParameter fromVariableElementToMethodParameter(int i, VariableElement v) { + return new MethodParameter(i, v.getSimpleName().toString(), v.asType().toString(), + v.getAnnotationMirrors().stream().map(am -> am.toString()).collect(Collectors.toList())); } /** Method generator encapsulates the generation of the `execute` method. */ @@ -145,25 +282,57 @@ private class MethodGenerator { private final List params; private final boolean isStatic; private final boolean isConstructor; + private final int varargExpansion; + private final boolean needsVarargExpansion; + private final SafeWrapException[] exceptionWrappers; - public MethodGenerator(ExecutableElement method) { + public MethodGenerator(ExecutableElement method, int expandedVarargs, SafeWrapException[] exceptionWrappers) { + this(method, method.getParameters(), expandedVarargs, exceptionWrappers); + } + + private MethodGenerator(ExecutableElement method, List params, int expandedVarargs, SafeWrapException[] exceptionWrappers) { this( method.getReturnType().toString(), - method.getParameters().stream() - .map(v -> fromVariableElementToMethodParameter(v)) - .collect(Collectors.toList()), + IntStream.range(0, method.getParameters().size()).mapToObj(i -> + fromVariableElementToMethodParameter(i, params.get(i))).collect(Collectors.toList()), method.getModifiers().contains(Modifier.STATIC), - method.getKind() == ElementKind.CONSTRUCTOR); + method.getKind() == ElementKind.CONSTRUCTOR, + expandedVarargs, + method.isVarArgs(), + exceptionWrappers); } - private MethodGenerator(String returnTpe, List params, boolean isStatic, boolean isConstructor) { + private MethodGenerator( + String returnTpe, + List params, + boolean isStatic, + boolean isConstructor, + int expandedVarargs, + boolean isVarargs, + SafeWrapException[] exceptionWrappers) { this.returnTpe = returnTpe; this.params = params; this.isStatic = isStatic; this.isConstructor = isConstructor; + this.varargExpansion = expandedVarargs; + this.needsVarargExpansion = isVarargs && (varargExpansion > 0); + this.exceptionWrappers = exceptionWrappers; + } + + private Optional expandVararg(int paramIndex) { + return needsVarargExpansion && params.size() >= (paramIndex + 1) ? Optional.of(varargExpansion) : Optional.empty(); } - public String[] generateMethod(String name, String owner) { + private String fromAnnotationValueToClassName(Attribute.Class clazz) { + String[] clazzElements = clazz.classType.baseType().toString().split("\\."); + if (clazzElements.length == 0) { + return clazz.classType.baseType().toString(); + } else { + return clazzElements[clazzElements.length - 1]; + } + } + + public List generateMethod(String name, String owner, Map builtinTypesParameterCounts) { String paramsDef; String paramsApplied; if (params.isEmpty()) { @@ -172,9 +341,9 @@ public String[] generateMethod(String name, String owner) { } else { paramsDef = ", " - + StringUtils.join(params.stream().map(x -> x.declaredParameter()).toArray(), ", "); + + StringUtils.join(params.stream().flatMap(x -> x.declaredParameters(expandVararg(x.index))).toArray(), ", "); paramsApplied = - StringUtils.join(params.stream().map(x -> x.name()).toArray(), ", "); + StringUtils.join(params.stream().flatMap(x -> x.names(expandVararg(x.index))).toArray(), ", "); } String thisParamTpe = isStatic || isConstructor ? "Object" : owner; String targetReturnTpe = isConstructor ? "Object" : returnTpe; @@ -186,14 +355,30 @@ public String[] generateMethod(String name, String owner) { ? " return " + owner + "." + name + "(" + paramsApplied + ");" : " return _this." + name + "(" + paramsApplied + ");"; } - return new String[] { - targetReturnTpe + " execute(" + thisParamTpe + " _this" + paramsDef + ") {", - body, - "}" - }; + boolean wrapsExceptions = exceptionWrappers.length != 0; + if (wrapsExceptions) { + List wrappedBody = new ArrayList<>(); + wrappedBody.add(targetReturnTpe + " execute(" + thisParamTpe + " _this" + paramsDef + ") {"); + wrappedBody.add(" try {"); + wrappedBody.add(" " + body); + for (int i=0; i < exceptionWrappers.length; i++) { + wrappedBody.addAll(exceptionWrappers[i].toCatchClause(params, builtinTypesParameterCounts)); + } + wrappedBody.add(" }"); + wrappedBody.add("}"); + return wrappedBody; + } else { + return List.of( + targetReturnTpe + " execute(" + thisParamTpe + " _this" + paramsDef + ") {", + body, + "}" + ); + } } + } + private record ClassName(String pkg, String name) { private ClassName(Name pkgName, Name nameSeq) { this(pkgName.toString(), nameSeq.toString()); @@ -204,10 +389,194 @@ public String fullyQualifiedName() { } } - private record MethodParameter(String name, String tpe) { - public String declaredParameter() { - return tpe + " " + name; + /** + * MethodParameter encapsulates the generation of string representation of the parameter. + * Additionally, it can optionally expand vararg parameters. + */ + private record MethodParameter(int index, String name, String tpe, List annotations) { + /** + * Returns a parameter's declaration. + * If the parameter represents a vararg, the declaration is repeated. Otherwise return a single + * element Stream of declarations. + * + * @param expand For a non-empty value n, the parameter must be repeated n-times. + * @return A string representation of the parameter, potentially repeated for varargs + */ + public Stream declaredParameters(Optional expand) { + // If the parameter is the expanded vararg we must get rid of the `[]` suffix + String parameterTpe = expand.isEmpty() ? tpe : tpe.substring(0, tpe.length() - 2); + String copiedAnnotations = annotations.isEmpty() ? "" : (StringUtils.joinWith(" ", annotations.toArray()) + " "); + return names(expand).map(n -> copiedAnnotations + parameterTpe + " " + n); + } + + /** + * Returns a parameter's name.. + * If the parameter represents a vararg, the name is repeated. Otherwise return a single + * element Stream of declarations. + * + * @param expand For a non-empty value n, the parameter must be repeated n-times. + * @return A string representation of the parameter, potentially repeated for varargs + */ + public Stream names(Optional expand) { + return expand.map(e-> + IntStream.range(0, e).mapToObj(i-> name + "_" + (i+1)) + ).orElse(Stream.of(name)); + } + } + + /** + * Wrapper around {@link Builtin.WrapException} annotation with all elements of Class type resolved. + * extracted + */ + private record SafeWrapException(Attribute.Class from, Attribute.Class to) { + + /** + * Generate a catch-clause that catches `from`, wraps it into `to` Enso type and rethrows the latter + * @param methodParameters list of all method's parameters, potentially to be applied to `to` constructor + * @param builtinTypesParameterCounts a map from builtin errors to the number of parameters in their constructors + * @return Lines representing the (unclosed) catch-clause catching the runtime `from` exception + */ + List toCatchClause(List methodParameters, Map builtinTypesParameterCounts) { + String from = fromAttributeToClassName(from()); + String to = fromAttributeToClassName(to()); + int toParamCount = errorParametersCount(to(), builtinTypesParameterCounts); + List errorParameters = + methodParameters + .stream() + .limit(toParamCount - 1) + .flatMap(x -> x.names(Optional.empty())) + .collect(Collectors.toList()); + String errorParameterCode = errorParameters.isEmpty() ? "" : ", " + StringUtils.join(errorParameters, ", "); + return List.of( + " } catch (" + from + " exception) {", + " Builtins builtins = Context.get(this).getBuiltins();", + " throw new PanicException(builtins.error().make" + to + "(_this" + errorParameterCode + "), this);" + ); + } + + private String fromAttributeToClassName(Attribute.Class clazz) { + String[] clazzElements = clazz.classType.baseType().toString().split("\\."); + if (clazzElements.length == 0) { + return clazz.classType.baseType().toString(); + } else { + return clazzElements[clazzElements.length - 1]; + } + } + + private int errorParametersCount(Attribute.Class clazz, Map builtinTypesParameterCounts) { + String clazzSimple = fromAttributeToClassName(clazz); + // `this` counts as 1 + return builtinTypesParameterCounts.getOrDefault(clazzSimple, 1); + } + } + + /** + * Helper class that encapsulates retrieving the values of elements which type involves Class. + * Such elements' values cannot be retrieved by invoking the () method. Instead one has to go through mirrors. + * The logic has to deal with the following scenarios: + * - method with an individual annotation + * - method with multiple annotations of the same type, thus implicitly being annotation with + * container annotation + * - method with an explicit container annotation + * + * Refer to blog + * for details. + **/ + private class WrapExceptionExtractor { + + private static final String FromElementName = "from"; + private static final String ToElementName = "to"; + private static final String ValueElementName = "value"; + + private Class wrapExceptionAnnotationClass; + private Class wrapExceptionsAnnotationClass; + + public WrapExceptionExtractor( + Class wrapExceptionAnnotationClass, + Class wrapExceptionsAnnotationClass) { + this.wrapExceptionAnnotationClass = wrapExceptionAnnotationClass; + this.wrapExceptionsAnnotationClass = wrapExceptionsAnnotationClass; + } + + /** + * Extract {@link org.enso.interpreter.dsl.Builtin.WrapException} from the annotated element + * in a mirror-safe manner. + * + * @param element a method annotated with either {@link org.enso.interpreter.dsl.Builtin.WrapException} or + * {@link org.enso.interpreter.dsl.Builtin.WrapExceptions} + * @return An array of safely retrieved (potentially repeated) values of + * {@link org.enso.interpreter.dsl.Builtin.WrapException} annotation(s) + */ + public SafeWrapException[] extract(Element element) { + if (element.getAnnotation(wrapExceptionsAnnotationClass) != null) { + return extractClassElementFromAnnotationContainer(element, wrapExceptionsAnnotationClass); + } else if (element.getAnnotation(wrapExceptionAnnotationClass) != null) { + return extractClassElementFromAnnotation(element, wrapExceptionAnnotationClass); + } else { + return new SafeWrapException[0]; + } } + + private SafeWrapException[] extractClassElementFromAnnotation(Element element, Class annotationClass) { + Element builtinElement = processingEnv.getElementUtils().getTypeElement(annotationClass.getCanonicalName()); + TypeMirror builtinType = builtinElement.asType(); + + List exceptionWrappers = new ArrayList<>(); + for (AnnotationMirror am: element.getAnnotationMirrors()) { + if (am.getAnnotationType().equals(builtinType)) { + Attribute.Class valueFrom = null; + Attribute.Class valueTo = null; + for (Map.Entry entry : am.getElementValues().entrySet() ) { + if (FromElementName.equals(entry.getKey().getSimpleName().toString())) { + valueFrom = (Attribute.Class)(entry.getValue()); + } else if (ToElementName.equals(entry.getKey().getSimpleName().toString())) { + valueTo = (Attribute.Class)(entry.getValue()); + } + } + if (valueFrom != null && valueTo != null) { + exceptionWrappers.add(new SafeWrapException(valueFrom, valueTo)); + } + } + } + return exceptionWrappers.toArray(new SafeWrapException[0]); + } + + + private SafeWrapException[] extractClassElementFromAnnotationContainer(Element element, Class annotationClass) { + + Element builtinElement = processingEnv.getElementUtils().getTypeElement(annotationClass.getCanonicalName()); + Types tpeUtils = processingEnv.getTypeUtils(); + TypeMirror builtinType = builtinElement.asType(); + + List wrappedExceptions = new ArrayList<>(); + for (AnnotationMirror am: element.getAnnotationMirrors()) { + if (tpeUtils.isSameType(am.getAnnotationType(), builtinType)) { + for (Map.Entry entry : am.getElementValues().entrySet()) { + if (ValueElementName.equals(entry.getKey().getSimpleName().toString())) { + Attribute.Array wrapExceptions = (Attribute.Array)entry.getValue(); + for (int i = 0; i p: attr.values) { + if (p.fst.getSimpleName().contentEquals(FromElementName)) { + valueFrom = (Attribute.Class)p.snd; + } else if (p.fst.getSimpleName().contentEquals(ToElementName)) { + valueTo = (Attribute.Class)p.snd; + } + } + if (valueFrom != null && valueTo != null) { + wrappedExceptions.add(new SafeWrapException(valueFrom, valueTo)); + } + } + break; + } + } + } + } + return wrappedExceptions.toArray(new SafeWrapException[0]); + } + } @Override diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/MethodProcessor.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/MethodProcessor.java index 08cd9f3dd19f..1524899f1e68 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/MethodProcessor.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/MethodProcessor.java @@ -1,5 +1,6 @@ package org.enso.interpreter.dsl; +import org.apache.commons.lang3.StringUtils; import org.enso.interpreter.dsl.model.MethodDefinition; import javax.annotation.processing.*; @@ -20,7 +21,7 @@ */ @SupportedAnnotationTypes("org.enso.interpreter.dsl.BuiltinMethod") @ServiceProvider(service = Processor.class) -public class MethodProcessor extends BuiltinsMetadataProcessor { +public class MethodProcessor extends BuiltinsMetadataProcessor { private final Map> builtinMethods = new HashMap<>(); @@ -436,7 +437,7 @@ private boolean generateWarningsCheck( * should not be appended to {@code writer} should be removed * @throws IOException */ - protected void storeMetadata(Writer writer, Map pastEntries) throws IOException { + protected void storeMetadata(Writer writer, Map pastEntries) throws IOException { for (Filer f : builtinMethods.keySet()) { for (Map.Entry entry : builtinMethods.get(f).entrySet()) { writer.append(entry.getKey() + ":" + entry.getValue() + "\n"); @@ -485,4 +486,24 @@ private String capitalize(String name) { public SourceVersion getSupportedSourceVersion() { return SourceVersion.latest(); } + + public record MethodMetadataEntry(String fullEnsoName, String clazzName) implements MetadataEntry { + + @Override + public String toString() { + return fullEnsoName + ":" + clazzName; + } + + @Override + public String key() { + return fullEnsoName; + } + } + + @Override + protected MethodMetadataEntry toMetadataEntry(String line) { + String[] elements = line.split(":"); + if (elements.length != 2) throw new RuntimeException("invalid builtin metadata entry: " + line); + return new MethodMetadataEntry(elements[0], elements[1]); + } } diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/TypeProcessor.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/TypeProcessor.java index 442c5990ebfc..2aaef110cda9 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/TypeProcessor.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/TypeProcessor.java @@ -15,7 +15,7 @@ @SupportedAnnotationTypes("org.enso.interpreter.dsl.BuiltinType") @ServiceProvider(service = Processor.class) -public class TypeProcessor extends BuiltinsMetadataProcessor { +public class TypeProcessor extends BuiltinsMetadataProcessor { private final Map> builtinTypes = new HashMap<>(); @@ -84,7 +84,7 @@ protected boolean handleProcess( * @throws IOException */ @Override - protected void storeMetadata(Writer writer, Map pastEntries) throws IOException { + protected void storeMetadata(Writer writer, Map pastEntries) throws IOException { JavaFileObject gen = processingEnv.getFiler().createSourceFile(ConstantsGenFullClassname); for (Filer f : builtinTypes.keySet()) { System.out.println("foo" + f.toString()); @@ -126,17 +126,16 @@ protected void storeMetadata(Writer writer, Map pastEntries) thr pastEntries .values() .forEach( - entry -> { - String[] elements = entry.split(":"); - if (elements.length == 4 && !elements[3].isEmpty()) { - out.println( - " public static void final String " - + elements[0].toUpperCase() - + " = \"" - + elements[3] - + "\";"); - } - }); + entry -> + entry.stdlibName().ifPresent(n -> + out.println( + " public static final String " + + entry.ensoName().toUpperCase() + + " = \"" + + n + + "\";") + ) + ); out.println(); out.println("}"); @@ -171,4 +170,30 @@ protected void cleanup() { public SourceVersion getSupportedSourceVersion() { return SourceVersion.latest(); } + + public record TypeMetadataEntry(String ensoName, String clazzName, String[] paramNames, Optional stdlibName) implements MetadataEntry { + + @Override + public String toString() { + return ensoName + ":" + clazzName + ":" + StringUtils.join(paramNames, ",") + ":" + stdlibName.orElse(""); + } + + @Override + public String key() { + return ensoName; + } + } + + @Override + protected TypeMetadataEntry toMetadataEntry(String line) { + return fromStringToMetadataEntry(line); + } + + public static TypeMetadataEntry fromStringToMetadataEntry(String line) { + String[] elements = line.split(":"); + if (elements.length < 2) throw new RuntimeException("invalid builtin metadata entry: " + line); + String[] params = elements.length >= 3 ? elements[2].split(",") : new String[0]; + Optional stdLibName = elements.length == 4 ? Optional.of(elements[3]) : Optional.empty(); + return new TypeMetadataEntry(elements[0], elements[1], params, stdLibName); + } }