diff --git a/distribution/std-lib/Base/src/Data/Vector.enso b/distribution/std-lib/Base/src/Data/Vector.enso
index 14430c5e9ee5..7a955aafb2bd 100644
--- a/distribution/std-lib/Base/src/Data/Vector.enso
+++ b/distribution/std-lib/Base/src/Data/Vector.enso
@@ -85,7 +85,7 @@ type Vector
Checking the length of a vector.
[1, 2, 3, 4].length == 4
length : Number
- length = this.to_array.length
+ length = Polyglot.get_array_size this.to_array
## Gets an element from the vector at a specified index (0-based).
@@ -208,10 +208,7 @@ type Vector
[2, 3, 4]
map : (Any -> Any) -> Vector
map function =
- arr = this.to_array
- new_arr = Array.new arr.length
- 0.up_to arr.length . each ix-> new_arr.set_at ix (function (arr.at ix))
- Vector new_arr
+ here.new this.length i-> function (this.at i)
## Applies a function to each element of the vector, returning the vector
of results.
@@ -247,12 +244,11 @@ type Vector
[1, 2, 3].to_text == "[1, 2, 3]"
to_text : Text
to_text =
- arr = this.to_array
- if arr.length == 0 then "[]" else
- if arr.length == 1 then "[" + (arr.at 0 . to_text) + "]" else
- folder = str -> ix -> str + ", " + (arr.at ix).to_text
- tail_elems = 1.up_to arr.length . fold "" folder
- "[" + (arr.at 0 . to_text) + tail_elems + "]"
+ if this.length == 0 then "[]" else
+ if this.length == 1 then "[" + (this.at 0 . to_text) + "]" else
+ folder = str -> ix -> str + ", " + (this.at ix).to_text
+ tail_elems = 1.up_to this.length . fold "" folder
+ "[" + (this.at 0 . to_text) + tail_elems + "]"
## Checks whether this vector is equal to `that`. Two vectors are considered
equal, when they have the same length and their items are pairwise equal.
@@ -262,10 +258,8 @@ type Vector
[1, 2, 3] == [2, 3, 4
== : Vector -> Boolean
== that =
- arr1 = this.to_array
- arr2 = that.to_array
- eq_at i = arr1.at i == arr2.at i
- if arr1.length == arr2.length then 0.up_to arr1.length . all eq_at else False
+ eq_at i = this.at i == that.at i
+ if this.length == that.length then 0.up_to this.length . all eq_at else False
## Concatenates two vectors, resulting in a new vector, containing all the
elements of `this`, followed by all the elements of `that`.
diff --git a/engine/runner/src/main/scala/org/enso/runner/ContextFactory.scala b/engine/runner/src/main/scala/org/enso/runner/ContextFactory.scala
index 668fd3405d98..89f6b148e6cd 100644
--- a/engine/runner/src/main/scala/org/enso/runner/ContextFactory.scala
+++ b/engine/runner/src/main/scala/org/enso/runner/ContextFactory.scala
@@ -34,7 +34,9 @@ class ContextFactory {
strictErrors: Boolean = false
): PolyglotContext = {
val context = Context
- .newBuilder(LanguageInfo.ID)
+ // TODO: Remove EPB from this list when https://github.com/oracle/graal/pull/3139 is merged
+ // and available in our Graal release.
+ .newBuilder(LanguageInfo.ID, "js", "epb")
.allowExperimentalOptions(true)
.allowAllAccess(true)
.option(RuntimeOptions.PACKAGES_PATH, packagesPath)
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/Language.java b/engine/runtime/src/main/java/org/enso/interpreter/Language.java
index f561e848e56f..d779a43db332 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/Language.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/Language.java
@@ -7,6 +7,7 @@
import com.oracle.truffle.api.nodes.RootNode;
import java.util.Collections;
+import org.enso.interpreter.epb.EpbLanguage;
import org.enso.interpreter.instrument.IdExecutionInstrument;
import org.enso.interpreter.node.ProgramRootNode;
import org.enso.interpreter.runtime.Context;
@@ -34,6 +35,7 @@
defaultMimeType = LanguageInfo.MIME_TYPE,
characterMimeTypes = {LanguageInfo.MIME_TYPE},
contextPolicy = TruffleLanguage.ContextPolicy.SHARED,
+ dependentLanguages = {EpbLanguage.ID},
fileTypeDetectors = FileDetector.class,
services = ExecutionService.class)
@ProvidedTags({
@@ -71,7 +73,7 @@ protected Context createContext(Env env) {
* @param context the language context
*/
@Override
- protected void initializeContext(Context context) throws Exception {
+ protected void initializeContext(Context context) {
context.initialize();
}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbContext.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbContext.java
new file mode 100644
index 000000000000..46f10fc988e7
--- /dev/null
+++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbContext.java
@@ -0,0 +1,67 @@
+package org.enso.interpreter.epb;
+
+import com.oracle.truffle.api.CompilerDirectives;
+import com.oracle.truffle.api.TruffleLanguage;
+import org.enso.interpreter.epb.runtime.GuardedTruffleContext;
+
+/**
+ * A context for {@link EpbLanguage}. Provides access to both isolated Truffle contexts used in
+ * polyglot execution.
+ */
+public class EpbContext {
+ private static final String INNER_OPTION = "isEpbInner";
+ private final boolean isInner;
+ private final TruffleLanguage.Env env;
+ private @CompilerDirectives.CompilationFinal GuardedTruffleContext innerContext;
+ private final GuardedTruffleContext currentContext;
+
+ /**
+ * Creates a new instance of this context.
+ *
+ * @param env the current language environment.
+ */
+ public EpbContext(TruffleLanguage.Env env) {
+ this.env = env;
+ isInner = env.getConfig().get(INNER_OPTION) != null;
+ currentContext = new GuardedTruffleContext(env.getContext(), isInner);
+ }
+
+ /**
+ * Initializes the context. No-op in the inner context. Spawns the inner context if called from
+ * the outer context.
+ */
+ public void initialize() {
+ if (!isInner) {
+ innerContext =
+ new GuardedTruffleContext(
+ env.newContextBuilder().config(INNER_OPTION, "yes").build(), true);
+ }
+ }
+
+ /**
+ * Checks if this context corresponds to the inner Truffle context.
+ *
+ * @return true if run in the inner Truffle context, false otherwise.
+ */
+ public boolean isInner() {
+ return isInner;
+ }
+
+ /**
+ * @return the inner Truffle context handle if called from the outer context, or null if called in
+ * the inner context.
+ */
+ public GuardedTruffleContext getInnerContext() {
+ return innerContext;
+ }
+
+ /** @return returns the currently entered Truffle context handle. */
+ public GuardedTruffleContext getCurrentContext() {
+ return currentContext;
+ }
+
+ /** @return the language environment associated with this context. */
+ public TruffleLanguage.Env getEnv() {
+ return env;
+ }
+}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbLanguage.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbLanguage.java
new file mode 100644
index 000000000000..46e351f6a95d
--- /dev/null
+++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbLanguage.java
@@ -0,0 +1,65 @@
+package org.enso.interpreter.epb;
+
+import com.oracle.truffle.api.CallTarget;
+import com.oracle.truffle.api.Truffle;
+import com.oracle.truffle.api.TruffleLanguage;
+import org.enso.interpreter.epb.node.ContextRewrapNode;
+import org.enso.interpreter.epb.node.ForeignEvalNode;
+
+/**
+ * An internal language that serves as a bridge between Enso and other supported languages.
+ *
+ *
Truffle places a lot of emphasis on safety guarantees, which also means that single-threaded
+ * languages cannot easily be called from multiple threads. We circumvent this by using two separate
+ * {@link com.oracle.truffle.api.TruffleContext}s, one (often referred to as "outer") is allowed to
+ * run Enso, Host Java, and possibly other thread-ready languages. Languages that cannot safely run
+ * in a multithreaded environment are relegated to the other context (referred to as "inner"). The
+ * inner context provides a GIL capability, ensuring that access to the single-threaded languages is
+ * serialized.
+ *
+ *
This imposes certain limitations on data interchange between the contexts. In particular, it
+ * is impossible to execute origin language's code when executing in the other context. Therefore
+ * outer context values need to be specially wrapped before being passed (e.g. as arguments) to the
+ * inner context, and inner context values need rewrapping for use in the outer context. See {@link
+ * org.enso.interpreter.epb.runtime.PolyglotProxy} and {@link
+ * ContextRewrapNode} for details of how and when this wrapping is done.
+ *
+ *
With the structure outlined above, EPB is the only language that is initialized in both inner
+ * and outer contexts and thus it is very minimal. Its only role is to manage both contexts and
+ * provide context-switching facilities.
+ */
+@TruffleLanguage.Registration(
+ id = EpbLanguage.ID,
+ name = "Enso Polyglot Bridge",
+ characterMimeTypes = {EpbLanguage.MIME},
+ // TODO mark this language as internal when https://github.com/oracle/graal/pull/3139 is
+ // released
+ internal = false,
+ defaultMimeType = EpbLanguage.MIME,
+ contextPolicy = TruffleLanguage.ContextPolicy.SHARED)
+public class EpbLanguage extends TruffleLanguage {
+ public static final String ID = "epb";
+ public static final String MIME = "application/epb";
+
+ @Override
+ protected EpbContext createContext(Env env) {
+ return new EpbContext(env);
+ }
+
+ @Override
+ protected void initializeContext(EpbContext context) {
+ context.initialize();
+ }
+
+ @Override
+ protected CallTarget parse(ParsingRequest request) {
+ EpbParser.Result code = EpbParser.parse(request.getSource());
+ return Truffle.getRuntime()
+ .createCallTarget(ForeignEvalNode.build(this, code, request.getArgumentNames()));
+ }
+
+ @Override
+ protected boolean isThreadAccessAllowed(Thread thread, boolean singleThreaded) {
+ return true;
+ }
+}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbParser.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbParser.java
new file mode 100644
index 000000000000..61c3fe764885
--- /dev/null
+++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbParser.java
@@ -0,0 +1,86 @@
+package org.enso.interpreter.epb;
+
+import com.oracle.truffle.api.source.Source;
+
+import java.util.Arrays;
+
+/** A class containing helpers for creating and parsing EPB code */
+public class EpbParser {
+ private static final String separator = "#";
+
+ /** Lists all the languages supported in polyglot eval. */
+ public enum ForeignLanguage {
+ JS("js", "js");
+
+ private final String truffleId;
+ private final String syntacticTag;
+
+ ForeignLanguage(String truffleId, String syntacticTag) {
+ this.truffleId = truffleId;
+ this.syntacticTag = syntacticTag;
+ }
+
+ /** @return a Truffle language ID associated with this language */
+ public String getTruffleId() {
+ return truffleId;
+ }
+
+ /**
+ * Transforms an Enso-side syntactic language tag into a recognized language object.
+ *
+ * @param tag the tag to parse
+ * @return a corresponding language value, or null if the language is not recognized
+ */
+ public static ForeignLanguage getBySyntacticTag(String tag) {
+ return Arrays.stream(values())
+ .filter(l -> l.syntacticTag.equals(tag))
+ .findFirst()
+ .orElse(null);
+ }
+ }
+
+ /** A parsing result. */
+ public static class Result {
+ private final ForeignLanguage language;
+ private final String foreignSource;
+
+ private Result(ForeignLanguage language, String foreignSource) {
+ this.language = language;
+ this.foreignSource = foreignSource;
+ }
+
+ /** @return the foreign language code to eval */
+ public String getForeignSource() {
+ return foreignSource;
+ }
+
+ /** @return the foreign language in which the source is written */
+ public ForeignLanguage getLanguage() {
+ return language;
+ }
+ }
+
+ /**
+ * Parses an EPB source
+ *
+ * @param source the source to parse
+ * @return the result of parsing
+ */
+ public static Result parse(Source source) {
+ String src = source.getCharacters().toString();
+ String[] langAndCode = src.split(separator, 2);
+ return new Result(ForeignLanguage.valueOf(langAndCode[0]), langAndCode[1]);
+ }
+
+ /**
+ * Builds a new source instance that can later be parsed by this class.
+ *
+ * @param language the foreign language to use
+ * @param foreignSource the foreign source to evaluate
+ * @param name the name of the source
+ * @return a source instance, parsable by the EPB language
+ */
+ public static Source buildSource(ForeignLanguage language, String foreignSource, String name) {
+ return Source.newBuilder(EpbLanguage.ID, language + separator + foreignSource, name).build();
+ }
+}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextRewrapNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextRewrapNode.java
new file mode 100644
index 000000000000..179bc5322520
--- /dev/null
+++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextRewrapNode.java
@@ -0,0 +1,68 @@
+package org.enso.interpreter.epb.node;
+
+import com.oracle.truffle.api.dsl.Fallback;
+import com.oracle.truffle.api.dsl.GenerateUncached;
+import com.oracle.truffle.api.dsl.ReportPolymorphism;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.nodes.Node;
+import org.enso.interpreter.epb.runtime.GuardedTruffleContext;
+import org.enso.interpreter.epb.runtime.PolyglotProxy;
+
+@GenerateUncached
+@ReportPolymorphism
+public abstract class ContextRewrapNode extends Node {
+ /**
+ * Wraps a value originating from {@code origin} into a value valid in {@code target}. This method
+ * is allowed to use interop library on {@code value} and therefore must be called with {@code
+ * origin} entered.
+ *
+ * @param value the value to wrap
+ * @param origin the context the value originates in (and is currently entered)
+ * @param target the context in which the value will be accessed in the future
+ * @return a context-switch-safe wrapper for the value
+ */
+ public abstract Object execute(
+ Object value, GuardedTruffleContext origin, GuardedTruffleContext target);
+
+ @Specialization
+ double doDouble(double d, GuardedTruffleContext origin, GuardedTruffleContext target) {
+ return d;
+ }
+
+ @Specialization
+ double doFloat(float d, GuardedTruffleContext origin, GuardedTruffleContext target) {
+ return d;
+ }
+
+ @Specialization
+ long doLong(long i, GuardedTruffleContext origin, GuardedTruffleContext target) {
+ return i;
+ }
+
+ @Specialization
+ long doInt(int i, GuardedTruffleContext origin, GuardedTruffleContext target) {
+ return i;
+ }
+
+ @Specialization
+ boolean doBoolean(boolean b, GuardedTruffleContext origin, GuardedTruffleContext target) {
+ return b;
+ }
+
+ @Specialization(guards = "proxy.getOrigin() == target")
+ Object doUnwrapProxy(
+ PolyglotProxy proxy, GuardedTruffleContext origin, GuardedTruffleContext target) {
+ return proxy.getDelegate();
+ }
+
+ @Specialization(guards = "proxy.getTarget() == target")
+ Object doAlreadyProxied(
+ PolyglotProxy proxy, GuardedTruffleContext origin, GuardedTruffleContext target) {
+ return proxy;
+ }
+
+ @Fallback
+ Object doWrapProxy(Object o, GuardedTruffleContext origin, GuardedTruffleContext target) {
+ return new PolyglotProxy(o, origin, target);
+ }
+}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignEvalNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignEvalNode.java
new file mode 100644
index 000000000000..215221354515
--- /dev/null
+++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignEvalNode.java
@@ -0,0 +1,92 @@
+package org.enso.interpreter.epb.node;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.TruffleLanguage.ContextReference;
+import com.oracle.truffle.api.dsl.CachedContext;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.frame.FrameDescriptor;
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.nodes.RootNode;
+import com.oracle.truffle.api.source.Source;
+import org.enso.interpreter.epb.EpbContext;
+import org.enso.interpreter.epb.EpbLanguage;
+import org.enso.interpreter.epb.EpbParser;
+import org.enso.interpreter.epb.runtime.GuardedTruffleContext;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public abstract class ForeignEvalNode extends RootNode {
+ private final EpbParser.Result code;
+ private @Child ForeignFunctionCallNode foreign;
+ private @Child ContextRewrapNode rewrapNode = ContextRewrapNodeGen.create();
+ private final String[] argNames;
+
+ ForeignEvalNode(EpbLanguage language, EpbParser.Result code, List arguments) {
+ super(language, new FrameDescriptor());
+ this.code = code;
+ argNames = arguments.toArray(new String[0]);
+ }
+
+ /**
+ * Creates a new instance of this node
+ *
+ * @param language the current language instance
+ * @param code the result of parsing EPB code
+ * @param arguments argument names allowed in the function body
+ * @return an instance of this node
+ */
+ public static ForeignEvalNode build(
+ EpbLanguage language, EpbParser.Result code, List arguments) {
+ return ForeignEvalNodeGen.create(language, code, arguments);
+ }
+
+ @Specialization
+ Object doExecute(
+ VirtualFrame frame,
+ @CachedContext(EpbLanguage.class) ContextReference contextRef) {
+ ensureParsed(contextRef);
+ return foreign.execute(frame.getArguments());
+ }
+
+ private void ensureParsed(ContextReference ctxRef) {
+ if (foreign == null) {
+ getLock().lock();
+ try {
+ if (foreign == null) {
+ CompilerDirectives.transferToInterpreterAndInvalidate();
+ if (code.getLanguage() == EpbParser.ForeignLanguage.JS) {
+ parseJs(ctxRef);
+ } else {
+ throw new IllegalStateException("Unsupported language resulted from EPB parsing");
+ }
+ }
+ } finally {
+ getLock().unlock();
+ }
+ }
+ }
+
+ private void parseJs(ContextReference ctxRef) {
+ EpbContext context = ctxRef.get();
+ GuardedTruffleContext outer = context.getCurrentContext();
+ GuardedTruffleContext inner = context.getInnerContext();
+ Object p = inner.enter();
+ try {
+ String args = Arrays.stream(argNames).skip(1).collect(Collectors.joining(","));
+ String wrappedSrc =
+ "var poly_enso_eval=function("
+ + args
+ + "){\n"
+ + code.getForeignSource()
+ + "\n};poly_enso_eval";
+ Source source = Source.newBuilder(code.getLanguage().getTruffleId(), wrappedSrc, "").build();
+ CallTarget ct = ctxRef.get().getEnv().parseInternal(source);
+ Object fn = rewrapNode.execute(ct.call(), inner, outer);
+ foreign = insert(JsForeignNodeGen.create(argNames.length, fn));
+ } finally {
+ inner.leave(p);
+ }
+ }
+}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignFunctionCallNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignFunctionCallNode.java
new file mode 100644
index 000000000000..207c7683b300
--- /dev/null
+++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignFunctionCallNode.java
@@ -0,0 +1,14 @@
+package org.enso.interpreter.epb.node;
+
+import com.oracle.truffle.api.nodes.Node;
+
+/** An interface for nodes responsible for calling into foreign languages. */
+public abstract class ForeignFunctionCallNode extends Node {
+ /**
+ * Executes the foreign call.
+ *
+ * @param arguments the arguments to pass to the foreign function
+ * @return the result of executing the foreign function
+ */
+ public abstract Object execute(Object[] arguments);
+}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/JsForeignNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/JsForeignNode.java
new file mode 100644
index 000000000000..42db304e02e1
--- /dev/null
+++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/JsForeignNode.java
@@ -0,0 +1,44 @@
+package org.enso.interpreter.epb.node;
+
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.interop.*;
+import com.oracle.truffle.api.library.CachedLibrary;
+import org.enso.interpreter.runtime.data.Array;
+
+/** A node responsible for performing foreign JS calls. */
+public abstract class JsForeignNode extends ForeignFunctionCallNode {
+
+ final Object jsFun;
+ private final int argsCount;
+
+ JsForeignNode(int argsCount, Object jsFun) {
+ this.argsCount = argsCount;
+ this.jsFun = jsFun;
+ }
+
+ /**
+ * Creates a new instance of this node.
+ *
+ * @param argumentsCount the number of arguments the function expects (including {@code this})
+ * @param jsFunction the parsed JS object (required to be {@link
+ * InteropLibrary#isExecutable(Object)})
+ * @return a node able to call the JS function with given arguments
+ */
+ public static JsForeignNode build(int argumentsCount, Object jsFunction) {
+ return JsForeignNodeGen.create(argumentsCount, jsFunction);
+ }
+
+ @Specialization
+ Object doExecute(Object[] arguments, @CachedLibrary("jsFun") InteropLibrary interopLibrary) {
+ Object[] positionalArgs = new Object[argsCount - 1];
+ if (argsCount - 1 >= 0) System.arraycopy(arguments, 1, positionalArgs, 0, argsCount - 1);
+ try {
+ return interopLibrary.invokeMember(jsFun, "apply", arguments[0], new Array(positionalArgs));
+ } catch (UnsupportedMessageException
+ | UnknownIdentifierException
+ | ArityException
+ | UnsupportedTypeException e) {
+ throw new IllegalStateException("Invalid JS function resulted from parsing.");
+ }
+ }
+}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/GuardedTruffleContext.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/GuardedTruffleContext.java
new file mode 100644
index 000000000000..3237f0ce6c3e
--- /dev/null
+++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/GuardedTruffleContext.java
@@ -0,0 +1,59 @@
+package org.enso.interpreter.epb.runtime;
+
+import com.oracle.truffle.api.TruffleContext;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/** Wraps a {@link TruffleContext} by providing an optional GIL functionality. */
+public class GuardedTruffleContext {
+ private final TruffleContext context;
+ private final Lock lock;
+
+ /**
+ * Creates a new instance of this wrapper.
+ *
+ * @param context the Truffle context to wrap
+ * @param isSingleThreaded whether or not the context should be accessed through a GIL.
+ */
+ public GuardedTruffleContext(TruffleContext context, boolean isSingleThreaded) {
+ this.context = context;
+ if (isSingleThreaded) {
+ this.lock = new ReentrantLock();
+ } else {
+ this.lock = null;
+ }
+ }
+
+ /**
+ * Enters this context. If this wrapper is single threaded and the context is in use, this method
+ * will block indefinitely until the context becomes available.
+ *
+ * Any code following a call to this method should be executed in a try/finally block, with
+ * {@link #leave(Object)} being called in the finally block. It is crucial that this context is
+ * always left as soon as guest code execution finishes.
+ *
+ *
The token returned from this method may not be stored or used for any purpose other than
+ * leaving the context.
+ *
+ * @return a context restoration token that must be passed to {@link #leave(Object)}
+ */
+ public Object enter() {
+ if (lock != null) {
+ lock.lock();
+ }
+ return context.enter();
+ }
+
+ /**
+ * Leaves the context and unlocks it if this wrapper is GILed.
+ *
+ * @param prev the token obtained from the call to {@link #enter()}
+ */
+ public void leave(Object prev) {
+ context.leave(prev);
+ if (lock != null) {
+ lock.unlock();
+ }
+ }
+}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/PolyglotProxy.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/PolyglotProxy.java
new file mode 100644
index 000000000000..659a30304401
--- /dev/null
+++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/PolyglotProxy.java
@@ -0,0 +1,248 @@
+package org.enso.interpreter.epb.runtime;
+
+import com.oracle.truffle.api.dsl.Cached;
+import com.oracle.truffle.api.interop.*;
+import com.oracle.truffle.api.library.CachedLibrary;
+import com.oracle.truffle.api.library.ExportLibrary;
+import com.oracle.truffle.api.library.ExportMessage;
+import org.enso.interpreter.epb.node.ContextRewrapNode;
+
+/**
+ * Wraps a polyglot value that is to be shared between Truffle contexts. See {@link
+ * org.enso.interpreter.epb.EpbLanguage} for the explanation of this system.
+ */
+@ExportLibrary(InteropLibrary.class)
+public class PolyglotProxy implements TruffleObject {
+ final Object delegate;
+ private final GuardedTruffleContext origin;
+ private final GuardedTruffleContext target;
+
+ /**
+ * Wraps a value for inter-context use.
+ *
+ * @param delegate the wrapped value
+ * @param origin the context in which {@code delegate} is a valid value
+ * @param target the context in which {@code delegate} will be accessed through this wrapper
+ */
+ public PolyglotProxy(
+ Object delegate, GuardedTruffleContext origin, GuardedTruffleContext target) {
+ this.delegate = delegate;
+ this.origin = origin;
+ this.target = target;
+ }
+
+ /** @return the wrapped value */
+ public Object getDelegate() {
+ return delegate;
+ }
+
+ /** @return the context in which the wrapped value originates */
+ public GuardedTruffleContext getOrigin() {
+ return origin;
+ }
+
+ /** @return the context in which this wrapper is supposed to be used */
+ public GuardedTruffleContext getTarget() {
+ return target;
+ }
+
+ @ExportMessage
+ public boolean isNull(@CachedLibrary("this.delegate") InteropLibrary nulls) {
+ Object p = origin.enter();
+ try {
+ return nulls.isNull(this.delegate);
+ } finally {
+ origin.leave(p);
+ }
+ }
+
+ @ExportMessage
+ public boolean hasMembers(@CachedLibrary("this.delegate") InteropLibrary members) {
+ Object p = origin.enter();
+ try {
+ return members.hasMembers(this.delegate);
+ } finally {
+ origin.leave(p);
+ }
+ }
+
+ @ExportMessage
+ public Object getMembers(
+ boolean includeInternal,
+ @CachedLibrary("this.delegate") InteropLibrary members,
+ @Cached @Cached.Exclusive ContextRewrapNode contextRewrapNode)
+ throws UnsupportedMessageException {
+ Object p = origin.enter();
+ try {
+ return contextRewrapNode.execute(
+ members.getMembers(this.delegate, includeInternal), origin, target);
+ } finally {
+ origin.leave(p);
+ }
+ }
+
+ @ExportMessage
+ public boolean isMemberInvocable(
+ String member, @CachedLibrary("this.delegate") InteropLibrary members) {
+ Object p = origin.enter();
+ try {
+ return members.isMemberInvocable(this.delegate, member);
+ } finally {
+ origin.leave(p);
+ }
+ }
+
+ @ExportMessage
+ public Object invokeMember(
+ String member,
+ Object[] arguments,
+ @CachedLibrary("this.delegate") InteropLibrary members,
+ @Cached @Cached.Exclusive ContextRewrapNode contextRewrapNode)
+ throws ArityException, UnknownIdentifierException, UnsupportedMessageException,
+ UnsupportedTypeException {
+ Object[] wrappedArgs = new Object[arguments.length];
+ for (int i = 0; i < arguments.length; i++) {
+ wrappedArgs[i] = contextRewrapNode.execute(arguments[i], target, origin);
+ }
+ Object p = origin.enter();
+ try {
+ return contextRewrapNode.execute(
+ members.invokeMember(this.delegate, member, wrappedArgs), origin, target);
+ } finally {
+ origin.leave(p);
+ }
+ }
+
+ @ExportMessage
+ public boolean isMemberReadable(
+ String member, @CachedLibrary("this.delegate") InteropLibrary members) {
+ Object p = origin.enter();
+ try {
+ return members.isMemberReadable(this.delegate, member);
+ } finally {
+ origin.leave(p);
+ }
+ }
+
+ @ExportMessage
+ public Object readMember(
+ String member,
+ @CachedLibrary("this.delegate") InteropLibrary members,
+ @Cached @Cached.Exclusive ContextRewrapNode contextRewrapNode)
+ throws UnknownIdentifierException, UnsupportedMessageException {
+ Object p = origin.enter();
+ try {
+ return contextRewrapNode.execute(members.readMember(this.delegate, member), origin, target);
+ } finally {
+ origin.leave(p);
+ }
+ }
+
+ @ExportMessage
+ public boolean isExecutable(@CachedLibrary("this.delegate") InteropLibrary functions) {
+ Object p = origin.enter();
+ try {
+ return functions.isExecutable(this.delegate);
+ } finally {
+ origin.leave(p);
+ }
+ }
+
+ @ExportMessage
+ public Object execute(
+ Object[] arguments,
+ @CachedLibrary("this.delegate") InteropLibrary functions,
+ @Cached @Cached.Exclusive ContextRewrapNode contextRewrapNode)
+ throws UnsupportedMessageException, ArityException, UnsupportedTypeException {
+ Object[] wrappedArgs = new Object[arguments.length];
+ for (int i = 0; i < arguments.length; i++) {
+ wrappedArgs[i] = contextRewrapNode.execute(arguments[i], target, origin);
+ }
+ Object p = origin.enter();
+ try {
+ return contextRewrapNode.execute(
+ functions.execute(this.delegate, wrappedArgs), origin, target);
+ } finally {
+ origin.leave(p);
+ }
+ }
+
+ @ExportMessage
+ public boolean hasArrayElements(@CachedLibrary("this.delegate") InteropLibrary arrays) {
+ Object p = origin.enter();
+ try {
+ return arrays.hasArrayElements(this.delegate);
+ } finally {
+ origin.leave(p);
+ }
+ }
+
+ @ExportMessage
+ public long getArraySize(@CachedLibrary("this.delegate") InteropLibrary arrays)
+ throws UnsupportedMessageException {
+ Object p = origin.enter();
+ try {
+ return arrays.getArraySize(this.delegate);
+ } finally {
+ origin.leave(p);
+ }
+ }
+
+ @ExportMessage
+ public boolean isArrayElementReadable(
+ long idx, @CachedLibrary("this.delegate") InteropLibrary arrays) {
+ Object p = origin.enter();
+ try {
+ return arrays.isArrayElementReadable(this.delegate, idx);
+ } finally {
+ origin.leave(p);
+ }
+ }
+
+ @ExportMessage
+ public Object readArrayElement(
+ long index,
+ @CachedLibrary("this.delegate") InteropLibrary arrays,
+ @Cached @Cached.Exclusive ContextRewrapNode contextRewrapNode)
+ throws InvalidArrayIndexException, UnsupportedMessageException {
+ Object p = origin.enter();
+ try {
+ return contextRewrapNode.execute(
+ arrays.readArrayElement(this.delegate, index), origin, target);
+ } finally {
+ origin.leave(p);
+ }
+ }
+
+ @ExportMessage
+ public boolean isString(@CachedLibrary("this.delegate") InteropLibrary strings) {
+ Object p = origin.enter();
+ try {
+ return strings.isString(this.delegate);
+ } finally {
+ origin.leave(p);
+ }
+ }
+
+ @ExportMessage
+ public String asString(@CachedLibrary("this.delegate") InteropLibrary strings)
+ throws UnsupportedMessageException {
+ Object p = origin.enter();
+ try {
+ return strings.asString(this.delegate);
+ } finally {
+ origin.leave(p);
+ }
+ }
+
+ @ExportMessage
+ public Object toDisplayString(
+ boolean allowSideEffects, @CachedLibrary("this.delegate") InteropLibrary displays) {
+ Object p = origin.enter();
+ try {
+ return displays.toDisplayString(this.delegate, allowSideEffects);
+ } finally {
+ origin.leave(p);
+ }
+ }
+}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeMethodNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeMethodNode.java
index c831b8f6f1ea..23c109c5e27a 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeMethodNode.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeMethodNode.java
@@ -135,7 +135,8 @@ Stateful doPanicSentinel(
guards = {
"!methods.hasFunctionalDispatch(_this)",
"!methods.hasSpecialDispatch(_this)",
- "polyglotCallType != NOT_SUPPORTED"
+ "polyglotCallType != NOT_SUPPORTED",
+ "polyglotCallType != CONVERT_TO_TEXT"
})
Stateful doPolyglot(
MaterializedFrame frame,
@@ -166,6 +167,49 @@ Stateful doPolyglot(
state, hostMethodCallNode.execute(polyglotCallType, symbol.getName(), _this, args));
}
+ @Specialization(
+ guards = {
+ "!methods.hasFunctionalDispatch(_this)",
+ "!methods.hasSpecialDispatch(_this)",
+ "getPolyglotCallType(_this, symbol.getName(), interop) == CONVERT_TO_TEXT"
+ })
+ Stateful doConvertText(
+ MaterializedFrame frame,
+ Object state,
+ UnresolvedSymbol symbol,
+ Object _this,
+ Object[] arguments,
+ CallArgumentInfo[] schema,
+ InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode,
+ InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode,
+ BaseNode.TailStatus isTail,
+ @CachedLibrary(limit = "10") MethodDispatchLibrary methods,
+ @CachedLibrary(limit = "1") MethodDispatchLibrary textDispatch,
+ @CachedLibrary(limit = "10") InteropLibrary interop,
+ @CachedContext(Language.class) TruffleLanguage.ContextReference ctx,
+ @Cached IndirectInvokeFunctionNode invokeFunctionNode) {
+ try {
+ String str = interop.asString(_this);
+ Text txt = Text.create(str);
+ Function function = textDispatch.getFunctionalDispatch(txt, symbol);
+ arguments[0] = txt;
+ return invokeFunctionNode.execute(
+ function,
+ frame,
+ state,
+ arguments,
+ schema,
+ defaultsExecutionMode,
+ argumentsExecutionMode,
+ isTail);
+ } catch (UnsupportedMessageException e) {
+ throw new IllegalStateException("Impossible, _this is guaranteed to be a string.");
+ } catch (MethodDispatchLibrary.NoSuchMethodException e) {
+ throw new PanicException(
+ ctx.get().getBuiltins().error().makeNoSuchMethodError(_this, symbol), this);
+ }
+ }
+
@ExplodeLoop
@Specialization(
guards = {
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java
index 9b822a523094..617b045226ea 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java
@@ -5,6 +5,7 @@
import com.oracle.truffle.api.dsl.*;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.InteropLibrary;
+import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.Node;
@@ -20,6 +21,7 @@
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
import org.enso.interpreter.runtime.callable.function.Function;
+import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.error.DataflowError;
import org.enso.interpreter.runtime.error.PanicSentinel;
import org.enso.interpreter.runtime.error.PanicException;
@@ -114,7 +116,8 @@ Stateful doPanicSentinel(
guards = {
"!methods.hasFunctionalDispatch(_this)",
"!methods.hasSpecialDispatch(_this)",
- "polyglotCallType != NOT_SUPPORTED"
+ "polyglotCallType != NOT_SUPPORTED",
+ "polyglotCallType != CONVERT_TO_TEXT"
})
Stateful doPolyglot(
VirtualFrame frame,
@@ -139,7 +142,36 @@ Stateful doPolyglot(
state, hostMethodCallNode.execute(polyglotCallType, symbol.getName(), _this, args));
}
- @ExplodeLoop
+ @Specialization(
+ guards = {
+ "!methods.hasFunctionalDispatch(_this)",
+ "!methods.hasSpecialDispatch(_this)",
+ "getPolyglotCallType(_this, symbol.getName(), interop) == CONVERT_TO_TEXT"
+ })
+ Stateful doConvertText(
+ VirtualFrame frame,
+ Object state,
+ UnresolvedSymbol symbol,
+ Object _this,
+ Object[] arguments,
+ @CachedLibrary(limit = "10") MethodDispatchLibrary methods,
+ @CachedLibrary(limit = "1") MethodDispatchLibrary textDispatch,
+ @CachedLibrary(limit = "10") InteropLibrary interop,
+ @CachedContext(Language.class) TruffleLanguage.ContextReference ctx) {
+ try {
+ String str = interop.asString(_this);
+ Text txt = Text.create(str);
+ Function function = textDispatch.getFunctionalDispatch(txt, symbol);
+ arguments[0] = txt;
+ return invokeFunctionNode.execute(function, frame, state, arguments);
+ } catch (UnsupportedMessageException e) {
+ throw new IllegalStateException("Impossible, _this is guaranteed to be a string.");
+ } catch (MethodDispatchLibrary.NoSuchMethodException e) {
+ throw new PanicException(
+ ctx.get().getBuiltins().error().makeNoSuchMethodError(_this, symbol), this);
+ }
+ }
+
@Specialization(
guards = {
"!methods.hasFunctionalDispatch(_this)",
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/resolver/HostMethodCallNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/resolver/HostMethodCallNode.java
index 6b392f665dba..00d7944ea030 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/resolver/HostMethodCallNode.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/resolver/HostMethodCallNode.java
@@ -18,11 +18,33 @@ public abstract class HostMethodCallNode extends Node {
/** Represents a mode of calling a method on a polyglot value. */
public enum PolyglotCallType {
+ /**
+ * The method call should be handled through {@link InteropLibrary#invokeMember(Object, String,
+ * Object...)}.
+ */
CALL_METHOD,
+ /**
+ * The method call should be handled through {@link InteropLibrary#readMember(Object, String)}.
+ */
GET_MEMBER,
+ /**
+ * The method call should be handled through {@link InteropLibrary#instantiate(Object,
+ * Object...)}.
+ */
INSTANTIATE,
+ /** The method call should be handled through {@link InteropLibrary#getArraySize(Object)}. */
GET_ARRAY_LENGTH,
+ /**
+ * The method call should be handled through {@link InteropLibrary#readArrayElement(Object,
+ * long)}.
+ */
READ_ARRAY_ELEMENT,
+ /**
+ * The method call should be handled by converting {@code _this} to a {@link
+ * org.enso.interpreter.runtime.data.text.Text} and dispatching natively.
+ */
+ CONVERT_TO_TEXT,
+ /** The method call should be handled by dispatching through the {@code Any} type. */
NOT_SUPPORTED
}
@@ -53,6 +75,8 @@ public static PolyglotCallType getPolyglotCallType(
return PolyglotCallType.GET_ARRAY_LENGTH;
} else if (library.hasArrayElements(_this) && methodName.equals(ARRAY_READ_NAME)) {
return PolyglotCallType.READ_ARRAY_ELEMENT;
+ } else if (library.isString(_this)) {
+ return PolyglotCallType.CONVERT_TO_TEXT;
} else {
return PolyglotCallType.NOT_SUPPORTED;
}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/TextBranchNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/TextBranchNode.java
index d229dcde1261..2b2cbbe2ee2e 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/TextBranchNode.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/TextBranchNode.java
@@ -1,14 +1,16 @@
package org.enso.interpreter.node.controlflow.caseexpr;
import com.oracle.truffle.api.RootCallTarget;
+import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.interop.InteropLibrary;
+import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.profiles.ConditionProfile;
import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
-import org.enso.interpreter.runtime.data.text.Text;
@NodeInfo(shortName = "TextMatch", description = "Allows matching on the Text type.")
public abstract class TextBranchNode extends BranchNode {
@@ -38,8 +40,12 @@ void doConstructor(VirtualFrame frame, Object state, Atom target) {
}
}
- @Specialization
- void doLiteral(VirtualFrame frame, Object state, Text target) {
+ @Specialization(guards = "strings.isString(target)")
+ void doLiteral(
+ VirtualFrame frame,
+ Object state,
+ Object target,
+ @CachedLibrary(limit = "10") InteropLibrary strings) {
accept(frame, state, new Object[0]);
}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/debug/DebugEvalNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/debug/DebugEvalNode.java
index 38ef7a9e6fcc..abc1af76c44a 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/debug/DebugEvalNode.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/debug/DebugEvalNode.java
@@ -4,6 +4,7 @@
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.dsl.MonadicState;
import org.enso.interpreter.node.BaseNode;
+import org.enso.interpreter.node.expression.builtin.text.util.ExpectTextNode;
import org.enso.interpreter.node.expression.debug.EvalNode;
import org.enso.interpreter.runtime.callable.CallerInfo;
import org.enso.interpreter.runtime.data.text.Text;
@@ -16,13 +17,14 @@
description = "Evaluates an expression passed as a Text argument, in the caller frame.")
public class DebugEvalNode extends Node {
private @Child EvalNode evalNode = EvalNode.build();
+ private @Child ExpectTextNode expectTextNode = ExpectTextNode.build();
DebugEvalNode() {
evalNode.setTailStatus(BaseNode.TailStatus.TAIL_DIRECT);
}
Stateful execute(
- CallerInfo callerInfo, @MonadicState Object state, Object _this, Text expression) {
- return evalNode.execute(callerInfo, state, expression);
+ CallerInfo callerInfo, @MonadicState Object state, Object _this, Object expression) {
+ return evalNode.execute(callerInfo, state, expectTextNode.execute(expression));
}
}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/EvalNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/EvalNode.java
deleted file mode 100644
index dfbad794ea19..000000000000
--- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/EvalNode.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package org.enso.interpreter.node.expression.builtin.interop.generic;
-
-import com.oracle.truffle.api.CallTarget;
-import com.oracle.truffle.api.dsl.Cached;
-import com.oracle.truffle.api.dsl.CachedContext;
-import com.oracle.truffle.api.dsl.ReportPolymorphism;
-import com.oracle.truffle.api.dsl.Specialization;
-import com.oracle.truffle.api.nodes.DirectCallNode;
-import com.oracle.truffle.api.nodes.IndirectCallNode;
-import com.oracle.truffle.api.nodes.Node;
-import com.oracle.truffle.api.source.Source;
-import org.enso.interpreter.Language;
-import org.enso.interpreter.dsl.BuiltinMethod;
-import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode;
-import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode;
-import org.enso.interpreter.runtime.Context;
-import org.enso.interpreter.runtime.data.text.Text;
-
-@BuiltinMethod(
- type = "Polyglot",
- name = "eval",
- description = "Evaluates a foreign language string.")
-@ReportPolymorphism
-public abstract class EvalNode extends Node {
- static final int LIMIT = 10;
-
- static EvalNode build() {
- return EvalNodeGen.create();
- }
-
- abstract Object execute(Object _this, Text language, Text code);
-
- @Specialization(
- guards = {"cachedLanguage == language", "cachedCode == code"},
- limit = "LIMIT")
- Object doCached(
- Object _this,
- Text language,
- Text code,
- @CachedContext(Language.class) Context context,
- @Cached("language") Text cachedLanguage,
- @Cached("code") Text cachedCode,
- @Cached("build()") ToJavaStringNode toJavaStringNode,
- @Cached("parse(context, cachedLanguage, cachedCode, toJavaStringNode)") CallTarget callTarget,
- @Cached("create(callTarget)") DirectCallNode callNode,
- @Cached("build()") HostValueToEnsoNode hostValueToEnsoNode) {
- return hostValueToEnsoNode.execute(callNode.call());
- }
-
- @Specialization(replaces = "doCached")
- Object doUncached(
- Object _this,
- Text language,
- Text code,
- @CachedContext(Language.class) Context context,
- @Cached IndirectCallNode callNode,
- @Cached("build()") ToJavaStringNode toJavaStringNode,
- @Cached("build()") HostValueToEnsoNode hostValueToEnsoNode) {
- CallTarget ct = parse(context, language, code, toJavaStringNode);
- return hostValueToEnsoNode.execute(callNode.call(ct));
- }
-
- CallTarget parse(Context context, Text language, Text code, ToJavaStringNode toJavaStringNode) {
- String languageStr = toJavaStringNode.execute(language);
- String codeStr = toJavaStringNode.execute(code);
-
- Source source = Source.newBuilder(languageStr, codeStr, "").build();
- return context.getEnvironment().parsePublic(source);
- }
-}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetArraySizeNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetArraySizeNode.java
new file mode 100644
index 000000000000..032cd3b39742
--- /dev/null
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetArraySizeNode.java
@@ -0,0 +1,31 @@
+package org.enso.interpreter.node.expression.builtin.interop.generic;
+
+import com.oracle.truffle.api.interop.*;
+import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.api.profiles.BranchProfile;
+import org.enso.interpreter.Constants;
+import org.enso.interpreter.Language;
+import org.enso.interpreter.dsl.BuiltinMethod;
+import org.enso.interpreter.runtime.builtin.Builtins;
+import org.enso.interpreter.runtime.error.PanicException;
+
+@BuiltinMethod(
+ type = "Polyglot",
+ name = "get_array_size",
+ description = "Returns the size of a polyglot array.")
+public class GetArraySizeNode extends Node {
+ private @Child InteropLibrary library =
+ InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH);
+ private final BranchProfile err = BranchProfile.create();
+
+ long execute(Object _this, Object array) {
+ try {
+ return library.getArraySize(array);
+ } catch (UnsupportedMessageException e) {
+ err.enter();
+ Builtins builtins = lookupContextReference(Language.class).get().getBuiltins();
+ throw new PanicException(
+ builtins.error().makeTypeError(builtins.mutable().array(), array), this);
+ }
+ }
+}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetMemberNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetMemberNode.java
index 31e04ea81aad..1bd964c5d9e3 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetMemberNode.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetMemberNode.java
@@ -7,6 +7,8 @@
import com.oracle.truffle.api.profiles.BranchProfile;
import org.enso.interpreter.Constants;
import org.enso.interpreter.dsl.BuiltinMethod;
+import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode;
+import org.enso.interpreter.node.expression.builtin.text.util.ExpectTextNode;
import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode;
import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.error.PanicException;
@@ -18,12 +20,12 @@
public class GetMemberNode extends Node {
private @Child InteropLibrary library =
InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH);
- private @Child ToJavaStringNode toJavaStringNode = ToJavaStringNode.build();
+ private @Child ExpectStringNode expectStringNode = ExpectStringNode.build();
private final BranchProfile err = BranchProfile.create();
- Object execute(Object _this, Object object, Text member_name) {
+ Object execute(Object _this, Object object, Object member_name) {
try {
- return library.readMember(object, toJavaStringNode.execute(member_name));
+ return library.readMember(object, expectStringNode.execute(member_name));
} catch (UnsupportedMessageException | UnknownIdentifierException e) {
err.enter();
throw new PanicException(e.getMessage(), this);
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/InvokeNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/InvokeNode.java
index 5b37f891ebc7..4909e60ccb1e 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/InvokeNode.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/InvokeNode.java
@@ -5,6 +5,8 @@
import com.oracle.truffle.api.profiles.BranchProfile;
import org.enso.interpreter.Constants;
import org.enso.interpreter.dsl.BuiltinMethod;
+import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode;
+import org.enso.interpreter.node.expression.builtin.text.util.ExpectTextNode;
import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode;
import org.enso.interpreter.runtime.data.Array;
import org.enso.interpreter.runtime.data.text.Text;
@@ -17,12 +19,12 @@
public class InvokeNode extends Node {
private @Child InteropLibrary library =
InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH);
- private @Child ToJavaStringNode toJavaStringNode = ToJavaStringNode.build();
+ private @Child ExpectStringNode expectStringNode = ExpectStringNode.build();
private final BranchProfile err = BranchProfile.create();
- Object execute(Object _this, Object target, Text name, Array arguments) {
+ Object execute(Object _this, Object target, Object name, Array arguments) {
try {
- return library.invokeMember(target, toJavaStringNode.execute(name), arguments.getItems());
+ return library.invokeMember(target, expectStringNode.execute(name), arguments.getItems());
} catch (UnsupportedMessageException
| ArityException
| UnsupportedTypeException
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/java/AddToClassPathNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/java/AddToClassPathNode.java
index 33b8cd64c556..d8e298341aca 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/java/AddToClassPathNode.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/java/AddToClassPathNode.java
@@ -6,9 +6,8 @@
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.Language;
import org.enso.interpreter.dsl.BuiltinMethod;
-import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode;
+import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode;
import org.enso.interpreter.runtime.Context;
-import org.enso.interpreter.runtime.data.text.Text;
import java.io.File;
@@ -25,14 +24,14 @@ static AddToClassPathNode build() {
@Specialization
Object doExecute(
Object _this,
- Text path,
+ Object path,
@CachedContext(Language.class) Context context,
- @Cached("build()") ToJavaStringNode toJavaStringNode) {
+ @Cached ExpectStringNode expectStringNode) {
context
.getEnvironment()
- .addToHostClassPath(context.getTruffleFile(new File(toJavaStringNode.execute(path))));
+ .addToHostClassPath(context.getTruffleFile(new File(expectStringNode.execute(path))));
return context.getBuiltins().nothing();
}
- abstract Object execute(Object _this, Text path);
+ abstract Object execute(Object _this, Object path);
}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/java/LookupClassNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/java/LookupClassNode.java
index df9a9679d7e9..38fa7eddfed6 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/java/LookupClassNode.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/java/LookupClassNode.java
@@ -6,9 +6,8 @@
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.Language;
import org.enso.interpreter.dsl.BuiltinMethod;
-import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode;
+import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode;
import org.enso.interpreter.runtime.Context;
-import org.enso.interpreter.runtime.data.text.Text;
@BuiltinMethod(type = "Java", name = "lookup_class", description = "Looks up a Java symbol.")
public abstract class LookupClassNode extends Node {
@@ -19,11 +18,11 @@ static LookupClassNode build() {
@Specialization
Object doExecute(
Object _this,
- Text name,
+ Object name,
@CachedContext(Language.class) Context ctx,
- @Cached("build()") ToJavaStringNode toJavaStringNode) {
- return ctx.getEnvironment().lookupHostSymbol(toJavaStringNode.execute(name));
+ @Cached("build()") ExpectStringNode expectStringNode) {
+ return ctx.getEnvironment().lookupHostSymbol(expectStringNode.execute(name));
}
- abstract Object execute(Object _this, Text name);
+ abstract Object execute(Object _this, Object name);
}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/GetFileNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/GetFileNode.java
index aff304d66024..f550c4d983f0 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/GetFileNode.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/GetFileNode.java
@@ -1,23 +1,17 @@
package org.enso.interpreter.node.expression.builtin.io;
-import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.TruffleFile;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.CachedContext;
import com.oracle.truffle.api.dsl.Specialization;
-import com.oracle.truffle.api.interop.InteropLibrary;
-import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.Language;
import org.enso.interpreter.dsl.BuiltinMethod;
-import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode;
+import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.data.EnsoFile;
import org.enso.interpreter.runtime.data.text.Text;
-import java.io.IOException;
-import java.util.stream.Collectors;
-
@BuiltinMethod(
type = "Prim_Io",
name = "get_file",
@@ -28,15 +22,15 @@ static GetFileNode build() {
return GetFileNodeGen.create();
}
- abstract Object execute(Object _this, Text path);
+ abstract Object execute(Object _this, Object path);
@Specialization
Object doGetFile(
Object _this,
- Text path,
+ Object path,
@CachedContext(Language.class) Context ctx,
- @Cached("build()") ToJavaStringNode toJavaStringNode) {
- String pathStr = toJavaStringNode.execute(path);
+ @Cached("build()") ExpectStringNode expectStringNode) {
+ String pathStr = expectStringNode.execute(path);
TruffleFile file = ctx.getEnvironment().getPublicTruffleFile(pathStr);
EnsoFile ensoFile = new EnsoFile(file);
return ctx.getEnvironment().asGuestValue(ensoFile);
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintErrNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintErrNode.java
index f1fa9175e9c4..e1a256796d63 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintErrNode.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintErrNode.java
@@ -5,6 +5,9 @@
import com.oracle.truffle.api.dsl.CachedContext;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.interop.InteropLibrary;
+import com.oracle.truffle.api.interop.UnsupportedMessageException;
+import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.Node;
import java.io.PrintStream;
import org.enso.interpreter.Language;
@@ -12,6 +15,7 @@
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.dsl.MonadicState;
import org.enso.interpreter.node.callable.InvokeCallableNode;
+import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode;
import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
@@ -32,30 +36,35 @@ static PrintErrNode build() {
abstract Stateful execute(
VirtualFrame frame, @MonadicState Object state, Object _this, @AcceptsError Object message);
- @Specialization
+ @Specialization(guards = "strings.isString(message)")
Stateful doPrintText(
VirtualFrame frame,
Object state,
Object self,
- Text message,
+ Object message,
@CachedContext(Language.class) Context ctx,
- @Cached("build()") ToJavaStringNode toJavaStringNode) {
- print(ctx.getErr(), toJavaStringNode.execute(message));
+ @CachedLibrary(limit = "10") InteropLibrary strings) {
+ try {
+ print(ctx.getErr(), strings.asString(message));
+ } catch (UnsupportedMessageException e) {
+ throw new IllegalStateException("Impossible. self is guaranteed to be a string");
+ }
return new Stateful(state, ctx.getUnit().newInstance());
}
- @Specialization(guards = "!isText(message)")
+ @Specialization(guards = "!strings.isString(message)")
Stateful doPrint(
VirtualFrame frame,
Object state,
Object self,
Object message,
@CachedContext(Language.class) Context ctx,
+ @CachedLibrary(limit = "10") InteropLibrary strings,
@Cached("buildSymbol(ctx)") UnresolvedSymbol symbol,
@Cached("buildInvokeCallableNode()") InvokeCallableNode invokeCallableNode,
- @Cached("build()") ToJavaStringNode toJavaStringNode) {
+ @Cached ExpectStringNode expectStringNode) {
Stateful str = invokeCallableNode.execute(symbol, frame, state, new Object[] {message});
- print(ctx.getErr(), toJavaStringNode.execute((Text) str.getValue()));
+ print(ctx.getErr(), expectStringNode.execute(str.getValue()));
return new Stateful(str.getState(), ctx.getUnit().newInstance());
}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintlnNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintlnNode.java
index b549b4ebc240..268c64f93491 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintlnNode.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintlnNode.java
@@ -5,6 +5,9 @@
import com.oracle.truffle.api.dsl.CachedContext;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.interop.InteropLibrary;
+import com.oracle.truffle.api.interop.UnsupportedMessageException;
+import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.Node;
import java.io.PrintStream;
import org.enso.interpreter.Language;
@@ -12,6 +15,7 @@
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.dsl.MonadicState;
import org.enso.interpreter.node.callable.InvokeCallableNode;
+import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode;
import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
@@ -31,31 +35,35 @@ public abstract class PrintlnNode extends Node {
abstract Stateful execute(
VirtualFrame frame, @MonadicState Object state, Object _this, @AcceptsError Object message);
- @Specialization
+ @Specialization(guards = "strings.isString(message)")
Stateful doPrintText(
VirtualFrame frame,
Object state,
- Object _this,
- Text message,
+ Object self,
+ Object message,
@CachedContext(Language.class) Context ctx,
- @Cached("build()") ToJavaStringNode toJavaStringNode) {
- print(ctx.getOut(), toJavaStringNode.execute(message));
+ @CachedLibrary(limit = "10") InteropLibrary strings) {
+ try {
+ print(ctx.getOut(), strings.asString(message));
+ } catch (UnsupportedMessageException e) {
+ throw new IllegalStateException("Impossible. self is guaranteed to be a string");
+ }
return new Stateful(state, ctx.getUnit().newInstance());
}
- @Specialization(guards = "!isText(message)")
+ @Specialization(guards = "!strings.isString(message)")
Stateful doPrint(
VirtualFrame frame,
Object state,
- Object _this,
+ Object self,
Object message,
@CachedContext(Language.class) Context ctx,
+ @CachedLibrary(limit = "10") InteropLibrary strings,
@Cached("buildSymbol(ctx)") UnresolvedSymbol symbol,
- @Cached("build()") ToJavaStringNode toJavaStringNode,
- @Cached("buildInvokeCallableNode()") InvokeCallableNode invokeCallableNode) {
+ @Cached("buildInvokeCallableNode()") InvokeCallableNode invokeCallableNode,
+ @Cached ExpectStringNode expectStringNode) {
Stateful str = invokeCallableNode.execute(symbol, frame, state, new Object[] {message});
- String strr = toJavaStringNode.execute((Text) str.getValue());
- print(ctx.getOut(), strr);
+ print(ctx.getOut(), expectStringNode.execute(str.getValue()));
return new Stateful(str.getState(), ctx.getUnit().newInstance());
}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/CreateUnresolvedSymbolNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/CreateUnresolvedSymbolNode.java
index 2d5fb6fc8f39..b7314af27586 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/CreateUnresolvedSymbolNode.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/CreateUnresolvedSymbolNode.java
@@ -2,6 +2,7 @@
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.BuiltinMethod;
+import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode;
import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.data.text.Text;
@@ -12,9 +13,9 @@
name = "create_unresolved_symbol",
description = "Creates a new unresolved symbol node")
public class CreateUnresolvedSymbolNode extends Node {
- private @Child ToJavaStringNode toJavaStringNode = ToJavaStringNode.build();
+ private @Child ExpectStringNode expectStringNode = ExpectStringNode.build();
- UnresolvedSymbol execute(Object _this, Text name, ModuleScope scope) {
- return UnresolvedSymbol.build(toJavaStringNode.execute(name), scope);
+ UnresolvedSymbol execute(Object _this, Object name, ModuleScope scope) {
+ return UnresolvedSymbol.build(expectStringNode.execute(name), scope);
}
}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/system/CreateProcessNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/system/CreateProcessNode.java
index 714fc70025c8..9e6a47c5f20d 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/system/CreateProcessNode.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/system/CreateProcessNode.java
@@ -8,6 +8,7 @@
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.Language;
import org.enso.interpreter.dsl.BuiltinMethod;
+import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode;
import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.data.Array;
@@ -28,9 +29,9 @@ static CreateProcessNode build() {
abstract Object execute(
Object _this,
- Text command,
+ Object command,
Array arguments,
- Text input,
+ Object input,
boolean redirectIn,
boolean redirectOut,
boolean redirectErr);
@@ -39,25 +40,25 @@ abstract Object execute(
@CompilerDirectives.TruffleBoundary
Object doCreate(
Object _this,
- Text command,
+ Object command,
Array arguments,
- Text input,
+ Object input,
boolean redirectIn,
boolean redirectOut,
boolean redirectErr,
@CachedContext(Language.class) Context ctx,
- @Cached("build()") ToJavaStringNode toJavaStringNode) {
+ @Cached ExpectStringNode expectStringNode) {
String[] cmd = new String[arguments.getItems().length + 1];
- cmd[0] = toJavaStringNode.execute(command);
+ cmd[0] = expectStringNode.execute(command);
for (int i = 1; i <= arguments.getItems().length; i++) {
- cmd[i] = toJavaStringNode.execute((Text) arguments.getItems()[i - 1]);
+ cmd[i] = expectStringNode.execute(arguments.getItems()[i - 1]);
}
TruffleProcessBuilder pb = ctx.getEnvironment().newProcessBuilder(cmd);
try {
Process p = pb.start();
ByteArrayInputStream in =
- new ByteArrayInputStream(toJavaStringNode.execute(input).getBytes());
+ new ByteArrayInputStream(expectStringNode.execute(input).getBytes());
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream err = new ByteArrayOutputStream();
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/ConcatNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/ConcatNode.java
index 3a144fa83bd6..bd04291a46c1 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/ConcatNode.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/ConcatNode.java
@@ -1,12 +1,28 @@
package org.enso.interpreter.node.expression.builtin.text;
+import com.oracle.truffle.api.dsl.Cached;
+import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.BuiltinMethod;
+import org.enso.interpreter.node.expression.builtin.text.util.ExpectTextNode;
import org.enso.interpreter.runtime.data.text.Text;
@BuiltinMethod(type = "Text", name = "+", description = "Text concatenation.")
-public class ConcatNode extends Node {
- Text execute(Text _this, Text that) {
- return Text.create(_this, that);
+public abstract class ConcatNode extends Node {
+ abstract Text execute(Object _this, Object that);
+
+ static ConcatNode build() {
+ return ConcatNodeGen.create();
+ }
+
+ @Specialization
+ Text doExecute(
+ Object _this,
+ Object that,
+ @Cached ExpectTextNode leftCast,
+ @Cached ExpectTextNode rightCast) {
+ Text l = leftCast.execute(_this);
+ Text r = rightCast.execute(that);
+ return Text.create(l, r);
}
}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/OptimizeNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/OptimizeNode.java
index 34f40da18db4..bc88b946fd3b 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/OptimizeNode.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/OptimizeNode.java
@@ -1,6 +1,8 @@
package org.enso.interpreter.node.expression.builtin.text;
import com.oracle.truffle.api.CompilerDirectives;
+import com.oracle.truffle.api.dsl.Fallback;
+import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode;
@@ -10,11 +12,23 @@
type = "Prim_Text_Helper",
name = "optimize",
description = "Forces flattening of a text value, for testing or purposes.")
-public class OptimizeNode extends Node {
+public abstract class OptimizeNode extends Node {
private @Child ToJavaStringNode toJavaStringNode = ToJavaStringNode.build();
- Text execute(Object _this, Text text) {
+ static OptimizeNode build() {
+ return OptimizeNodeGen.create();
+ }
+
+ abstract Object execute(Object _this, Object text);
+
+ @Specialization
+ Text doText(Object _this, Text text) {
toJavaStringNode.execute(text);
return text;
}
+
+ @Fallback
+ Object doOther(Object _this, Object that) {
+ return that;
+ }
}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/ExpectStringNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/ExpectStringNode.java
new file mode 100644
index 000000000000..6467630a96ee
--- /dev/null
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/ExpectStringNode.java
@@ -0,0 +1,44 @@
+package org.enso.interpreter.node.expression.builtin.text.util;
+
+import com.oracle.truffle.api.dsl.Cached;
+import com.oracle.truffle.api.dsl.Fallback;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.interop.InteropLibrary;
+import com.oracle.truffle.api.interop.UnsupportedMessageException;
+import com.oracle.truffle.api.nodes.Node;
+import org.enso.interpreter.Language;
+import org.enso.interpreter.runtime.builtin.Builtins;
+import org.enso.interpreter.runtime.callable.atom.Atom;
+import org.enso.interpreter.runtime.data.text.Text;
+import org.enso.interpreter.runtime.error.PanicException;
+
+public abstract class ExpectStringNode extends Node {
+ private @Child InteropLibrary library = InteropLibrary.getFactory().createDispatched(10);
+
+ public abstract String execute(Object o);
+
+ public static ExpectStringNode build() {
+ return ExpectStringNodeGen.create();
+ }
+
+ @Specialization
+ String doText(Text o, @Cached("build()") ToJavaStringNode toJavaStringNode) {
+ return toJavaStringNode.execute(o);
+ }
+
+ @Specialization
+ String doString(String o) {
+ return o;
+ }
+
+ @Fallback
+ String doFallback(Object o) {
+ try {
+ return library.asString(o);
+ } catch (UnsupportedMessageException e) {
+ Builtins builtins = lookupContextReference(Language.class).get().getBuiltins();
+ Atom err = builtins.error().makeTypeError(builtins.text().getText(), o);
+ throw new PanicException(err, this);
+ }
+ }
+}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/ExpectTextNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/ExpectTextNode.java
new file mode 100644
index 000000000000..8442653c8121
--- /dev/null
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/util/ExpectTextNode.java
@@ -0,0 +1,45 @@
+package org.enso.interpreter.node.expression.builtin.text.util;
+
+import com.oracle.truffle.api.dsl.Fallback;
+import com.oracle.truffle.api.dsl.ReportPolymorphism;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.interop.InteropLibrary;
+import com.oracle.truffle.api.interop.UnsupportedMessageException;
+import com.oracle.truffle.api.nodes.Node;
+import org.enso.interpreter.Language;
+import org.enso.interpreter.runtime.builtin.Builtins;
+import org.enso.interpreter.runtime.callable.atom.Atom;
+import org.enso.interpreter.runtime.data.text.Text;
+import org.enso.interpreter.runtime.error.PanicException;
+
+@ReportPolymorphism
+public abstract class ExpectTextNode extends Node {
+ private @Child InteropLibrary library = InteropLibrary.getFactory().createDispatched(10);
+
+ public abstract Text execute(Object o);
+
+ public static ExpectTextNode build() {
+ return ExpectTextNodeGen.create();
+ }
+
+ @Specialization
+ Text doText(Text o) {
+ return o;
+ }
+
+ @Specialization
+ Text doString(String o) {
+ return Text.create(o);
+ }
+
+ @Fallback
+ Text doFallback(Object o) {
+ try {
+ return Text.create(library.asString(o));
+ } catch (UnsupportedMessageException e) {
+ Builtins builtins = lookupContextReference(Language.class).get().getBuiltins();
+ Atom err = builtins.error().makeTypeError(builtins.text().getText(), o);
+ throw new PanicException(err, this);
+ }
+ }
+}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/foreign/ForeignMethodCallNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/foreign/ForeignMethodCallNode.java
new file mode 100644
index 000000000000..786bd74325b4
--- /dev/null
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/foreign/ForeignMethodCallNode.java
@@ -0,0 +1,39 @@
+package org.enso.interpreter.node.expression.foreign;
+
+import com.oracle.truffle.api.CallTarget;
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.nodes.DirectCallNode;
+import com.oracle.truffle.api.nodes.ExplodeLoop;
+import org.enso.interpreter.node.ExpressionNode;
+
+/** Performs a call into a given foreign call target. */
+public class ForeignMethodCallNode extends ExpressionNode {
+ private @Children ExpressionNode[] arguments;
+ private @Child DirectCallNode callNode;
+
+ ForeignMethodCallNode(ExpressionNode[] arguments, CallTarget foreignCt) {
+ this.arguments = arguments;
+ this.callNode = DirectCallNode.create(foreignCt);
+ }
+
+ /**
+ * Creates a new instance of this node
+ *
+ * @param arguments expressions resulting in the computation of function arguments
+ * @param foreignCt the foreign call target to call
+ * @return the result of calling the foreign call target with the executed arguments
+ */
+ public static ForeignMethodCallNode build(ExpressionNode[] arguments, CallTarget foreignCt) {
+ return new ForeignMethodCallNode(arguments, foreignCt);
+ }
+
+ @Override
+ @ExplodeLoop
+ public Object executeGeneric(VirtualFrame frame) {
+ Object[] args = new Object[arguments.length];
+ for (int i = 0; i < arguments.length; i++) {
+ args[i] = arguments[i].executeGeneric(frame);
+ }
+ return callNode.call(args);
+ }
+}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Polyglot.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Polyglot.java
index 136d4635e87f..de2ef9775a7d 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Polyglot.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Polyglot.java
@@ -26,9 +26,9 @@ private void createPolyglot(Language language, ModuleScope scope) {
scope.registerMethod(polyglot, "execute", ExecuteMethodGen.makeFunction(language));
scope.registerMethod(polyglot, "invoke", InvokeMethodGen.makeFunction(language));
scope.registerMethod(polyglot, "new", InstantiateMethodGen.makeFunction(language));
- scope.registerMethod(polyglot, "eval", EvalMethodGen.makeFunction(language));
scope.registerMethod(polyglot, "get_member", GetMemberMethodGen.makeFunction(language));
scope.registerMethod(polyglot, "get_members", GetMembersMethodGen.makeFunction(language));
+ scope.registerMethod(polyglot, "get_array_size", GetArraySizeMethodGen.makeFunction(language));
}
/** @return the atom constructor for polyglot */
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java
index 98b2a0dec841..089c2b10d108 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java
@@ -8,6 +8,7 @@
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
+import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import org.enso.interpreter.Language;
import org.enso.interpreter.runtime.Context;
@@ -121,6 +122,28 @@ public boolean isMemberInvocable(String member) {
return members != null && members.containsKey(member);
}
+ @ExportMessage
+ @ExplodeLoop
+ public boolean isMemberReadable(String member) {
+ for (int i = 0; i < constructor.getArity(); i++) {
+ if (member.equals(constructor.getFields()[i].getName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @ExportMessage
+ @ExplodeLoop
+ public Object readMember(String member) throws UnknownIdentifierException {
+ for (int i = 0; i < constructor.getArity(); i++) {
+ if (member.equals(constructor.getFields()[i].getName())) {
+ return fields[i];
+ }
+ }
+ throw UnknownIdentifierException.create(member);
+ }
+
@ExportMessage
static class InvokeMember {
diff --git a/engine/runtime/src/main/resources/Builtins.enso b/engine/runtime/src/main/resources/Builtins.enso
index 37329b84c045..252a01631701 100644
--- a/engine/runtime/src/main/resources/Builtins.enso
+++ b/engine/runtime/src/main/resources/Builtins.enso
@@ -380,17 +380,12 @@ type Polyglot
@Builtin_Type
type Polyglot
- ## PRIVATE
-
- Evaluates the provided code in the provided language to produce a
- value.
+ ## Reads the number of elements in a given polyglot array object.
Arguments:
- - language: The polyglot language in which to evaluate the code. The
- available languages depend on what is installed on your platform.
- - code: The code in language to evaluate.
- eval : Text -> Text -> Any
- eval language code = @Builtin_Method "Polyglot.eval"
+ - array: a polyglot array object, originating in any supported language.
+ get_array_size : Any -> Integer
+ get_array_size array = @Builtin_Method "Polyglot.get_array_size"
## Executes a polyglot function object (e.g. a lambda).
diff --git a/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstToIr.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstToIr.scala
index c6b81dc00afe..1d0771c5fb8b 100644
--- a/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstToIr.scala
+++ b/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstToIr.scala
@@ -9,6 +9,7 @@ import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Name.MethodReference
import org.enso.compiler.core.IR._
import org.enso.compiler.exception.UnhandledEntity
+import org.enso.interpreter.epb.EpbParser
import org.enso.syntax.text.AST
import org.enso.syntax.text.Shape.{
SegmentEscape,
@@ -144,9 +145,9 @@ object AstToIr {
case AstView.TypeDef(typeName, args, body) =>
val translatedBody = translateTypeBody(body)
val containsAtomDefOrInclude = translatedBody.exists {
- case _: IR.Module.Scope.Definition.Atom => true
- case _: IR.Name.Literal => true
- case _ => false
+ case _: IR.Module.Scope.Definition.Atom => true
+ case _: IR.Name.Literal => true
+ case _ => false
}
val hasArgs = args.nonEmpty
@@ -202,6 +203,28 @@ object AstToIr {
translateExpression(definition),
getIdentifiedLocation(inputAst)
)
+ case AstView.FunctionSugar(
+ AST.Ident.Var("foreign"),
+ header,
+ body
+ ) =>
+ translateForeignDefinition(header, body) match {
+ case Right((name, arguments, body)) =>
+ val typeName = Name.Here(None)
+ val methodRef =
+ Name.MethodReference(typeName, name, name.location)
+ Module.Scope.Definition.Method.Binding(
+ methodRef,
+ arguments,
+ body,
+ getIdentifiedLocation(inputAst)
+ )
+ case Left(reason) =>
+ IR.Error.Syntax(
+ inputAst,
+ IR.Error.Syntax.InvalidForeignDefinition(reason)
+ )
+ }
case AstView.FunctionSugar(name, args, body) =>
val typeName = Name.Here(None)
val methodName = buildName(name)
@@ -281,8 +304,27 @@ object AstToIr {
inputAst match {
case AST.Ident.Annotation.any(ann) =>
IR.Name.Annotation(ann.name, getIdentifiedLocation(ann))
- case AST.Ident.Cons.any(include) => translateIdent(include)
- case atom @ AstView.Atom(_, _) => translateModuleSymbol(atom)
+ case AST.Ident.Cons.any(include) => translateIdent(include)
+ case atom @ AstView.Atom(_, _) => translateModuleSymbol(atom)
+ case AstView.FunctionSugar(
+ AST.Ident.Var("foreign"),
+ header,
+ body
+ ) =>
+ translateForeignDefinition(header, body) match {
+ case Right((name, arguments, body)) =>
+ IR.Function.Binding(
+ name,
+ arguments,
+ body,
+ getIdentifiedLocation(inputAst)
+ )
+ case Left(reason) =>
+ IR.Error.Syntax(
+ inputAst,
+ IR.Error.Syntax.InvalidForeignDefinition(reason)
+ )
+ }
case fs @ AstView.FunctionSugar(_, _, _) => translateExpression(fs)
case AST.Comment.any(inputAST) => translateComment(inputAST)
case AstView.Binding(AST.App.Section.Right(opr, arg), body) =>
@@ -305,6 +347,44 @@ object AstToIr {
}
}
+ private def translateForeignDefinition(header: List[AST], body: AST): Either[
+ String,
+ (IR.Name, List[IR.DefinitionArgument], IR.Foreign.Definition)
+ ] = {
+ header match {
+ case AST.Ident.Var(lang) :: AST.Ident.Var.any(name) :: args =>
+ body.shape match {
+ case AST.Literal.Text.Block.Raw(lines, _, _) =>
+ val code = lines
+ .map(t =>
+ t.text.collect {
+ case AST.Literal.Text.Segment.Plain(str) => str
+ case AST.Literal.Text.Segment.RawEsc(code) => code.repr
+ }.mkString
+ )
+ .mkString("\n")
+ val methodName = buildName(name)
+ val arguments = args.map(translateArgumentDefinition(_))
+ val language = EpbParser.ForeignLanguage.getBySyntacticTag(lang)
+ if (language == null) {
+ Left(s"Language $lang is not a supported polyglot language.")
+ } else {
+ val foreign = IR.Foreign.Definition(
+ language,
+ code,
+ getIdentifiedLocation(body)
+ )
+ Right((methodName, arguments, foreign))
+ }
+ case _ =>
+ Left(
+ "The body of a foreign block must be an uninterpolated string block literal."
+ )
+ }
+ case _ => Left("The method name is not specified.")
+ }
+ }
+
/** Translates a method reference from [[AST]] into [[IR]].
*
* @param inputAst the method reference to translate
diff --git a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala
index 1da7192c8243..038678fbcadc 100644
--- a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala
+++ b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala
@@ -1,6 +1,9 @@
package org.enso.compiler.codegen
+import java.math.BigInteger
+
import com.oracle.truffle.api.Truffle
+import com.oracle.truffle.api.frame.FrameSlot
import com.oracle.truffle.api.source.{Source, SourceSection}
import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Module.Scope.Import
@@ -21,6 +24,7 @@ import org.enso.compiler.pass.resolve.{
Patterns,
UppercaseNames
}
+import org.enso.interpreter.epb.EpbParser
import org.enso.interpreter.node.callable.argument.ReadArgumentNode
import org.enso.interpreter.node.callable.function.{
BlockNode,
@@ -35,6 +39,7 @@ import org.enso.interpreter.node.callable.{
import org.enso.interpreter.node.controlflow.caseexpr._
import org.enso.interpreter.node.expression.atom.QualifiedAccessorNode
import org.enso.interpreter.node.expression.constant._
+import org.enso.interpreter.node.expression.foreign.ForeignMethodCallNode
import org.enso.interpreter.node.expression.literal.{
BigIntegerLiteralNode,
DecimalLiteralNode,
@@ -61,10 +66,13 @@ import org.enso.interpreter.runtime.callable.function.{
}
import org.enso.interpreter.runtime.data.text.Text
import org.enso.interpreter.runtime.error.DuplicateArgumentNameException
-import org.enso.interpreter.runtime.scope.{LocalScope, ModuleScope}
+import org.enso.interpreter.runtime.scope.{
+ FramePointer,
+ LocalScope,
+ ModuleScope
+}
import org.enso.interpreter.{Constants, Language}
-import java.math.BigInteger
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
@@ -1023,37 +1031,63 @@ class IrToTruffle(
val seenArgNames = mutable.Set[String]()
// Note [Rewriting Arguments]
- for ((unprocessedArg, idx) <- arguments.view.zipWithIndex) {
- val arg = argFactory.run(unprocessedArg, idx)
- argDefinitions(idx) = arg
+ val argSlots =
+ arguments.zipWithIndex.map { case (unprocessedArg, idx) =>
+ val arg = argFactory.run(unprocessedArg, idx)
+ argDefinitions(idx) = arg
- val occInfo = unprocessedArg
- .unsafeGetMetadata(
- AliasAnalysis,
- "No occurrence on an argument definition."
- )
- .unsafeAs[AliasAnalysis.Info.Occurrence]
+ val occInfo = unprocessedArg
+ .unsafeGetMetadata(
+ AliasAnalysis,
+ "No occurrence on an argument definition."
+ )
+ .unsafeAs[AliasAnalysis.Info.Occurrence]
- val slot = scope.createVarSlot(occInfo.id)
- val readArg =
- ReadArgumentNode.build(idx, arg.getDefaultValue.orElse(null))
- val assignArg = AssignmentNode.build(readArg, slot)
+ val slot = scope.createVarSlot(occInfo.id)
+ val readArg =
+ ReadArgumentNode.build(idx, arg.getDefaultValue.orElse(null))
+ val assignArg = AssignmentNode.build(readArg, slot)
- argExpressions.append(assignArg)
+ argExpressions.append(assignArg)
- val argName = arg.getName
+ val argName = arg.getName
- if (seenArgNames contains argName) {
- throw new DuplicateArgumentNameException(argName)
- } else seenArgNames.add(argName)
- }
+ if (seenArgNames contains argName) {
+ throw new DuplicateArgumentNameException(argName)
+ } else seenArgNames.add(argName)
+ slot
+ }
- val bodyExpr = this.run(body)
+ val bodyExpr = body match {
+ case IR.Foreign.Definition(lang, code, _, _, _) =>
+ buildForeignBody(
+ lang,
+ code,
+ arguments.map(_.name.name),
+ argSlots
+ )
+ case _ => this.run(body)
+ }
val fnBodyNode = BlockNode.build(argExpressions.toArray, bodyExpr)
(fnBodyNode, argDefinitions)
}
+ private def buildForeignBody(
+ language: EpbParser.ForeignLanguage,
+ code: String,
+ argumentNames: List[String],
+ argumentSlots: List[FrameSlot]
+ ): RuntimeExpression = {
+ val src = EpbParser.buildSource(language, code, scopeName)
+ val foreignCt = context.getEnvironment
+ .parseInternal(src, argumentNames: _*)
+ val argumentReaders = argumentSlots
+ .map(slot => ReadLocalVariableNode.build(new FramePointer(0, slot)))
+ .toArray[RuntimeExpression]
+ ForeignMethodCallNode.build(argumentReaders, foreignCt)
+ }
+
/** Generates code for an Enso function body.
*
* @param arguments the arguments to the function
diff --git a/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala b/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala
index 2ff316496a42..b60319a27d96 100644
--- a/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala
+++ b/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala
@@ -8,6 +8,7 @@ import org.enso.compiler.core.ir.MetadataStorage.MetadataPair
import org.enso.compiler.data.BindingsMap
import org.enso.compiler.exception.CompilerError
import org.enso.compiler.pass.IRPass
+import org.enso.interpreter.epb.EpbParser
import org.enso.syntax.text.{AST, Debug, Location}
import scala.annotation.unused
@@ -5113,7 +5114,7 @@ object IR {
* @param diagnostics compiler diagnostics for this node
*/
sealed case class Definition(
- lang: String,
+ lang: EpbParser.ForeignLanguage,
code: String,
override val location: Option[IdentifiedLocation],
override val passData: MetadataStorage = MetadataStorage(),
@@ -5133,7 +5134,7 @@ object IR {
* @return a copy of `this`, updated with the specified values
*/
def copy(
- lang: String = lang,
+ lang: EpbParser.ForeignLanguage = lang,
code: String = code,
location: Option[IdentifiedLocation] = location,
passData: MetadataStorage = passData,
@@ -5808,6 +5809,11 @@ object IR {
case object InvalidOperatorName extends Reason {
override def explanation: String = "Invalid operator name."
}
+
+ case class InvalidForeignDefinition(details: String) extends Reason {
+ override def explanation: String =
+ s"Invalid foreign definition. $details"
+ }
}
/** A representation of an invalid piece of IR.
diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/lint/UnusedBindings.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/lint/UnusedBindings.scala
index 2e45eaec7994..60a33d6b04db 100644
--- a/engine/runtime/src/main/scala/org/enso/compiler/pass/lint/UnusedBindings.scala
+++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/lint/UnusedBindings.scala
@@ -118,6 +118,8 @@ case object UnusedBindings extends IRPass {
@unused context: InlineContext
): IR.Function = {
function match {
+ case IR.Function.Lambda(_, _: IR.Foreign.Definition, _, _, _, _) =>
+ function
case lam @ IR.Function.Lambda(args, body, _, _, _, _) =>
lam.copy(
arguments = args.map(lintFunctionArgument(_, context)),
diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala
index 7cffd53a0412..e406867c1ee4 100644
--- a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala
+++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala
@@ -3145,7 +3145,7 @@ class RuntimeServerTest
contextId,
Seq(
Api.ExecutionResult.Diagnostic.error(
- "Unexpected type provided for argument `that` in Text.+",
+ "Type_Error Text 2",
None,
None,
None,
diff --git a/lib/scala/syntax/definition/src/main/scala/org/enso/syntax/text/ast/meta/Builtin.scala b/lib/scala/syntax/definition/src/main/scala/org/enso/syntax/text/ast/meta/Builtin.scala
index e04d97783f8b..318a3dc1f3c3 100644
--- a/lib/scala/syntax/definition/src/main/scala/org/enso/syntax/text/ast/meta/Builtin.scala
+++ b/lib/scala/syntax/definition/src/main/scala/org/enso/syntax/text/ast/meta/Builtin.scala
@@ -200,25 +200,6 @@ object Builtin {
}
}
- val foreign = Definition(
- Var("foreign") -> (Pattern.Cons() :: Pattern.Block())
- ) { ctx =>
- ctx.body match {
- case List(s1) =>
- s1.body.toStream match {
- case List(langAST, Shifted(_, AST.Block.any(bodyAST))) =>
- val indent = bodyAST.indent
- val lang = langAST.wrapped.show()
- val body = bodyAST.show()
- val bodyLines = body.split("\\r?\\n").toList.drop(1)
- val bodyLines2 = bodyLines.map(_.drop(indent))
- AST.Foreign(indent, lang, bodyLines2)
- case _ => internalError
- }
- case _ => internalError
- }
- }
-
val skip = Definition(
Var("skip") -> Pattern.Expr()
) { ctx =>
@@ -459,7 +440,6 @@ object Builtin {
qualifiedExport,
defn,
arrow,
- foreign,
docComment,
disableComment,
skip,
diff --git a/project/FixInstrumentsGeneration.scala b/project/FixInstrumentsGeneration.scala
index a28c42c08756..789e6a342caf 100644
--- a/project/FixInstrumentsGeneration.scala
+++ b/project/FixInstrumentsGeneration.scala
@@ -98,10 +98,16 @@ object FixInstrumentsGeneration {
): FragileFiles = {
val fragileSources =
(file(s"$root/src/main/java/") ** "*Instrument.java").get ++
- Seq(file(s"$root/src/main/java/org/enso/interpreter/Language.java"))
+ Seq(
+ file(s"$root/src/main/java/org/enso/interpreter/Language.java"),
+ file(s"$root/src/main/java/org/enso/interpreter/epb/EpbLanguage.java")
+ )
val fragileClassFiles =
(classFilesDirectory ** "*Instrument.class").get ++
- Seq(file(s"$classFilesDirectory/org/enso/interpreter/Language.class"))
+ Seq(
+ file(s"$classFilesDirectory/org/enso/interpreter/Language.class"),
+ file(s"$classFilesDirectory/org/enso/interpreter/epb/EpbLanguage.class")
+ )
FragileFiles(fragileSources, fragileClassFiles)
}
}
diff --git a/test/Tests/src/Main.enso b/test/Tests/src/Main.enso
index 4ba8efc5a511..e8ee2b875a29 100644
--- a/test/Tests/src/Main.enso
+++ b/test/Tests/src/Main.enso
@@ -8,6 +8,7 @@ import Tests.Semantic.Deep_Export.Spec as Deep_Export_Spec
import Tests.Semantic.Error_Spec
import Tests.Semantic.Import_Loop.Spec as Import_Loop_Spec
import Tests.Semantic.Java_Interop_Spec
+import Tests.Semantic.Js_Interop_Spec
import Tests.Semantic.Meta_Spec
import Tests.Semantic.Names_Spec
@@ -45,6 +46,7 @@ main = Test.Suite.runMain <|
Import_Loop_Spec.spec
Interval_Spec.spec
Java_Interop_Spec.spec
+ Js_Interop_Spec.spec
Json_Spec.spec
List_Spec.spec
Map_Spec.spec
diff --git a/test/Tests/src/Semantic/Js_Interop_Spec.enso b/test/Tests/src/Semantic/Js_Interop_Spec.enso
new file mode 100644
index 000000000000..5e3ae6d1354f
--- /dev/null
+++ b/test/Tests/src/Semantic/Js_Interop_Spec.enso
@@ -0,0 +1,62 @@
+from Base import all
+import Test
+
+foreign js my_method a b = """
+ return a + b;
+
+type My_Type
+ type My_Type a b
+
+ foreign js my_method = """
+ return this.a + this.b;
+
+ my_method_2 x = this.my_method * x
+
+ foreign js my_method_3 y = """
+ var r = this.my_method_2(y)
+ return r + 1;
+
+foreign js make_object = """
+ return {
+ x: 10,
+ y: false,
+ compare: function (guess) {
+ return this.x < guess;
+ }
+ };
+
+foreign js make_array = """
+ return [{ x: 10}, {x: 20}, {x: 30}];
+
+foreign js make_str str = """
+ return "foo " + str + " bar"
+
+spec = Test.group "Polyglot JS" <|
+ Test.specify "should allow declaring module-level methods in JS" <|
+ here.my_method 1 2 . should_equal 3
+
+ Test.specify "should allow mutual calling of instance-level methods" <|
+ My_Type 3 4 . my_method_3 5 . should_equal 36
+
+ Test.specify "should expose methods and fields of JS objects" <|
+ obj = here.make_object
+ obj.x . should_equal 10
+ obj.y . should_be_false
+ obj.compare 5 . should_be_false
+ obj.compare 11 . should_be_true
+
+ Test.specify "should expose array interfaces for JS arrays" <|
+ vec = Vector.Vector here.make_array
+ vec.map .x . should_equal [10, 20, 30]
+
+ Test.specify "should correctly marshall strings" <|
+ str = here.make_str "x" + " baz"
+ str.should_equal "foo x bar baz"
+
+ Test.specify "should make JS strings type pattern-matchable" <|
+ str = here.make_str "x"
+ t = case str of
+ Text -> True
+ _ -> False
+ t.should_be_true
+