mirror of
https://github.com/enso-org/enso.git
synced 2025-01-03 14:04:44 +03:00
EpbLanguage re-uses other TruffleContext support to run tests with assertions enabled (#7882)
This commit is contained in:
parent
927df167d7
commit
4b65e44ef3
20
build.sbt
20
build.sbt
@ -1178,11 +1178,6 @@ val truffleRunOpts = Seq(
|
||||
"-Dpolyglot.compiler.BackgroundCompilation=false"
|
||||
)
|
||||
|
||||
val truffleRunOptionsNoAssertSettings = Seq(
|
||||
fork := true,
|
||||
javaOptions ++= benchOnlyOptions
|
||||
)
|
||||
|
||||
val truffleRunOptionsSettings = Seq(
|
||||
fork := true,
|
||||
javaOptions ++= "-ea" +: benchOnlyOptions
|
||||
@ -1378,11 +1373,20 @@ lazy val instrumentationSettings = frgaalJavaCompilerSetting ++ Seq(
|
||||
lazy val `runtime-language-epb` =
|
||||
(project in file("engine/runtime-language-epb"))
|
||||
.settings(
|
||||
frgaalJavaCompilerSetting,
|
||||
inConfig(Compile)(truffleRunOptionsSettings),
|
||||
truffleDslSuppressWarnsSetting,
|
||||
instrumentationSettings
|
||||
commands += WithDebugCommand.withDebug,
|
||||
fork := true,
|
||||
Test / javaOptions ++= Seq(),
|
||||
instrumentationSettings,
|
||||
libraryDependencies ++= Seq(
|
||||
"junit" % "junit" % junitVersion % Test,
|
||||
"com.github.sbt" % "junit-interface" % junitIfVersion % Test,
|
||||
"org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % "provided",
|
||||
"org.graalvm.truffle" % "truffle-dsl-processor" % graalMavenPackagesVersion % "provided"
|
||||
)
|
||||
)
|
||||
.dependsOn(`polyglot-api`)
|
||||
|
||||
lazy val runtime = (project in file("engine/runtime"))
|
||||
.configs(Benchmark)
|
||||
@ -1730,7 +1734,7 @@ lazy val `runtime-with-polyglot` =
|
||||
.configs(Benchmark)
|
||||
.settings(
|
||||
frgaalJavaCompilerSetting,
|
||||
inConfig(Compile)(truffleRunOptionsNoAssertSettings),
|
||||
inConfig(Compile)(truffleRunOptionsSettings),
|
||||
inConfig(Benchmark)(Defaults.testSettings),
|
||||
commands += WithDebugCommand.withDebug,
|
||||
Benchmark / javacOptions --= Seq(
|
||||
|
@ -30,7 +30,7 @@ impl From<bool> for Boolean {
|
||||
}
|
||||
|
||||
ide_ci::define_env_var! {
|
||||
ENSO_JVM_OPTS, String;
|
||||
JAVA_OPTS, String;
|
||||
ENSO_BENCHMARK_TEST_DRY_RUN, Boolean;
|
||||
}
|
||||
|
||||
@ -88,7 +88,7 @@ impl BuiltEnso {
|
||||
.arg(test_path)
|
||||
// This flag enables assertions in the JVM. Some of our stdlib tests had in the past
|
||||
// failed on Graal/Truffle assertions, so we want to have them triggered.
|
||||
.set_env(ENSO_JVM_OPTS, &ide_ci::programs::java::Option::EnableAssertions.as_ref())?;
|
||||
.set_env(JAVA_OPTS, &ide_ci::programs::java::Option::EnableAssertions.as_ref())?;
|
||||
Ok(command)
|
||||
}
|
||||
|
||||
|
@ -1,47 +0,0 @@
|
||||
package org.enso.polyglot;
|
||||
|
||||
import com.oracle.truffle.api.source.Source;
|
||||
import java.util.Arrays;
|
||||
|
||||
/** Lists all the languages supported in polyglot eval. */
|
||||
public enum ForeignLanguage {
|
||||
JS("js", "js"),
|
||||
PY("python", "python"),
|
||||
R("R", "r");
|
||||
|
||||
public static final String ID = "epb";
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new source instance that can later be parsed by this class.
|
||||
*
|
||||
* @param foreignSource the foreign source to evaluate
|
||||
* @param name the name of the source
|
||||
* @return a source instance, parsable by the EPB language
|
||||
*/
|
||||
public Source buildSource(String foreignSource, String name) {
|
||||
return Source.newBuilder(ID, this + "#" + foreignSource, name).build();
|
||||
}
|
||||
}
|
@ -22,7 +22,6 @@ import org.enso.compiler.pass.analyse.{
|
||||
}
|
||||
import org.enso.compiler.pass.lint.UnusedBindings
|
||||
import org.enso.compiler.pass.optimise.LambdaConsolidate
|
||||
import org.enso.polyglot.ForeignLanguage
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
||||
@ -161,7 +160,7 @@ case object GenerateMethodBodies extends IRPass {
|
||||
case lam @ Function.Lambda(_, body, _, _, _, _)
|
||||
if findForeignDefinition(
|
||||
body,
|
||||
lang = Some(ForeignLanguage.JS)
|
||||
lang = Some("js")
|
||||
).isDefined =>
|
||||
val thisArgs = chainedFunctionArgs.collect {
|
||||
case (arg, idx) if arg.name.name == "this" =>
|
||||
@ -308,7 +307,7 @@ case object GenerateMethodBodies extends IRPass {
|
||||
@tailrec
|
||||
private def findForeignDefinition(
|
||||
body: Expression,
|
||||
lang: Option[ForeignLanguage]
|
||||
lang: Option[String]
|
||||
): Option[Foreign.Definition] = {
|
||||
body match {
|
||||
case foreignDef: Foreign.Definition =>
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.enso.interpreter.epb.node;
|
||||
package org.enso.interpreter.epb;
|
||||
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.GenerateUncached;
|
||||
@ -10,7 +10,7 @@ import com.oracle.truffle.api.strings.TruffleString;
|
||||
|
||||
@GenerateUncached
|
||||
@ReportPolymorphism
|
||||
public abstract class CoercePrimitiveNode extends Node {
|
||||
abstract class CoercePrimitiveNode extends Node {
|
||||
|
||||
/**
|
||||
* Create a new node responsible for coercing primitive values to Enso primitives at the polyglot
|
@ -1,19 +1,18 @@
|
||||
package org.enso.interpreter.epb;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
|
||||
import com.oracle.truffle.api.TruffleContext;
|
||||
import com.oracle.truffle.api.TruffleLanguage;
|
||||
import com.oracle.truffle.api.TruffleLogger;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.logging.Level;
|
||||
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 {
|
||||
final class EpbContext {
|
||||
|
||||
private static final TruffleLanguage.ContextReference<EpbContext> REFERENCE =
|
||||
TruffleLanguage.ContextReference.create(EpbLanguage.class);
|
||||
@ -21,18 +20,19 @@ 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;
|
||||
private @CompilationFinal TruffleContext innerContext;
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
private final TruffleLogger log;
|
||||
|
||||
/**
|
||||
* Creates a new instance of this context.
|
||||
*
|
||||
* @param env the current language environment.
|
||||
*/
|
||||
public EpbContext(TruffleLanguage.Env env) {
|
||||
EpbContext(TruffleLanguage.Env env) {
|
||||
this.env = env;
|
||||
isInner = env.getConfig().get(INNER_OPTION) != null;
|
||||
currentContext = new GuardedTruffleContext(env.getContext(), isInner);
|
||||
this.log = env.getLogger(EpbContext.class);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -45,56 +45,13 @@ public class EpbContext {
|
||||
if (!isInner) {
|
||||
if (innerContext == null) {
|
||||
innerContext =
|
||||
new GuardedTruffleContext(
|
||||
env.newInnerContextBuilder()
|
||||
.initializeCreatorContext(true)
|
||||
.inheritAllAccess(true)
|
||||
.config(INNER_OPTION, "yes")
|
||||
.build(),
|
||||
true);
|
||||
}
|
||||
initializeLanguages(env, innerContext, preInitializeLanguages);
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private static void initializeLanguages(
|
||||
TruffleLanguage.Env environment, GuardedTruffleContext innerContext, String langs) {
|
||||
if (langs == null || langs.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
var log = environment.getLogger(EpbContext.class);
|
||||
log.log(Level.FINE, "Initializing languages {0}", langs);
|
||||
var cdl = new CountDownLatch(1);
|
||||
var run =
|
||||
(Consumer<TruffleContext>)
|
||||
(context) -> {
|
||||
var lock = innerContext.enter(null);
|
||||
try {
|
||||
log.log(Level.FINEST, "Entering initialization thread");
|
||||
cdl.countDown();
|
||||
for (var l : langs.split(",")) {
|
||||
log.log(Level.FINEST, "Initializing language {0}", l);
|
||||
long then = System.currentTimeMillis();
|
||||
var res = context.initializeInternal(null, l);
|
||||
long took = System.currentTimeMillis() - then;
|
||||
log.log(
|
||||
Level.FINE,
|
||||
"Done initializing language {0} with {1} in {2} ms",
|
||||
new Object[] {l, res, took});
|
||||
}
|
||||
} finally {
|
||||
innerContext.leave(null, lock);
|
||||
}
|
||||
};
|
||||
var init = innerContext.createThread(environment, run);
|
||||
log.log(Level.FINEST, "Starting initialization thread");
|
||||
init.start();
|
||||
try {
|
||||
cdl.await();
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
log.log(Level.FINEST, "Initializing on background");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,30 +63,16 @@ public class EpbContext {
|
||||
return REFERENCE.get(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
public TruffleContext getInnerContext() {
|
||||
return innerContext;
|
||||
}
|
||||
|
||||
public void log(Level level, String msg, Object... args) {
|
||||
this.log.log(level, msg, args);
|
||||
}
|
||||
}
|
||||
|
@ -3,41 +3,17 @@ package org.enso.interpreter.epb;
|
||||
import com.oracle.truffle.api.CallTarget;
|
||||
import com.oracle.truffle.api.TruffleLanguage;
|
||||
import java.util.function.Consumer;
|
||||
import org.enso.interpreter.epb.node.ContextRewrapNode;
|
||||
import org.enso.interpreter.epb.node.ForeignEvalNode;
|
||||
import org.enso.polyglot.ForeignLanguage;
|
||||
|
||||
/**
|
||||
* An internal language that serves as a bridge between Enso and other supported languages.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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.
|
||||
*/
|
||||
/** An internal language that serves as a bridge between Enso and other supported languages. */
|
||||
@TruffleLanguage.Registration(
|
||||
id = ForeignLanguage.ID,
|
||||
id = "epb",
|
||||
name = "Enso Polyglot Bridge",
|
||||
characterMimeTypes = {EpbLanguage.MIME},
|
||||
internal = true,
|
||||
defaultMimeType = EpbLanguage.MIME,
|
||||
contextPolicy = TruffleLanguage.ContextPolicy.SHARED,
|
||||
services = Consumer.class)
|
||||
public class EpbLanguage extends TruffleLanguage<EpbContext> {
|
||||
public final class EpbLanguage extends TruffleLanguage<EpbContext> {
|
||||
public static final String MIME = "application/epb";
|
||||
|
||||
@Override
|
||||
@ -55,9 +31,8 @@ public class EpbLanguage extends TruffleLanguage<EpbContext> {
|
||||
|
||||
@Override
|
||||
protected CallTarget parse(ParsingRequest request) {
|
||||
EpbParser.Result code = EpbParser.parse(request.getSource());
|
||||
ForeignEvalNode foreignEvalNode = ForeignEvalNode.build(this, code, request.getArgumentNames());
|
||||
return foreignEvalNode.getCallTarget();
|
||||
var node = ForeignEvalNode.parse(this, request.getSource(), request.getArgumentNames());
|
||||
return node.getCallTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,40 +0,0 @@
|
||||
package org.enso.interpreter.epb;
|
||||
|
||||
import com.oracle.truffle.api.source.Source;
|
||||
import org.enso.polyglot.ForeignLanguage;
|
||||
|
||||
/** A class containing helpers for creating and parsing EPB code */
|
||||
public class EpbParser {
|
||||
/** 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("#", 2);
|
||||
return new Result(ForeignLanguage.valueOf(langAndCode[0]), langAndCode[1]);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package org.enso.interpreter.epb;
|
||||
|
||||
final class ExceptionForeignNode extends ForeignFunctionCallNode {
|
||||
private final ForeignParsingException ex;
|
||||
|
||||
ExceptionForeignNode(ForeignParsingException ex) {
|
||||
this.ex = ex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(Object[] arguments) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
package org.enso.interpreter.epb;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.oracle.truffle.api.CallTarget;
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.frame.FrameDescriptor;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.interop.InteropException;
|
||||
import com.oracle.truffle.api.nodes.RootNode;
|
||||
import com.oracle.truffle.api.source.Source;
|
||||
|
||||
final class ForeignEvalNode extends RootNode {
|
||||
private final Source langAndCode;
|
||||
private @Child ForeignFunctionCallNode foreign;
|
||||
private final String[] argNames;
|
||||
|
||||
private ForeignEvalNode(EpbLanguage language, Source langAndCode, List<String> arguments) {
|
||||
super(language, new FrameDescriptor());
|
||||
this.langAndCode = langAndCode;
|
||||
this.argNames = arguments.toArray(new String[0]);
|
||||
}
|
||||
|
||||
static ForeignEvalNode parse(EpbLanguage epb, Source langAndCode, List<String> args) {
|
||||
var node = new ForeignEvalNode(epb, langAndCode, args);
|
||||
return node;
|
||||
}
|
||||
|
||||
private String truffleId(Source langAndCode) {
|
||||
var seq = langAndCode.getCharacters();
|
||||
return seq.subSequence(0, splitAt(seq)).toString().toLowerCase();
|
||||
}
|
||||
|
||||
private int splitAt(CharSequence seq) {
|
||||
var at = 0;
|
||||
while (at < seq.length()) {
|
||||
if (seq.charAt(at) == '#') {
|
||||
return at;
|
||||
}
|
||||
at++;
|
||||
}
|
||||
throw new ForeignParsingException("No # found", this);
|
||||
}
|
||||
|
||||
private String foreignSource(Source langAndCode) {
|
||||
var seq = langAndCode.getCharacters();
|
||||
return seq.toString().substring(splitAt(seq) + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(VirtualFrame frame) {
|
||||
if (foreign == null) {
|
||||
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||
var id = truffleId(langAndCode);
|
||||
var context = EpbContext.get(this);
|
||||
var installedLanguages = context.getEnv().getInternalLanguages();
|
||||
var node = switch (installedLanguages.containsKey(id) ? 1 : 0) {
|
||||
case 0 -> {
|
||||
var ex = new ForeignParsingException(id, installedLanguages.keySet(), this);
|
||||
yield new ExceptionForeignNode(ex);
|
||||
}
|
||||
default -> {
|
||||
context.log(Level.FINE, "Parsing foreign script {1} - language {0}", id, langAndCode.getName());
|
||||
yield switch (id) {
|
||||
case "js" -> parseJs();
|
||||
case "python" -> parseGeneric("python", PyForeignNode::new);
|
||||
default -> parseGeneric(id, GenericForeignNode::new);
|
||||
};
|
||||
}
|
||||
};
|
||||
foreign = insert(node);
|
||||
}
|
||||
try {
|
||||
var toRet = foreign.execute(frame.getArguments());
|
||||
return toRet;
|
||||
} catch (InteropException ex) {
|
||||
throw new ForeignParsingException(ex.getMessage(), this);
|
||||
}
|
||||
}
|
||||
|
||||
private ForeignFunctionCallNode parseJs() {
|
||||
var context = EpbContext.get(this);
|
||||
var inner = context.getInnerContext();
|
||||
var code = foreignSource(langAndCode);
|
||||
var args = Arrays.stream(argNames).skip(1).collect(Collectors.joining(","));
|
||||
var wrappedSrc
|
||||
= "var poly_enso_eval=function("
|
||||
+ args
|
||||
+ "){\n"
|
||||
+ code
|
||||
+ "\n};poly_enso_eval";
|
||||
Source source = Source.newBuilder("js", wrappedSrc, "").build();
|
||||
var fn = inner.evalPublic(this, source);
|
||||
return JsForeignNode.build(fn);
|
||||
}
|
||||
|
||||
private ForeignFunctionCallNode parseGeneric(String language, Function<CallTarget,ForeignFunctionCallNode> nodeFactory) {
|
||||
var ctx = EpbContext.get(this);
|
||||
Source source = Source.newBuilder(language, foreignSource(langAndCode), langAndCode.getName()).build();
|
||||
CallTarget ct = ctx.getEnv().parsePublic(source, argNames);
|
||||
return nodeFactory.apply(ct);
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
package org.enso.interpreter.epb.node;
|
||||
package org.enso.interpreter.epb;
|
||||
|
||||
import com.oracle.truffle.api.interop.InteropException;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
|
||||
/** An interface for nodes responsible for calling into foreign languages. */
|
||||
public abstract class ForeignFunctionCallNode extends Node {
|
||||
abstract class ForeignFunctionCallNode extends Node {
|
||||
/**
|
||||
* Executes the foreign call.
|
||||
*
|
@ -1,4 +1,4 @@
|
||||
package org.enso.interpreter.epb.runtime;
|
||||
package org.enso.interpreter.epb;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.exception.AbstractTruffleException;
|
||||
@ -13,7 +13,7 @@ import java.util.Set;
|
||||
* language is not installed, or enabled, in the Truffle engine.
|
||||
*/
|
||||
@ExportLibrary(InteropLibrary.class)
|
||||
public class ForeignParsingException extends AbstractTruffleException {
|
||||
class ForeignParsingException extends AbstractTruffleException {
|
||||
private final String message;
|
||||
|
||||
/**
|
@ -0,0 +1,22 @@
|
||||
package org.enso.interpreter.epb;
|
||||
|
||||
import com.oracle.truffle.api.CallTarget;
|
||||
import com.oracle.truffle.api.interop.InteropException;
|
||||
import com.oracle.truffle.api.nodes.DirectCallNode;
|
||||
|
||||
class GenericForeignNode extends ForeignFunctionCallNode {
|
||||
private @Child DirectCallNode callNode;
|
||||
private @Child CoercePrimitiveNode coerceNode;
|
||||
|
||||
GenericForeignNode(CallTarget ct) {
|
||||
callNode = DirectCallNode.create(ct);
|
||||
coerceNode = CoercePrimitiveNode.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(Object[] arguments) throws InteropException {
|
||||
var rawResult = callNode.call(arguments);
|
||||
var res = coerceNode.execute(rawResult);
|
||||
return res;
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package org.enso.interpreter.epb;
|
||||
|
||||
import com.oracle.truffle.api.dsl.NodeField;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.interop.InteropException;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
|
||||
import com.oracle.truffle.api.interop.TruffleObject;
|
||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||
import com.oracle.truffle.api.library.CachedLibrary;
|
||||
import com.oracle.truffle.api.library.ExportLibrary;
|
||||
import com.oracle.truffle.api.library.ExportMessage;
|
||||
import java.util.Arrays;
|
||||
|
||||
/** A node responsible for performing foreign JS calls. */
|
||||
@NodeField(name = "foreignFunction", type = Object.class)
|
||||
abstract class JsForeignNode extends ForeignFunctionCallNode {
|
||||
|
||||
private @Child CoercePrimitiveNode coercePrimitiveNode = CoercePrimitiveNode.build();
|
||||
|
||||
abstract Object getForeignFunction();
|
||||
|
||||
/**
|
||||
* Creates a new instance of this node.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
static JsForeignNode build(Object jsFunction) {
|
||||
return JsForeignNodeGen.create(jsFunction);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
Object doExecute(Object[] arguments, @CachedLibrary("foreignFunction") InteropLibrary iop)
|
||||
throws InteropException {
|
||||
var args = new ArgumentsArray(arguments);
|
||||
var self = arguments[0];
|
||||
var raw = iop.invokeMember(getForeignFunction(), "apply", self, args);
|
||||
return coercePrimitiveNode.execute(raw);
|
||||
}
|
||||
|
||||
@ExportLibrary(InteropLibrary.class)
|
||||
static final class ArgumentsArray implements TruffleObject {
|
||||
private static final int OFFSET = 1;
|
||||
private final Object[] items;
|
||||
|
||||
public ArgumentsArray(Object... items) {
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
public boolean hasArrayElements() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
public Object readArrayElement(long index) throws InvalidArrayIndexException {
|
||||
if (index >= getArraySize() || index < 0) {
|
||||
throw InvalidArrayIndexException.create(index);
|
||||
}
|
||||
return items[OFFSET + (int) index];
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
long getArraySize() {
|
||||
return items.length - OFFSET;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean isArrayElementReadable(long index) {
|
||||
return index < getArraySize() && index >= 0;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
void writeArrayElement(long index, Object value) throws UnsupportedMessageException {
|
||||
throw UnsupportedMessageException.create();
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean isArrayElementModifiable(long index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean isArrayElementInsertable(long index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Arrays.toString(items);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,11 @@
|
||||
package org.enso.interpreter.epb.node;
|
||||
package org.enso.interpreter.epb;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
|
||||
import org.enso.interpreter.epb.EpbContext;
|
||||
|
||||
import com.oracle.truffle.api.CallTarget;
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.dsl.NodeField;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.interop.ArityException;
|
||||
import com.oracle.truffle.api.interop.InteropException;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
@ -21,10 +18,7 @@ import com.oracle.truffle.api.library.ExportLibrary;
|
||||
import com.oracle.truffle.api.library.ExportMessage;
|
||||
import com.oracle.truffle.api.source.Source;
|
||||
|
||||
@NodeField(name = "foreignFunction", type = Object.class)
|
||||
public abstract class PyForeignNode extends ForeignFunctionCallNode {
|
||||
@Child
|
||||
private CoercePrimitiveNode coercePrimitiveNode = CoercePrimitiveNode.build();
|
||||
final class PyForeignNode extends GenericForeignNode {
|
||||
@CompilerDirectives.CompilationFinal
|
||||
private Object fnPythonDate;
|
||||
@Child
|
||||
@ -39,10 +33,72 @@ public abstract class PyForeignNode extends ForeignFunctionCallNode {
|
||||
private InteropLibrary nodePythonZone;
|
||||
@CompilerDirectives.CompilationFinal
|
||||
private Object fnPythonCombine;
|
||||
@CompilerDirectives.CompilationFinal
|
||||
private Object none;
|
||||
@Child
|
||||
private InteropLibrary nodePythonCombine;
|
||||
@Child
|
||||
private InteropLibrary iop = InteropLibrary.getFactory().createDispatched(3);
|
||||
|
||||
abstract Object getForeignFunction();
|
||||
PyForeignNode(CallTarget ct) {
|
||||
super(ct);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(Object[] arguments) throws InteropException {
|
||||
// initialize venv by importing site
|
||||
none();
|
||||
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
var javaTime = iop.isTime(arguments[i]) ? iop.asTime(arguments[i]) : null;
|
||||
var time = javaTime != null ? wrapPythonTime(javaTime) : null;
|
||||
var javaDate = iop.isDate(arguments[i]) ? iop.asDate(arguments[i]) : null;
|
||||
var date = javaDate != null ? wrapPythonDate(javaDate) : null;
|
||||
var zone = iop.isTimeZone(arguments[i]) ? wrapPythonZone(iop.asTimeZone(arguments[i]), javaTime, javaDate) : null;
|
||||
if (date != null && time != null) {
|
||||
arguments[i] = combinePythonDateTimeZone(date, time, zone);
|
||||
} else if (date != null) {
|
||||
arguments[i] = date;
|
||||
} else if (time != null) {
|
||||
arguments[i] = time;
|
||||
} else if (zone != null) {
|
||||
arguments[i] = zone;
|
||||
}
|
||||
// Enso's Text type should be converted to TruffleString before sent to python, because
|
||||
// python does not tend to use InteropLibrary, so any Text object would be treated as
|
||||
// a foreign object, rather than 'str' type.
|
||||
if (iop.isString(arguments[i])) {
|
||||
arguments[i] = iop.asTruffleString(arguments[i]);
|
||||
}
|
||||
}
|
||||
var res = super.execute(arguments);
|
||||
if (iop.hasMetaObject(res)) {
|
||||
var meta = iop.getMetaObject(res);
|
||||
var name = iop.getMetaQualifiedName(meta);
|
||||
if ("module".equals(name)) {
|
||||
return none();
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private Object none() throws UnsupportedTypeException, ArityException, UnsupportedMessageException {
|
||||
if (none == null) {
|
||||
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||
var ctx = EpbContext.get(this);
|
||||
var src = Source.newBuilder("python", """
|
||||
import site
|
||||
def nothing():
|
||||
return None
|
||||
nothing
|
||||
""", "nothing.py").build();
|
||||
var nothingFn = ctx.getEnv().parsePublic(src).call();
|
||||
assert InteropLibrary.getUncached().isExecutable(nothingFn);
|
||||
none = InteropLibrary.getUncached().execute(nothingFn);
|
||||
assert InteropLibrary.getUncached().isNull(none);
|
||||
}
|
||||
return none;
|
||||
}
|
||||
|
||||
private Object wrapPythonDate(LocalDate date) throws UnsupportedTypeException, ArityException, UnsupportedMessageException {
|
||||
if (nodePythonDate == null) {
|
||||
@ -129,37 +185,6 @@ public abstract class PyForeignNode extends ForeignFunctionCallNode {
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization
|
||||
public Object doExecute(
|
||||
Object[] arguments,
|
||||
@CachedLibrary("foreignFunction") InteropLibrary interopLibrary,
|
||||
@CachedLibrary(limit = "3") InteropLibrary iop
|
||||
) throws InteropException {
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
var javaTime = iop.isTime(arguments[i]) ? iop.asTime(arguments[i]) : null;
|
||||
var time = javaTime != null ? wrapPythonTime(javaTime) : null;
|
||||
var javaDate = iop.isDate(arguments[i]) ? iop.asDate(arguments[i]) : null;
|
||||
var date = javaDate != null ? wrapPythonDate(javaDate) : null;
|
||||
var zone = iop.isTimeZone(arguments[i]) ? wrapPythonZone(iop.asTimeZone(arguments[i]), javaTime, javaDate) : null;
|
||||
if (date != null && time != null) {
|
||||
arguments[i] = combinePythonDateTimeZone(date, time, zone);
|
||||
} else if (date != null) {
|
||||
arguments[i] = date;
|
||||
} else if (time != null) {
|
||||
arguments[i] = time;
|
||||
} else if (zone != null) {
|
||||
arguments[i] = zone;
|
||||
}
|
||||
// Enso's Text type should be converted to TruffleString before sent to python, because
|
||||
// python does not tend to use InteropLibrary, so any Text object would be treated as
|
||||
// a foreign object, rather than 'str' type.
|
||||
if (iop.isString(arguments[i])) {
|
||||
arguments[i] = iop.asTruffleString(arguments[i]);
|
||||
}
|
||||
}
|
||||
return coercePrimitiveNode.execute(interopLibrary.execute(getForeignFunction(), arguments));
|
||||
}
|
||||
|
||||
@ExportLibrary(InteropLibrary.class)
|
||||
static final class ZoneWrapper implements TruffleObject {
|
||||
|
@ -1,55 +0,0 @@
|
||||
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.exception.AbstractTruffleException;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import org.enso.interpreter.epb.runtime.GuardedTruffleContext;
|
||||
import org.enso.interpreter.epb.runtime.PolyglotExceptionProxy;
|
||||
|
||||
@GenerateUncached
|
||||
@ReportPolymorphism
|
||||
public abstract class ContextRewrapExceptionNode extends Node {
|
||||
|
||||
/**
|
||||
* Create a new context rewrap exception node.
|
||||
*
|
||||
* @return a new context rewrap exception node
|
||||
*/
|
||||
public static ContextRewrapExceptionNode build() {
|
||||
return ContextRewrapExceptionNodeGen.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 AbstractTruffleException execute(
|
||||
AbstractTruffleException value, GuardedTruffleContext origin, GuardedTruffleContext target);
|
||||
|
||||
@Specialization(guards = "proxy.getOrigin() == target")
|
||||
AbstractTruffleException doUnwrapProxy(
|
||||
PolyglotExceptionProxy proxy, GuardedTruffleContext origin, GuardedTruffleContext target) {
|
||||
return proxy.getOriginal();
|
||||
}
|
||||
|
||||
@Specialization(guards = "proxy.getTarget() == target")
|
||||
AbstractTruffleException doAlreadyProxied(
|
||||
PolyglotExceptionProxy proxy, GuardedTruffleContext origin, GuardedTruffleContext target) {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
@Fallback
|
||||
AbstractTruffleException doWrapProxy(
|
||||
AbstractTruffleException o, GuardedTruffleContext origin, GuardedTruffleContext target) {
|
||||
return new PolyglotExceptionProxy(o, origin, target);
|
||||
}
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
package org.enso.interpreter.epb.node;
|
||||
|
||||
import com.oracle.truffle.api.dsl.Cached.Exclusive;
|
||||
import com.oracle.truffle.api.dsl.Cached.Shared;
|
||||
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.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||
import com.oracle.truffle.api.library.CachedLibrary;
|
||||
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 {
|
||||
|
||||
/**
|
||||
* Create a new context rewrap node.
|
||||
*
|
||||
* @return a new context rewrap node.
|
||||
*/
|
||||
static ContextRewrapNode build() {
|
||||
return ContextRewrapNodeGen.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
@Specialization(guards = "bools.isBoolean(b)")
|
||||
boolean doWrappedBoolean(
|
||||
Object b,
|
||||
GuardedTruffleContext origin,
|
||||
GuardedTruffleContext target,
|
||||
@Exclusive @CachedLibrary(limit = "5") InteropLibrary bools) {
|
||||
try {
|
||||
return bools.asBoolean(b);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException("Impossible, `b` is checked to be a boolean");
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization(guards = {"numbers.isNumber(l)", "numbers.fitsInLong(l)"})
|
||||
long doWrappedLong(
|
||||
Object l,
|
||||
GuardedTruffleContext origin,
|
||||
GuardedTruffleContext target,
|
||||
@Shared("interop") @CachedLibrary(limit = "5") InteropLibrary numbers) {
|
||||
try {
|
||||
return numbers.asLong(l);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException("Impossible, `l` is checked to be a long");
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization(
|
||||
guards = {"numbers.isNumber(d)", "!numbers.fitsInLong(d)", "numbers.fitsInDouble(d)"})
|
||||
double doWrappedDouble(
|
||||
Object d,
|
||||
GuardedTruffleContext origin,
|
||||
GuardedTruffleContext target,
|
||||
@Shared("interop") @CachedLibrary(limit = "5") InteropLibrary numbers) {
|
||||
try {
|
||||
return numbers.asDouble(d);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException("Impossible, `l` is checked to be a double");
|
||||
}
|
||||
}
|
||||
|
||||
@Fallback
|
||||
Object doWrapProxy(Object o, GuardedTruffleContext origin, GuardedTruffleContext target) {
|
||||
return new PolyglotProxy(o, origin, target);
|
||||
}
|
||||
}
|
@ -1,191 +0,0 @@
|
||||
package org.enso.interpreter.epb.node;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.enso.interpreter.epb.EpbContext;
|
||||
import org.enso.interpreter.epb.EpbLanguage;
|
||||
import org.enso.interpreter.epb.EpbParser;
|
||||
import org.enso.interpreter.epb.runtime.ForeignParsingException;
|
||||
import org.enso.interpreter.epb.runtime.GuardedTruffleContext;
|
||||
|
||||
import com.oracle.truffle.api.CallTarget;
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
|
||||
import com.oracle.truffle.api.exception.AbstractTruffleException;
|
||||
import com.oracle.truffle.api.frame.FrameDescriptor;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.interop.InteropException;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.nodes.RootNode;
|
||||
import com.oracle.truffle.api.source.Source;
|
||||
|
||||
public class ForeignEvalNode extends RootNode {
|
||||
private final EpbParser.Result code;
|
||||
private @Child ForeignFunctionCallNode foreign;
|
||||
private @Child ContextRewrapNode rewrapNode = ContextRewrapNode.build();
|
||||
|
||||
private @Child ContextRewrapExceptionNode rewrapExceptionNode =
|
||||
ContextRewrapExceptionNode.build();
|
||||
private @CompilationFinal ForeignParsingException parseException;
|
||||
private final String[] argNames;
|
||||
|
||||
/**
|
||||
* 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<String> arguments) {
|
||||
return new ForeignEvalNode(language, code, arguments);
|
||||
}
|
||||
|
||||
ForeignEvalNode(EpbLanguage language, EpbParser.Result code, List<String> arguments) {
|
||||
super(language, new FrameDescriptor());
|
||||
this.code = code;
|
||||
argNames = arguments.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public Object execute(VirtualFrame frame) {
|
||||
ensureParsed();
|
||||
if (foreign != null) {
|
||||
try {
|
||||
return foreign.execute(frame.getArguments());
|
||||
} catch (InteropException ex) {
|
||||
throw new ForeignParsingException(ex.getMessage(), this);
|
||||
}
|
||||
} else {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw parseException;
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureParsed() {
|
||||
if (foreign == null && parseException == null) {
|
||||
lockAndParse();
|
||||
}
|
||||
}
|
||||
|
||||
@CompilerDirectives.TruffleBoundary
|
||||
private void lockAndParse() throws IllegalStateException {
|
||||
getLock().lock();
|
||||
try {
|
||||
if (foreign == null) {
|
||||
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||
var foreignLang = code.getLanguage();
|
||||
String truffleLangId = foreignLang.getTruffleId();
|
||||
var context = EpbContext.get(this);
|
||||
var installedLanguages = context.getEnv().getInternalLanguages();
|
||||
if (!installedLanguages.containsKey(truffleLangId)) {
|
||||
this.parseException =
|
||||
new ForeignParsingException(truffleLangId, installedLanguages.keySet(), this);
|
||||
} else {
|
||||
switch (foreignLang) {
|
||||
case JS:
|
||||
parseJs();
|
||||
break;
|
||||
case PY:
|
||||
parsePy();
|
||||
break;
|
||||
case R:
|
||||
parseR();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported language resulted from EPB parsing");
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
getLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void parseJs() {
|
||||
EpbContext context = EpbContext.get(this);
|
||||
GuardedTruffleContext outer = context.getCurrentContext();
|
||||
GuardedTruffleContext inner = context.getInnerContext();
|
||||
Object p = inner.enter(this);
|
||||
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();
|
||||
|
||||
// After calling inner.enter, operating in a different, isolated truffle instance so need to
|
||||
// call one with the correct semantics.
|
||||
CallTarget ct = EpbContext.get(this).getEnv().parsePublic(source);
|
||||
Object fn = rewrapNode.execute(ct.call(), inner, outer);
|
||||
foreign = insert(JsForeignNode.build(fn, argNames.length));
|
||||
} catch (Throwable e) {
|
||||
if (InteropLibrary.getUncached().isException(e)) {
|
||||
throw rewrapExceptionNode.execute((AbstractTruffleException) e, inner, outer);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
inner.leave(this, p);
|
||||
}
|
||||
}
|
||||
|
||||
private void parsePy() {
|
||||
try {
|
||||
String args = Arrays.stream(argNames).collect(Collectors.joining(","));
|
||||
String head = """
|
||||
import site
|
||||
import polyglot
|
||||
@polyglot.export_value
|
||||
def polyglot_enso_python_eval("""
|
||||
+ args
|
||||
+ "):\n";
|
||||
String indentLines =
|
||||
code.getForeignSource().lines().map(l -> " " + l).collect(Collectors.joining("\n"));
|
||||
Source source =
|
||||
Source.newBuilder(code.getLanguage().getTruffleId(), head + indentLines, "").build();
|
||||
EpbContext context = EpbContext.get(this);
|
||||
CallTarget ct = context.getEnv().parsePublic(source);
|
||||
ct.call();
|
||||
Object fn = context.getEnv().importSymbol("polyglot_enso_python_eval");
|
||||
foreign = insert(PyForeignNodeGen.create(fn));
|
||||
} catch (Throwable e) {
|
||||
if (InteropLibrary.getUncached().isException(e)) {
|
||||
throw (AbstractTruffleException) e;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseR() {
|
||||
EpbContext context = EpbContext.get(this);
|
||||
GuardedTruffleContext outer = context.getCurrentContext();
|
||||
GuardedTruffleContext inner = context.getInnerContext();
|
||||
Object p = inner.enter(this);
|
||||
try {
|
||||
String args = String.join(",", argNames);
|
||||
String wrappedSrc = "function(" + args + "){\n" + code.getForeignSource() + "\n}";
|
||||
Source source = Source.newBuilder(code.getLanguage().getTruffleId(), wrappedSrc, "").build();
|
||||
|
||||
// After calling inner.enter, operating in a different, isolated truffle instance so need to
|
||||
// call one with the correct semantics.
|
||||
CallTarget ct = EpbContext.get(this).getEnv().parsePublic(source);
|
||||
Object fn = rewrapNode.execute(ct.call(), inner, outer);
|
||||
foreign = insert(RForeignNodeGen.create(fn));
|
||||
} catch (Throwable e) {
|
||||
if (InteropLibrary.getUncached().isException(e)) {
|
||||
throw rewrapExceptionNode.execute((AbstractTruffleException) e, inner, outer);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
inner.leave(this, p);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
package org.enso.interpreter.epb.node;
|
||||
|
||||
import com.oracle.truffle.api.dsl.NodeField;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.interop.InteropException;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.library.CachedLibrary;
|
||||
|
||||
/** A node responsible for performing foreign JS calls. */
|
||||
@NodeField(name = "foreignFunction", type = Object.class)
|
||||
@NodeField(name = "arity", type = int.class)
|
||||
public abstract class JsForeignNode extends ForeignFunctionCallNode {
|
||||
|
||||
private @Child CoercePrimitiveNode coercePrimitiveNode = CoercePrimitiveNode.build();
|
||||
|
||||
abstract Object getForeignFunction();
|
||||
|
||||
abstract int getArity();
|
||||
|
||||
/**
|
||||
* 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(Object jsFunction, int argumentsCount) {
|
||||
return JsForeignNodeGen.create(jsFunction, argumentsCount);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
Object doExecute(
|
||||
Object[] arguments, @CachedLibrary("foreignFunction") InteropLibrary interopLibrary)
|
||||
throws InteropException {
|
||||
int newLength = getArity() - 1;
|
||||
Object[] positionalArgs = new Object[newLength];
|
||||
System.arraycopy(arguments, 1, positionalArgs, 0, newLength);
|
||||
return coercePrimitiveNode.execute(
|
||||
interopLibrary.invokeMember(
|
||||
getForeignFunction(), "apply", arguments[0], new ReadOnlyArray(positionalArgs)));
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package org.enso.interpreter.epb.node;
|
||||
|
||||
import com.oracle.truffle.api.dsl.NodeField;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.interop.InteropException;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.library.CachedLibrary;
|
||||
|
||||
@NodeField(name = "foreignFunction", type = Object.class)
|
||||
public abstract class RForeignNode extends ForeignFunctionCallNode {
|
||||
|
||||
private @Child CoercePrimitiveNode coercePrimitiveNode = CoercePrimitiveNode.build();
|
||||
|
||||
abstract Object getForeignFunction();
|
||||
|
||||
@Specialization
|
||||
public Object doExecute(
|
||||
Object[] arguments, @CachedLibrary("foreignFunction") InteropLibrary interopLibrary)
|
||||
throws InteropException {
|
||||
return coercePrimitiveNode.execute(interopLibrary.execute(getForeignFunction(), arguments));
|
||||
}
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
package org.enso.interpreter.epb.node;
|
||||
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
|
||||
import com.oracle.truffle.api.interop.TruffleObject;
|
||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||
import com.oracle.truffle.api.library.ExportLibrary;
|
||||
import com.oracle.truffle.api.library.ExportMessage;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* A primitive boxed array type to be used only in EPB.
|
||||
*
|
||||
* <p>{@link ReadOnlyArray} is essentially a stripped-down, read-only, version of {@link
|
||||
* org.enso.interpreter.runtime.data.vector.Array}, used for passing arguments. The latter cannot be
|
||||
* used in EPB because EPB is a dependency of runtime.
|
||||
*/
|
||||
@ExportLibrary(InteropLibrary.class)
|
||||
public final class ReadOnlyArray implements TruffleObject {
|
||||
|
||||
private final Object[] items;
|
||||
|
||||
/**
|
||||
* Creates a new array
|
||||
*
|
||||
* @param items the element values
|
||||
*/
|
||||
public ReadOnlyArray(Object... items) {
|
||||
this.items = items;
|
||||
}
|
||||
/**
|
||||
* Marks the object as array-like for Polyglot APIs.
|
||||
*
|
||||
* @return {@code true}
|
||||
*/
|
||||
@ExportMessage
|
||||
public boolean hasArrayElements() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles reading an element by index through the polyglot API.
|
||||
*
|
||||
* @param index the index to read
|
||||
* @return the element value at the provided index
|
||||
* @throws InvalidArrayIndexException when the index is out of bounds.
|
||||
*/
|
||||
@ExportMessage
|
||||
public Object readArrayElement(long index) throws InvalidArrayIndexException {
|
||||
if (index >= items.length || index < 0) {
|
||||
throw InvalidArrayIndexException.create(index);
|
||||
}
|
||||
return items[(int) index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes the size of this collection through the polyglot API.
|
||||
*
|
||||
* @return the size of this array
|
||||
*/
|
||||
@ExportMessage
|
||||
long getArraySize() {
|
||||
return items.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes an index validity check through the polyglot API.
|
||||
*
|
||||
* @param index the index to check
|
||||
* @return {@code true} if the index is valid, {@code false} otherwise.
|
||||
*/
|
||||
@ExportMessage
|
||||
boolean isArrayElementReadable(long index) {
|
||||
return index < getArraySize() && index >= 0;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
void writeArrayElement(long index, Object value) throws UnsupportedMessageException {
|
||||
throw UnsupportedMessageException.create();
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean isArrayElementModifiable(long index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean isArrayElementInsertable(long index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Arrays.toString(items);
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
package org.enso.interpreter.epb.runtime;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.TruffleContext;
|
||||
import com.oracle.truffle.api.TruffleLanguage;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/** 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawns new thread with associated {@code context}
|
||||
*
|
||||
* @param env environment to spawn the thread in
|
||||
* @param run code to execute in given TruffleContext
|
||||
*/
|
||||
public Thread createThread(TruffleLanguage.Env env, Consumer<TruffleContext> run) {
|
||||
return env.newTruffleThreadBuilder(() -> run.accept(context)).context(context).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* <p>Any code following a call to this method should be executed in a try/finally block, with
|
||||
* {@link #leave(Node, Object)} being called in the finally block. It is crucial that this context
|
||||
* is always left as soon as guest code execution finishes.
|
||||
*
|
||||
* <p>The token returned from this method may not be stored or used for any purpose other than
|
||||
* leaving the context.
|
||||
*
|
||||
* @param node the node to enter this context for
|
||||
* @return a context restoration token that must be passed to {@link #leave(Node, Object)}
|
||||
*/
|
||||
public Object enter(Node node) {
|
||||
if (lock != null) {
|
||||
lock();
|
||||
}
|
||||
return context.enter(node);
|
||||
}
|
||||
|
||||
@CompilerDirectives.TruffleBoundary
|
||||
private void lock() {
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Leaves the context and unlocks it if this wrapper is GILed.
|
||||
*
|
||||
* @param node the node to leave this context for (this must correspond to the same node used in
|
||||
* the call that provided the {@code prev} token
|
||||
* @param prev the token obtained from the call to {@link #enter(Node)}
|
||||
*/
|
||||
public void leave(Node node, Object prev) {
|
||||
context.leave(node, prev);
|
||||
if (lock != null) {
|
||||
unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@CompilerDirectives.TruffleBoundary
|
||||
private void unlock() {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
package org.enso.interpreter.epb.runtime;
|
||||
|
||||
import com.oracle.truffle.api.exception.AbstractTruffleException;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.library.ExportLibrary;
|
||||
import com.oracle.truffle.api.library.ExportMessage;
|
||||
|
||||
/**
|
||||
* A wrapper for exceptions that cross the polyglot boundary.
|
||||
*
|
||||
* <p>It is responsible for proxying messages across the polyglot boundary specifically for the case
|
||||
* of exceptions. Without this, exceptions (as non-linear control flow) would bubble up into Enso
|
||||
* without their context. This would allow them to be caught, but Enso would have no means of
|
||||
* executing code on the guest-language exception. Instead, we wrap the exception into this proxy,
|
||||
* which holds onto the foreign exception, as well as the two contexts necessary for mediating calls
|
||||
* between Enso and the foreign language.
|
||||
*
|
||||
* <p>This is _separate_ to the {@link PolyglotProxy} as we did not want to make that proxy into an
|
||||
* {@link AbstractTruffleException}. This means that we have more control over when foreign objects
|
||||
* are represented as exceptions.
|
||||
*/
|
||||
@ExportLibrary(value = InteropLibrary.class, delegateTo = "delegate")
|
||||
public class PolyglotExceptionProxy extends AbstractTruffleException {
|
||||
final PolyglotProxy delegate;
|
||||
final AbstractTruffleException original;
|
||||
|
||||
public PolyglotExceptionProxy(
|
||||
AbstractTruffleException prototype,
|
||||
GuardedTruffleContext origin,
|
||||
GuardedTruffleContext target) {
|
||||
super(prototype);
|
||||
this.original = prototype;
|
||||
this.delegate = new PolyglotProxy(prototype, origin, target);
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean isException() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
RuntimeException throwException() {
|
||||
throw this;
|
||||
}
|
||||
|
||||
public PolyglotProxy getDelegate() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
public AbstractTruffleException getOriginal() {
|
||||
return original;
|
||||
}
|
||||
|
||||
public GuardedTruffleContext getOrigin() {
|
||||
return delegate.getOrigin();
|
||||
}
|
||||
|
||||
public GuardedTruffleContext getTarget() {
|
||||
return delegate.getTarget();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,49 @@
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class EpbLanguageDependenciesTest {
|
||||
public EpbLanguageDependenciesTest() {}
|
||||
|
||||
@Test(expected = ClassNotFoundException.class)
|
||||
public void avoidFlatbuffers() throws Exception {
|
||||
var c = Class.forName("com.google.flatbuffers.Constants");
|
||||
fail("No class should be found: " + c);
|
||||
}
|
||||
|
||||
@Test(expected = ClassNotFoundException.class)
|
||||
public void avoidJackson() throws Exception {
|
||||
var c = Class.forName("com.fasterxml.jackson.core.JsonParser");
|
||||
fail("No class should be found: " + c);
|
||||
}
|
||||
|
||||
@Test(expected = ClassNotFoundException.class)
|
||||
public void avoidCirce() throws Exception {
|
||||
var c = Class.forName("io.circe.Codec");
|
||||
fail("No class should be found: " + c);
|
||||
}
|
||||
|
||||
@Test(expected = ClassNotFoundException.class)
|
||||
public void avoidApacheCommons() throws Exception {
|
||||
var c = Class.forName("org.apache.commons.io.Charsets");
|
||||
fail("No class should be found: " + c);
|
||||
}
|
||||
|
||||
@Test(expected = ClassNotFoundException.class)
|
||||
public void avoidCats() throws Exception {
|
||||
var c = Class.forName("cats.Align");
|
||||
fail("No class should be found: " + c);
|
||||
}
|
||||
|
||||
@Test(expected = ClassNotFoundException.class)
|
||||
public void avoidSlf4j() throws Exception {
|
||||
var c = Class.forName("org.slf4j.Logger");
|
||||
fail("No class should be found: " + c);
|
||||
}
|
||||
|
||||
@Test(expected = ClassNotFoundException.class)
|
||||
public void avoidSnakeyaml() throws Exception {
|
||||
var c = Class.forName("org.yaml.snakeyaml.Yaml");
|
||||
fail("No class should be found: " + c);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package org.enso.interpreter.epb;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import com.oracle.truffle.api.source.Source;
|
||||
|
||||
public class ForeignEvalNodeTest {
|
||||
|
||||
public ForeignEvalNodeTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sourceWithoutHash() throws Exception {
|
||||
var src = Source.newBuilder("epb", """
|
||||
nonsensecontent
|
||||
""", "simple.test").build();
|
||||
|
||||
var node = ForeignEvalNode.parse(null, src, Collections.emptyList());
|
||||
try {
|
||||
var res = node.execute(null);
|
||||
fail("Unexpected result: " + res);
|
||||
} catch (ForeignParsingException e) {
|
||||
assertEquals("No # found", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,14 @@
|
||||
package org.enso.interpreter.test;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.Value;
|
||||
import org.junit.AfterClass;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ForeignMethodInvokeTest extends TestBase {
|
||||
@ -13,7 +16,7 @@ public class ForeignMethodInvokeTest extends TestBase {
|
||||
|
||||
@BeforeClass
|
||||
public static void prepareCtx() {
|
||||
ctx = defaultContextBuilder("enso").build();
|
||||
ctx = defaultContextBuilder("enso", "js").build();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
@ -44,4 +47,66 @@ public class ForeignMethodInvokeTest extends TestBase {
|
||||
e.getMessage().matches("Cannot parse foreign python method. Only available languages are .+"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInteropWithJavaScript() throws Exception {
|
||||
var source = """
|
||||
from Standard.Base import all
|
||||
|
||||
foreign js js_array t = \"\"\"
|
||||
return [1, 2, t]
|
||||
|
||||
third t = js_array t
|
||||
""";
|
||||
|
||||
var module = ctx.eval("enso", source);
|
||||
var third = module.invokeMember("eval_expression", "third");
|
||||
var res = third.execute(13);
|
||||
assertTrue("It is an array", res.hasArrayElements());
|
||||
assertEquals(3, res.getArraySize());
|
||||
assertEquals(1, res.getArrayElement(0).asInt());
|
||||
assertEquals(2, res.getArrayElement(1).asInt());
|
||||
assertEquals(13, res.getArrayElement(2).asInt());
|
||||
|
||||
var res2 = Executors.newSingleThreadExecutor().submit(() -> {
|
||||
return third.execute(12);
|
||||
}).get();
|
||||
|
||||
assertTrue("It is an array2", res2.hasArrayElements());
|
||||
assertEquals(12, res2.getArrayElement(2).asInt());
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void testParallelInteropWithJavaScript() throws Exception {
|
||||
var source = """
|
||||
from Standard.Base import all
|
||||
|
||||
polyglot java import java.lang.Thread
|
||||
|
||||
foreign js js_array t f = \"\"\"
|
||||
f(300)
|
||||
return [1, 2, t]
|
||||
|
||||
third t = js_array t (delay-> Thread.sleep delay)
|
||||
""";
|
||||
|
||||
var module = ctx.eval("enso", source);
|
||||
var third = module.invokeMember("eval_expression", "third");
|
||||
|
||||
var future = Executors.newSingleThreadExecutor().submit(() -> {
|
||||
return third.execute(12);
|
||||
});
|
||||
var res = third.execute(13);
|
||||
assertTrue("It is an array", res.hasArrayElements());
|
||||
assertEquals(3, res.getArraySize());
|
||||
assertEquals(1, res.getArrayElement(0).asInt());
|
||||
assertEquals(2, res.getArrayElement(1).asInt());
|
||||
assertEquals(13, res.getArrayElement(2).asInt());
|
||||
|
||||
var res2 = future.get();
|
||||
|
||||
assertTrue("It is an array2", res2.hasArrayElements());
|
||||
assertEquals(12, res2.getArrayElement(2).asInt());
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,21 @@
|
||||
package org.enso.interpreter.test.instrument;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.enso.interpreter.test.MockLogHandler;
|
||||
import org.enso.polyglot.MethodNames;
|
||||
import org.enso.polyglot.RuntimeOptions;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.Source;
|
||||
import org.graalvm.polyglot.io.IOAccess;
|
||||
import org.junit.AfterClass;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
public class VerifyJavaScriptIsAvailableTest {
|
||||
public class VerifyLanguageAvailabilityTest {
|
||||
private static Context ctx;
|
||||
private static MockLogHandler handler;
|
||||
|
||||
@ -44,16 +46,22 @@ public class VerifyJavaScriptIsAvailableTest {
|
||||
|
||||
var args =
|
||||
handler.assertMessage(
|
||||
"epb.org.enso.interpreter.epb.EpbContext", "Done initializing language");
|
||||
"epb.org.enso.interpreter.epb.EpbContext", "Parsing foreign script");
|
||||
assertEquals("js", args[0]);
|
||||
assertEquals(Boolean.TRUE, args[1]);
|
||||
assertEquals("mul.mul", args[1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void javaScriptIsPresent() {
|
||||
public void javaScriptIsPresent() throws Exception {
|
||||
var js = ctx.getEngine().getLanguages().get("js");
|
||||
assertNotNull("JavaScript is available", js);
|
||||
var fourtyTwo = ctx.eval("js", "6 * 7");
|
||||
var src = Source.newBuilder("enso", """
|
||||
foreign js mul a b = \"\"\"
|
||||
return a * b
|
||||
|
||||
run = mul 6 7
|
||||
""", "mul.enso").build();
|
||||
var fourtyTwo = ctx.eval(src).invokeMember(MethodNames.Module.EVAL_EXPRESSION, "run");
|
||||
assertEquals(42, fourtyTwo.asInt());
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import java.util.Objects;
|
||||
|
||||
import org.enso.compiler.Compiler;
|
||||
import org.enso.compiler.context.InlineContext;
|
||||
import org.enso.compiler.context.LocalScope;
|
||||
import org.enso.compiler.context.ModuleContext;
|
||||
import org.enso.compiler.data.CompilerConfig;
|
||||
import org.enso.compiler.exception.CompilationAbortedException;
|
||||
@ -24,7 +25,6 @@ import org.enso.interpreter.node.ExpressionNode;
|
||||
import org.enso.interpreter.node.ProgramRootNode;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.IrToTruffle;
|
||||
import org.enso.compiler.context.LocalScope;
|
||||
import org.enso.interpreter.runtime.state.ExecutionEnvironment;
|
||||
import org.enso.interpreter.runtime.tag.AvoidIdInstrumentationTag;
|
||||
import org.enso.interpreter.runtime.tag.IdentifiedTag;
|
||||
@ -32,7 +32,6 @@ import org.enso.interpreter.runtime.tag.Patchable;
|
||||
import org.enso.interpreter.util.FileDetector;
|
||||
import org.enso.lockmanager.client.ConnectedLockManager;
|
||||
import org.enso.logger.masking.MaskingFactory;
|
||||
import org.enso.polyglot.ForeignLanguage;
|
||||
import org.enso.polyglot.LanguageInfo;
|
||||
import org.enso.polyglot.RuntimeOptions;
|
||||
import org.enso.syntax2.Line;
|
||||
@ -71,7 +70,7 @@ import com.oracle.truffle.api.nodes.RootNode;
|
||||
defaultMimeType = LanguageInfo.MIME_TYPE,
|
||||
characterMimeTypes = {LanguageInfo.MIME_TYPE},
|
||||
contextPolicy = TruffleLanguage.ContextPolicy.EXCLUSIVE,
|
||||
dependentLanguages = {ForeignLanguage.ID},
|
||||
dependentLanguages = {"epb"},
|
||||
fileTypeDetectors = FileDetector.class,
|
||||
services= { Timer.class, NotificationHandler.Forwarder.class, LockManager.class }
|
||||
)
|
||||
|
@ -173,8 +173,15 @@ public class FunctionCallInstrumentationNode extends Node implements Instrumenta
|
||||
/** @return the source section of this node. */
|
||||
@Override
|
||||
public SourceSection getSourceSection() {
|
||||
Node parent = getParent();
|
||||
return parent == null ? null : parent.getSourceSection();
|
||||
var parent = getParent();
|
||||
while (parent != null) {
|
||||
var ss = parent.getSourceSection();
|
||||
if (ss != null) {
|
||||
return ss;
|
||||
}
|
||||
parent = parent.getParent();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @return the expression ID of this node. */
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.enso.interpreter.node.expression.builtin.meta;
|
||||
|
||||
import org.enso.interpreter.EnsoLanguage;
|
||||
import org.enso.interpreter.instrument.Timer;
|
||||
import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
@ -8,8 +9,8 @@ import org.enso.interpreter.runtime.data.EnsoObject;
|
||||
import org.enso.interpreter.runtime.data.vector.ArrayLikeHelpers;
|
||||
import org.enso.polyglot.debugger.IdExecutionService;
|
||||
|
||||
import com.oracle.truffle.api.CallTarget;
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.RootCallTarget;
|
||||
import com.oracle.truffle.api.instrumentation.EventBinding;
|
||||
import com.oracle.truffle.api.interop.InteropException;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
@ -17,7 +18,7 @@ import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
final class Instrumentor implements EnsoObject, IdExecutionService.Callbacks {
|
||||
|
||||
private final IdExecutionService service;
|
||||
private final CallTarget target;
|
||||
private final RootCallTarget target;
|
||||
private final Module module;
|
||||
private final Object onEnter;
|
||||
private final Object onReturn;
|
||||
@ -25,7 +26,7 @@ final class Instrumentor implements EnsoObject, IdExecutionService.Callbacks {
|
||||
private final Object onCall;
|
||||
private final EventBinding<?> handle;
|
||||
|
||||
Instrumentor(Module module, IdExecutionService service, CallTarget target) {
|
||||
Instrumentor(Module module, IdExecutionService service, RootCallTarget target) {
|
||||
this.module = module;
|
||||
this.service = service;
|
||||
this.target = target;
|
||||
@ -80,10 +81,12 @@ final class Instrumentor implements EnsoObject, IdExecutionService.Callbacks {
|
||||
var result = onReturnExpr == null || !iop.isString(onReturnExpr) ?
|
||||
info.getResult()
|
||||
:
|
||||
InstrumentorEvalNode.asSuspendedEval(onReturnExpr, info);
|
||||
InstrumentorEvalNode.asSuspendedEval(EnsoLanguage.get(target.getRootNode()), onReturnExpr, info);
|
||||
iop.execute(onReturn, info.getId().toString(), result);
|
||||
}
|
||||
} catch (InteropException ignored) {
|
||||
} catch (Throwable ignored) {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
ignored.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
package org.enso.interpreter.node.expression.builtin.meta;
|
||||
|
||||
import com.oracle.truffle.api.RootCallTarget;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
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.nodes.RootNode;
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import org.enso.interpreter.EnsoLanguage;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.callable.Annotation;
|
||||
@ -25,14 +27,22 @@ final class InstrumentorEvalNode extends RootNode {
|
||||
new boolean[] {true, true},
|
||||
new CallArgumentInfo[0],
|
||||
new Annotation[0]);
|
||||
private static final RootCallTarget CALL = new InstrumentorEvalNode().getCallTarget();
|
||||
private static Reference<InstrumentorEvalNode> last = new WeakReference<>(null);
|
||||
|
||||
private InstrumentorEvalNode() {
|
||||
super(EnsoLanguage.get(null));
|
||||
private InstrumentorEvalNode(EnsoLanguage language) {
|
||||
super(language);
|
||||
}
|
||||
|
||||
static Function asSuspendedEval(Object expr, IdExecutionService.Info info) {
|
||||
return new Function(CALL, null, SUSPENDED_EVAL, new Object[] {expr, info}, new Object[0]);
|
||||
@TruffleBoundary
|
||||
static Function asSuspendedEval(
|
||||
EnsoLanguage language, Object expr, IdExecutionService.Info info) {
|
||||
var node = last.get();
|
||||
if (node == null || node.getLanguage(EnsoLanguage.class) != language) {
|
||||
node = new InstrumentorEvalNode(language);
|
||||
last = new WeakReference<>(node);
|
||||
}
|
||||
var call = node.getCallTarget();
|
||||
return new Function(call, null, SUSPENDED_EVAL, new Object[] {expr, info}, new Object[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -53,7 +53,14 @@ public abstract class PowNode extends IntegerNode {
|
||||
|
||||
@Specialization
|
||||
Object doBigInteger(long self, EnsoBigInteger that) {
|
||||
return doBigInteger(toBigInteger(self), that);
|
||||
var thatValue = that.getValue();
|
||||
if (thatValue.signum() > 0) {
|
||||
return Math.pow((double) self, BigIntegerOps.toDouble(thatValue));
|
||||
} else if (thatValue.signum() == 0) {
|
||||
return 1.0D;
|
||||
} else {
|
||||
return 0.0D;
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@ -69,10 +76,11 @@ public abstract class PowNode extends IntegerNode {
|
||||
|
||||
@Specialization
|
||||
Object doBigInteger(EnsoBigInteger self, EnsoBigInteger that) {
|
||||
if (that.getValue().signum() > 0) {
|
||||
return Math.pow(
|
||||
BigIntegerOps.toDouble(self.getValue()), BigIntegerOps.toDouble(that.getValue()));
|
||||
} else if (that.getValue().signum() == 0) {
|
||||
var thatValue = that.getValue();
|
||||
if (thatValue.signum() > 0) {
|
||||
var selfValue = self.getValue();
|
||||
return Math.pow(BigIntegerOps.toDouble(selfValue), BigIntegerOps.toDouble(thatValue));
|
||||
} else if (thatValue.signum() == 0) {
|
||||
return 1.0D;
|
||||
} else {
|
||||
return 0.0D;
|
||||
|
@ -39,7 +39,6 @@ import org.enso.librarymanager.resolved.LibraryRoot;
|
||||
import org.enso.pkg.Package;
|
||||
import org.enso.pkg.PackageManager;
|
||||
import org.enso.pkg.QualifiedName;
|
||||
import org.enso.polyglot.ForeignLanguage;
|
||||
import org.enso.polyglot.LanguageInfo;
|
||||
import org.enso.polyglot.RuntimeOptions;
|
||||
import org.enso.polyglot.debugger.IdExecutionService;
|
||||
@ -50,7 +49,6 @@ import com.oracle.truffle.api.CallTarget;
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.InstrumentInfo;
|
||||
import com.oracle.truffle.api.ThreadLocalAction;
|
||||
import com.oracle.truffle.api.Truffle;
|
||||
import com.oracle.truffle.api.TruffleFile;
|
||||
@ -210,7 +208,7 @@ public final class EnsoContext {
|
||||
|
||||
var preinit = environment.getOptions().get(RuntimeOptions.PREINITIALIZE_KEY);
|
||||
if (preinit != null && preinit.length() > 0) {
|
||||
var epb = environment.getInternalLanguages().get(ForeignLanguage.ID);
|
||||
var epb = environment.getInternalLanguages().get("epb");
|
||||
@SuppressWarnings("unchecked")
|
||||
var run = (Consumer<String>) environment.lookup(epb, Consumer.class);
|
||||
if (run != null) {
|
||||
|
@ -37,11 +37,20 @@ final class Array implements EnsoObject {
|
||||
*
|
||||
* @param items the element values
|
||||
*/
|
||||
Array(Object... items) {
|
||||
assert noNulls(items);
|
||||
private Array(Object... items) {
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
static Array wrap(Object... items) {
|
||||
assert noNulls(items);
|
||||
return new Array(items);
|
||||
}
|
||||
|
||||
static Array allocate(long size) {
|
||||
var arr = new Object[Math.toIntExact(size)];
|
||||
return new Array(arr);
|
||||
}
|
||||
|
||||
private static boolean noNulls(Object[] arr) {
|
||||
for (Object o : arr) {
|
||||
if (o == null) {
|
||||
|
@ -188,7 +188,7 @@ final class ArrayBuilder implements EnsoObject {
|
||||
if (arr instanceof double[] doubles) {
|
||||
yield Vector.fromDoubleArray(doubles);
|
||||
}
|
||||
yield Vector.fromInteropArray(new Array((Object[])arr));
|
||||
yield Vector.fromInteropArray(Array.wrap((Object[])arr));
|
||||
}
|
||||
default -> throw UnknownIdentifierException.create(name);
|
||||
};
|
||||
|
@ -3,6 +3,7 @@ package org.enso.interpreter.runtime.data.vector;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.NeverDefault;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.exception.AbstractTruffleException;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
|
||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||
@ -33,7 +34,10 @@ public abstract class ArrayLikeAtNode extends Node {
|
||||
try {
|
||||
return self.getItems()[Math.toIntExact(index)];
|
||||
} catch (ArithmeticException | IndexOutOfBoundsException ex) {
|
||||
throw InvalidArrayIndexException.create(index, ex);
|
||||
var cause =
|
||||
new AbstractTruffleException(
|
||||
ex.getMessage(), ex, AbstractTruffleException.UNLIMITED_STACK_TRACE, this) {};
|
||||
throw InvalidArrayIndexException.create(index, cause);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,8 +64,7 @@ public final class ArrayLikeHelpers {
|
||||
* @return the array instance
|
||||
*/
|
||||
public static EnsoObject allocate(long size) {
|
||||
var arr = new Object[Math.toIntExact(size)];
|
||||
return new Array(arr);
|
||||
return Array.allocate(size);
|
||||
}
|
||||
|
||||
@Builtin.Method(
|
||||
@ -107,7 +106,7 @@ public final class ArrayLikeHelpers {
|
||||
return Vector.fromDoubleArray(doubles);
|
||||
}
|
||||
if (nonTrivialEnsoValue) {
|
||||
return Vector.fromInteropArray(new Array((Object[])res));
|
||||
return Vector.fromInteropArray(Array.wrap((Object[])res));
|
||||
} else {
|
||||
return Vector.fromEnsoOnlyArray((Object[])res);
|
||||
}
|
||||
@ -136,15 +135,15 @@ public final class ArrayLikeHelpers {
|
||||
}
|
||||
|
||||
public static EnsoObject wrapEnsoObjects(EnsoObject... arr) {
|
||||
return new Array((Object[]) arr);
|
||||
return Array.wrap((Object[]) arr);
|
||||
}
|
||||
|
||||
public static EnsoObject wrapStrings(String... arr) {
|
||||
return new Array((Object[]) arr);
|
||||
return Array.wrap((Object[]) arr);
|
||||
}
|
||||
|
||||
public static EnsoObject wrapObjectsWithCheckAt(Object... arr) {
|
||||
return new Array((Object[]) arr);
|
||||
return Array.wrap((Object[]) arr);
|
||||
}
|
||||
|
||||
public static EnsoObject empty() {
|
||||
@ -152,7 +151,7 @@ public final class ArrayLikeHelpers {
|
||||
}
|
||||
|
||||
public static EnsoObject asVectorWithCheckAt(Object... arr) {
|
||||
return Vector.fromInteropArray(new Array((Object[]) arr));
|
||||
return Vector.fromInteropArray(Array.wrap((Object[]) arr));
|
||||
}
|
||||
|
||||
public static EnsoObject asVectorFromArray(Object storage) {
|
||||
|
@ -187,7 +187,11 @@ public class DebugLocalScope implements EnsoObject {
|
||||
|
||||
@ExportMessage
|
||||
@TruffleBoundary
|
||||
Object readMember(String member, @CachedLibrary("this") InteropLibrary interop) {
|
||||
Object readMember(String member, @CachedLibrary("this") InteropLibrary interop)
|
||||
throws UnknownIdentifierException {
|
||||
if (!allBindings.containsKey(member)) {
|
||||
throw UnknownIdentifierException.create(member);
|
||||
}
|
||||
FramePointer framePtr = allBindings.get(member);
|
||||
var value = getValue(frame, framePtr);
|
||||
return value != null ? value : DataflowError.UNINITIALIZED;
|
||||
|
@ -62,7 +62,6 @@ import org.enso.compiler.pass.resolve.{
|
||||
TypeNames,
|
||||
TypeSignatures
|
||||
}
|
||||
import org.enso.polyglot.ForeignLanguage
|
||||
import org.enso.interpreter.node.callable.argument.ReadArgumentNode
|
||||
import org.enso.interpreter.node.callable.argument.ReadArgumentCheckNode
|
||||
import org.enso.interpreter.node.callable.function.{
|
||||
@ -1897,7 +1896,7 @@ class IrToTruffle(
|
||||
val bodyExpr = body match {
|
||||
case Foreign.Definition(lang, code, _, _, _) =>
|
||||
buildForeignBody(
|
||||
ForeignLanguage.getBySyntacticTag(lang),
|
||||
lang,
|
||||
code,
|
||||
arguments.map(_.name.name),
|
||||
argSlotIdxs
|
||||
@ -1958,12 +1957,13 @@ class IrToTruffle(
|
||||
}
|
||||
|
||||
private def buildForeignBody(
|
||||
language: ForeignLanguage,
|
||||
language: String,
|
||||
code: String,
|
||||
argumentNames: List[String],
|
||||
argumentSlotIdxs: List[Int]
|
||||
): RuntimeExpression = {
|
||||
val src = language.buildSource(code, scopeName)
|
||||
val src =
|
||||
Source.newBuilder("epb", language + "#" + code, scopeName).build()
|
||||
val foreignCt = context.parseInternal(src, argumentNames: _*)
|
||||
val argumentReaders = argumentSlotIdxs
|
||||
.map(slotIdx =>
|
||||
|
@ -1,11 +1,8 @@
|
||||
package org.enso.benchmarks.processor;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
@ -79,7 +76,6 @@ public class BenchProcessor extends AbstractProcessor {
|
||||
ensoHomeOverride = ensoDir.toPath().resolve("distribution").resolve("component").toFile();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public SourceVersion getSupportedSourceVersion() {
|
||||
return SourceVersion.latest();
|
||||
|
@ -38,7 +38,7 @@ public abstract class Builder {
|
||||
case BigIntegerType x -> new BigIntegerBuilder(size, problemAggregator);
|
||||
case null -> new InferredBuilder(size, problemAggregator);
|
||||
};
|
||||
assert builder.getType().equals(type);
|
||||
assert java.util.Objects.equals(builder.getType(), type);
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
@ -27,22 +27,23 @@ type Filter
|
||||
pair = Meta.atom_with_hole (h -> Filter.Item n h)
|
||||
newLast = pair.value
|
||||
set_last newLast
|
||||
Filter.Head head.first pair.fill
|
||||
[True, Filter.Head head.first pair.fill]
|
||||
|
||||
case self of
|
||||
Filter.Empty ->
|
||||
pair = Meta.atom_with_hole (h -> Filter.Item n h)
|
||||
Filter.Head pair.value pair.fill
|
||||
Filter.Head _ _ -> if iterate self.first then appendN self self.set_last else self
|
||||
[True, Filter.Head pair.value pair.fill]
|
||||
Filter.Head _ _ -> if iterate self.first then appendN self self.set_last else [False, Nothing]
|
||||
|
||||
type Primes
|
||||
Alg generator filter
|
||||
|
||||
Primes.next self = case self of
|
||||
Primes.Alg g f ->
|
||||
filter = f.acceptAndAdd g.n
|
||||
new_primes = Primes.Alg g.next filter
|
||||
if Meta.is_same_object filter f then @Tail_Call new_primes.next else new_primes
|
||||
found_new = f.acceptAndAdd g.n
|
||||
if found_new.first then Primes.Alg g.next found_new.last else
|
||||
new_primes = Primes.Alg g.next f
|
||||
@Tail_Call new_primes.next
|
||||
|
||||
Primes.last_prime self = case self of
|
||||
Primes.Alg g _ -> g.n - 1
|
||||
|
@ -33,7 +33,7 @@ foreign js filter_in_js = """
|
||||
while (filter != null) {
|
||||
if (filter.number) {
|
||||
if (n % filter.number === 0) {
|
||||
return null;
|
||||
return [false, null];
|
||||
}
|
||||
if (filter.number > sqrt) {
|
||||
break;
|
||||
@ -44,7 +44,7 @@ foreign js filter_in_js = """
|
||||
var newFilter = new Filter(n);
|
||||
this.last.next = newFilter;
|
||||
this.last = newFilter;
|
||||
return this;
|
||||
return [true, this];
|
||||
};
|
||||
return new Filter(null);
|
||||
|
||||
@ -65,9 +65,10 @@ type Primes
|
||||
|
||||
Primes.next self = case self of
|
||||
Primes.Alg g f ->
|
||||
filter = f.acceptAndAdd g.n
|
||||
new_primes = Primes.Alg g.next filter
|
||||
if Meta.is_same_object filter f then @Tail_Call new_primes.next else new_primes
|
||||
found_new = f.acceptAndAdd g.n
|
||||
if found_new.first then Primes.Alg g.next found_new.last else
|
||||
new_primes = Primes.Alg g.next f
|
||||
@Tail_Call new_primes.next
|
||||
|
||||
Primes.last_prime self = case self of
|
||||
Primes.Alg g _ -> g.n - 1
|
||||
|
@ -27,22 +27,23 @@ type Filter
|
||||
pair = Meta.atom_with_hole (h -> Filter.Item n h)
|
||||
newLast = pair.value
|
||||
set_last newLast
|
||||
Filter.Head head.first pair.fill
|
||||
[True, Filter.Head head.first pair.fill]
|
||||
|
||||
case self of
|
||||
Filter.Empty ->
|
||||
pair = Meta.atom_with_hole (h -> Filter.Item n h)
|
||||
Filter.Head pair.value pair.fill
|
||||
Filter.Head _ _ -> if iterate self.first then appendN self self.set_last else self
|
||||
[True, Filter.Head pair.value pair.fill]
|
||||
Filter.Head _ _ -> if iterate self.first then appendN self self.set_last else [False, Nothing]
|
||||
|
||||
type Primes
|
||||
Alg generator filter
|
||||
|
||||
Primes.next self = case self of
|
||||
Primes.Alg g f ->
|
||||
filter = f.acceptAndAdd g.n
|
||||
new_primes = Primes.Alg g.next filter
|
||||
if Meta.is_same_object filter f then @Tail_Call new_primes.next else new_primes
|
||||
found_new = f.acceptAndAdd g.n
|
||||
if found_new.first then Primes.Alg g.next found_new.last else
|
||||
new_primes = Primes.Alg g.next f
|
||||
@Tail_Call new_primes.next
|
||||
|
||||
Primes.last_prime self = case self of
|
||||
Primes.Alg g _ -> g.n - 1
|
||||
|
@ -24,23 +24,24 @@ type Filter
|
||||
pair = Meta.atom_with_hole (h -> Filter.Item n h)
|
||||
newLast = pair.value
|
||||
set_last newLast
|
||||
Filter.Head head.first pair.fill
|
||||
[True, Filter.Head head.first pair.fill]
|
||||
|
||||
case self of
|
||||
Filter.Empty ->
|
||||
pair = Meta.atom_with_hole (h -> Filter.Item n h)
|
||||
Filter.Head pair.value pair.fill
|
||||
Filter.Head _ _ -> if iterate self.first then appendN self self.set_last else self
|
||||
[True, Filter.Head pair.value pair.fill]
|
||||
Filter.Head _ _ -> if iterate self.first then appendN self self.set_last else
|
||||
[False, Nothing]
|
||||
|
||||
type Primes
|
||||
Alg generator filter
|
||||
|
||||
Primes.next self = case self of
|
||||
Primes.Alg g f ->
|
||||
filter = f.acceptAndAdd g.n
|
||||
nf = if filter.is_nothing then f else filter
|
||||
new_primes = Primes.Alg g.next nf
|
||||
if filter.is_nothing || Meta.is_same_object filter f then @Tail_Call new_primes.next else new_primes
|
||||
found_new = f.acceptAndAdd g.n
|
||||
if found_new.first then Primes.Alg g.next found_new.last else
|
||||
new_primes = Primes.Alg g.next f
|
||||
@Tail_Call new_primes.next
|
||||
|
||||
Primes.last_prime self = case self of
|
||||
Primes.Alg g _ -> g.n - 1
|
||||
|
@ -173,6 +173,14 @@ spec =
|
||||
_ -> False
|
||||
num_double_match.should_be_true
|
||||
|
||||
Test.specify "should make Python number values equal to Enso ints" <|
|
||||
py_10 = make_int
|
||||
py_10 . should_equal 10
|
||||
|
||||
Test.specify "should make Python number values equal to Enso doubles" <|
|
||||
py_d = make_double
|
||||
py_d . should_equal 10.5
|
||||
|
||||
Test.specify "should make Python None values equal to Nothing" <|
|
||||
py_null = make_null
|
||||
py_null . should_equal Nothing
|
||||
|
Loading…
Reference in New Issue
Block a user