diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index c5334047afcd..9695a5ccb0cc 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -205,6 +205,11 @@ jobs: go get -v github.com/ahmetb/go-httpbin/cmd/httpbin $(go env GOPATH)/bin/httpbin -host :8080 & + - name: Install Graalpython + if: runner.os != 'Windows' + run: | + gu install python + - name: Test Engine Distribution (Unix) shell: bash if: runner.os != 'Windows' diff --git a/distribution/std-lib/Test/src/Test.enso b/distribution/std-lib/Test/src/Test.enso index f2654926a718..389d64f4d6bb 100644 --- a/distribution/std-lib/Test/src/Test.enso +++ b/distribution/std-lib/Test/src/Test.enso @@ -28,12 +28,12 @@ type Matched_On_Error err type Assertion type Success type Failure message - type Pending + type Pending reason is_fail = case this of Success -> False Failure _ -> True - Pending -> False + Pending _ -> False type Verbs type Verbs @@ -116,8 +116,9 @@ Spec.print_report = Failure msg -> IO.print_err (" - [FAILED] " + behavior.name) IO.print_err (" Reason: " + msg) - Pending -> + Pending reason -> IO.print_err (" - [PENDING] " + behavior.name) + IO.print_err (" Reason: " + reason) ## Creates a new test group, desribing properties of the object described by `this`. @@ -129,14 +130,19 @@ Spec.print_report = 2+3 . should_equal 5 Test.specify "should define multiplication" <| 2*3 . should_equal 6 -group name ~behaviors = - r = State.run Spec (Spec name Nil) <| - behaviors - State.get Spec - r.print_report - suite = State.get Suite - new_suite = Suite (Cons r suite.specs) - State.put Suite new_suite +group name ~behaviors pending=Nothing = + case pending of + Nothing -> + r = State.run Spec (Spec name Nil) <| + behaviors + State.get Spec + r.print_report + suite = State.get Suite + new_suite = Suite (Cons r suite.specs) + State.put Suite new_suite + reason -> + IO.print_err ("[PENDING] " + name) + IO.print_err (" Reason: " + reason) ## Specifies a single behavior, described by `this`. @@ -147,8 +153,10 @@ group name ~behaviors = 2+3 . should_equal 5 it "should define multiplication" <| 2*3 . should_equal 6 -specify label ~behavior pending=False = - result = if pending then Pending else here.run_spec behavior +specify label ~behavior pending=Nothing = + result = case pending of + Nothing -> here.run_spec behavior + reason -> Pending reason spec = State.get Spec new_spec = Spec spec.name (Cons (Behavior label result) spec.behaviors) State.put Spec new_spec 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 7ca725a27292..4e1c5d73fac8 100644 --- a/engine/runner/src/main/scala/org/enso/runner/ContextFactory.scala +++ b/engine/runner/src/main/scala/org/enso/runner/ContextFactory.scala @@ -8,7 +8,7 @@ import org.enso.polyglot.debugger.{ DebugServerInfo, DebuggerSessionManagerEndpoint } -import org.enso.polyglot.{LanguageInfo, PolyglotContext, RuntimeOptions} +import org.enso.polyglot.{PolyglotContext, RuntimeOptions} import org.graalvm.polyglot.Context /** Utility class for creating Graal polyglot contexts. @@ -34,9 +34,7 @@ class ContextFactory { strictErrors: Boolean = false ): PolyglotContext = { val context = Context - // 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") + .newBuilder() .allowExperimentalOptions(true) .allowAllAccess(true) .option(RuntimeOptions.PACKAGES_PATH, packagesPath) 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 index 61c3fe764885..b266704220c3 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbParser.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbParser.java @@ -10,7 +10,8 @@ public class EpbParser { /** Lists all the languages supported in polyglot eval. */ public enum ForeignLanguage { - JS("js", "js"); + JS("js", "js"), + PY("python", "python"); private final String truffleId; private final String syntacticTag; 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 index c390bc942414..526f87671586 100644 --- 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 @@ -6,6 +6,10 @@ 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.interop.InteropLibrary; +import com.oracle.truffle.api.interop.UnknownIdentifierException; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.nodes.IndirectCallNode; import com.oracle.truffle.api.nodes.RootNode; import com.oracle.truffle.api.source.Source; import org.enso.interpreter.epb.EpbContext; @@ -56,10 +60,15 @@ private void ensureParsed(ContextReference ctxRef) { try { if (foreign == null) { CompilerDirectives.transferToInterpreterAndInvalidate(); - if (code.getLanguage() == EpbParser.ForeignLanguage.JS) { - parseJs(ctxRef); - } else { - throw new IllegalStateException("Unsupported language resulted from EPB parsing"); + switch (code.getLanguage()) { + case JS: + parseJs(ctxRef); + break; + case PY: + parsePy(ctxRef); + break; + default: + throw new IllegalStateException("Unsupported language resulted from EPB parsing"); } } } finally { @@ -84,7 +93,37 @@ private void parseJs(ContextReference ctxRef) { 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)); + foreign = insert(JsForeignNode.build(argNames.length, fn)); + } finally { + inner.leave(this, p); + } + } + + private void parsePy(ContextReference ctxRef) { + EpbContext context = ctxRef.get(); + GuardedTruffleContext outer = context.getCurrentContext(); + GuardedTruffleContext inner = context.getInnerContext(); + Object p = inner.enter(this); + try { + String args = + Arrays.stream(argNames) + .map(arg -> arg.equals("this") ? "self" : arg) + .collect(Collectors.joining(",")); + String head = + "import polyglot\n" + + "@polyglot.export_value\n" + + "def poly_enso_py_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(); + CallTarget ct = ctxRef.get().getEnv().parseInternal(source); + ct.call(); + Object fn = ctxRef.get().getEnv().importSymbol("poly_enso_py_eval"); + Object contextWrapped = rewrapNode.execute(fn, inner, outer); + foreign = insert(PyForeignNodeGen.create(contextWrapped)); } finally { inner.leave(this, p); } 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 index 42db304e02e1..92ff66627614 100644 --- 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 @@ -1,20 +1,19 @@ 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.*; import com.oracle.truffle.api.library.CachedLibrary; import org.enso.interpreter.runtime.data.Array; /** 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 { - final Object jsFun; - private final int argsCount; + abstract int getArity(); - JsForeignNode(int argsCount, Object jsFun) { - this.argsCount = argsCount; - this.jsFun = jsFun; - } + abstract Object getForeignFunction(); /** * Creates a new instance of this node. @@ -25,15 +24,17 @@ public abstract class JsForeignNode extends ForeignFunctionCallNode { * @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); + return JsForeignNodeGen.create(jsFunction, argumentsCount); } @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); + Object doExecute( + Object[] arguments, @CachedLibrary("foreignFunction") InteropLibrary interopLibrary) { + Object[] positionalArgs = new Object[getArity() - 1]; + if (getArity() - 1 >= 0) System.arraycopy(arguments, 1, positionalArgs, 0, getArity() - 1); try { - return interopLibrary.invokeMember(jsFun, "apply", arguments[0], new Array(positionalArgs)); + return interopLibrary.invokeMember( + getForeignFunction(), "apply", arguments[0], new Array(positionalArgs)); } catch (UnsupportedMessageException | UnknownIdentifierException | ArityException diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/PyForeignNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/PyForeignNode.java new file mode 100644 index 000000000000..eb7439712e84 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/PyForeignNode.java @@ -0,0 +1,25 @@ +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.ArityException; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.interop.UnsupportedTypeException; +import com.oracle.truffle.api.library.CachedLibrary; + +@NodeField(name = "foreignFunction", type = Object.class) +public abstract class PyForeignNode extends ForeignFunctionCallNode { + + abstract Object getForeignFunction(); + + @Specialization + public Object doExecute( + Object[] arguments, @CachedLibrary("foreignFunction") InteropLibrary interopLibrary) { + try { + return interopLibrary.execute(getForeignFunction(), arguments); + } catch (UnsupportedMessageException | UnsupportedTypeException | ArityException e) { + throw new IllegalStateException("Python parser returned a malformed object", e); + } + } +} 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 index 6b2c990983f5..58ee4c369401 100644 --- 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 @@ -56,6 +56,22 @@ public GuardedTruffleContext getTarget() { return target; } + Object enterOrigin(InteropLibrary n) { + if (n.isAdoptable()) { + return origin.enter(n); + } else { + return origin.enter(null); + } + } + + void leaveOrigin(InteropLibrary node, Object prev) { + if (node.isAdoptable()) { + origin.leave(node, prev); + } else { + origin.leave(null, prev); + } + } + @ExportMessage public boolean isNull( @CachedLibrary("this.delegate") InteropLibrary nulls, @@ -63,7 +79,7 @@ public boolean isNull( @CachedLibrary(limit = "5") InteropLibrary errors, @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, @Cached @Cached.Exclusive BranchProfile profile) { - Object p = origin.enter(node); + Object p = enterOrigin(node); try { return nulls.isNull(this.delegate); } catch (Throwable e) { @@ -76,7 +92,7 @@ public boolean isNull( throw e; } } finally { - origin.leave(node, p); + leaveOrigin(node, p); } } @@ -87,7 +103,7 @@ public boolean hasMembers( @CachedLibrary(limit = "5") InteropLibrary errors, @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, @Cached @Cached.Exclusive BranchProfile profile) { - Object p = origin.enter(node); + Object p = enterOrigin(node); try { return members.hasMembers(this.delegate); } catch (Throwable e) { @@ -100,7 +116,7 @@ public boolean hasMembers( throw e; } } finally { - origin.leave(node, p); + leaveOrigin(node, p); } } @@ -114,7 +130,7 @@ public Object getMembers( @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, @Cached @Cached.Exclusive BranchProfile profile) throws UnsupportedMessageException { - Object p = origin.enter(node); + Object p = enterOrigin(node); try { return contextRewrapNode.execute( members.getMembers(this.delegate, includeInternal), origin, target); @@ -128,7 +144,7 @@ public Object getMembers( throw e; } } finally { - origin.leave(node, p); + leaveOrigin(node, p); } } @@ -140,7 +156,7 @@ public boolean isMemberInvocable( @CachedLibrary(limit = "5") InteropLibrary errors, @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, @Cached @Cached.Exclusive BranchProfile profile) { - Object p = origin.enter(node); + Object p = enterOrigin(node); try { return members.isMemberInvocable(this.delegate, member); } catch (Throwable e) { @@ -153,7 +169,7 @@ public boolean isMemberInvocable( throw e; } } finally { - origin.leave(node, p); + leaveOrigin(node, p); } } @@ -173,7 +189,7 @@ public Object invokeMember( for (int i = 0; i < arguments.length; i++) { wrappedArgs[i] = contextRewrapNode.execute(arguments[i], target, origin); } - Object p = origin.enter(node); + Object p = enterOrigin(node); try { return contextRewrapNode.execute( members.invokeMember(this.delegate, member, wrappedArgs), origin, target); @@ -187,7 +203,7 @@ public Object invokeMember( throw e; } } finally { - origin.leave(node, p); + leaveOrigin(node, p); } } @@ -199,7 +215,7 @@ public boolean isMemberReadable( @CachedLibrary(limit = "5") InteropLibrary errors, @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, @Cached @Cached.Exclusive BranchProfile profile) { - Object p = origin.enter(node); + Object p = enterOrigin(node); try { return members.isMemberReadable(this.delegate, member); } catch (Throwable e) { @@ -212,7 +228,7 @@ public boolean isMemberReadable( throw e; } } finally { - origin.leave(node, p); + leaveOrigin(node, p); } } @@ -226,7 +242,7 @@ public Object readMember( @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, @Cached @Cached.Exclusive BranchProfile profile) throws UnknownIdentifierException, UnsupportedMessageException { - Object p = origin.enter(node); + Object p = enterOrigin(node); try { return contextRewrapNode.execute(members.readMember(this.delegate, member), origin, target); } catch (Throwable e) { @@ -239,7 +255,7 @@ public Object readMember( throw e; } } finally { - origin.leave(node, p); + leaveOrigin(node, p); } } @@ -250,7 +266,7 @@ public boolean isExecutable( @CachedLibrary(limit = "5") InteropLibrary errors, @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, @Cached @Cached.Exclusive BranchProfile profile) { - Object p = origin.enter(node); + Object p = enterOrigin(node); try { return functions.isExecutable(this.delegate); } catch (Throwable e) { @@ -263,7 +279,7 @@ public boolean isExecutable( throw e; } } finally { - origin.leave(node, p); + leaveOrigin(node, p); } } @@ -281,7 +297,7 @@ public Object execute( for (int i = 0; i < arguments.length; i++) { wrappedArgs[i] = contextRewrapNode.execute(arguments[i], target, origin); } - Object p = origin.enter(node); + Object p = enterOrigin(node); try { return contextRewrapNode.execute( functions.execute(this.delegate, wrappedArgs), origin, target); @@ -295,7 +311,7 @@ public Object execute( throw e; } } finally { - origin.leave(node, p); + leaveOrigin(node, p); } } @@ -306,7 +322,7 @@ public boolean hasArrayElements( @CachedLibrary(limit = "5") InteropLibrary errors, @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, @Cached @Cached.Exclusive BranchProfile profile) { - Object p = origin.enter(node); + Object p = enterOrigin(node); try { return arrays.hasArrayElements(this.delegate); } catch (Throwable e) { @@ -319,7 +335,7 @@ public boolean hasArrayElements( throw e; } } finally { - origin.leave(node, p); + leaveOrigin(node, p); } } @@ -331,7 +347,7 @@ public long getArraySize( @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, @Cached @Cached.Exclusive BranchProfile profile) throws UnsupportedMessageException { - Object p = origin.enter(node); + Object p = enterOrigin(node); try { return arrays.getArraySize(this.delegate); } catch (Throwable e) { @@ -344,7 +360,7 @@ public long getArraySize( throw e; } } finally { - origin.leave(node, p); + leaveOrigin(node, p); } } @@ -356,7 +372,7 @@ public boolean isArrayElementReadable( @CachedLibrary(limit = "5") InteropLibrary errors, @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, @Cached @Cached.Exclusive BranchProfile profile) { - Object p = origin.enter(node); + Object p = enterOrigin(node); try { return arrays.isArrayElementReadable(this.delegate, idx); } catch (Throwable e) { @@ -369,7 +385,7 @@ public boolean isArrayElementReadable( throw e; } } finally { - origin.leave(node, p); + leaveOrigin(node, p); } } @@ -383,7 +399,7 @@ public Object readArrayElement( @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, @Cached @Cached.Exclusive BranchProfile profile) throws InvalidArrayIndexException, UnsupportedMessageException { - Object p = origin.enter(node); + Object p = enterOrigin(node); try { return contextRewrapNode.execute( arrays.readArrayElement(this.delegate, index), origin, target); @@ -397,7 +413,7 @@ public Object readArrayElement( throw e; } } finally { - origin.leave(node, p); + leaveOrigin(node, p); } } @@ -408,7 +424,7 @@ public boolean isString( @CachedLibrary(limit = "5") InteropLibrary errors, @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, @Cached @Cached.Exclusive BranchProfile profile) { - Object p = origin.enter(node); + Object p = enterOrigin(node); try { return strings.isString(this.delegate); } catch (Throwable e) { @@ -421,7 +437,7 @@ public boolean isString( throw e; } } finally { - origin.leave(node, p); + leaveOrigin(node, p); } } @@ -433,7 +449,7 @@ public String asString( @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, @Cached @Cached.Exclusive BranchProfile profile) throws UnsupportedMessageException { - Object p = origin.enter(node); + Object p = enterOrigin(node); try { return strings.asString(this.delegate); } catch (Throwable e) { @@ -446,7 +462,7 @@ public String asString( throw e; } } finally { - origin.leave(node, p); + leaveOrigin(node, p); } } @@ -458,7 +474,7 @@ public Object toDisplayString( @CachedLibrary(limit = "5") InteropLibrary errors, @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, @Cached @Cached.Exclusive BranchProfile profile) { - Object p = origin.enter(node); + Object p = enterOrigin(node); try { return displays.toDisplayString(this.delegate, allowSideEffects); } catch (Throwable e) { @@ -471,7 +487,7 @@ public Object toDisplayString( throw e; } } finally { - origin.leave(node, p); + leaveOrigin(node, p); } } @@ -479,11 +495,11 @@ public Object toDisplayString( boolean isException( @CachedLibrary("this") InteropLibrary node, @CachedLibrary("this.delegate") InteropLibrary errors) { - Object p = origin.enter(node); + Object p = enterOrigin(node); try { return errors.isException(delegate); } finally { - origin.leave(node, p); + leaveOrigin(node, p); } } @@ -494,7 +510,7 @@ RuntimeException throwException( @CachedLibrary(limit = "5") InteropLibrary errors, @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode) throws UnsupportedMessageException { - Object p = origin.enter(node); + Object p = enterOrigin(node); try { throw delegate.throwException(delegate); } catch (Throwable e) { @@ -506,7 +522,7 @@ RuntimeException throwException( throw e; } } finally { - origin.leave(node, p); + leaveOrigin(node, p); } } @@ -515,11 +531,11 @@ ExceptionType getExceptionType( @CachedLibrary("this") InteropLibrary node, @CachedLibrary("this.delegate") InteropLibrary errors) throws UnsupportedMessageException { - Object p = origin.enter(node); + Object p = enterOrigin(node); try { return errors.getExceptionType(delegate); } finally { - origin.leave(node, p); + leaveOrigin(node, p); } } @@ -528,11 +544,11 @@ int getExceptionExitStatus( @CachedLibrary("this") InteropLibrary node, @CachedLibrary("this.delegate") InteropLibrary errors) throws UnsupportedMessageException { - Object p = origin.enter(node); + Object p = enterOrigin(node); try { return errors.getExceptionExitStatus(delegate); } finally { - origin.leave(node, p); + leaveOrigin(node, p); } } @@ -541,11 +557,11 @@ boolean isExceptionIncompleteSource( @CachedLibrary("this.delegate") InteropLibrary errors, @CachedLibrary("this") InteropLibrary node) throws UnsupportedMessageException { - Object p = origin.enter(node); + Object p = enterOrigin(node); try { return errors.isExceptionIncompleteSource(delegate); } finally { - origin.leave(node, p); + leaveOrigin(node, p); } } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/RecoverPanicNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/RecoverPanicNode.java index 0c0d88e288a2..e0e6af07e12b 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/RecoverPanicNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/RecoverPanicNode.java @@ -2,6 +2,7 @@ import com.oracle.truffle.api.dsl.CachedContext; 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.library.CachedLibrary; import com.oracle.truffle.api.nodes.Node; @@ -42,13 +43,13 @@ Stateful doExecute( try { return thunkExecutorNode.executeThunk(action, state, BaseNode.TailStatus.NOT_TAIL); } catch (PanicException e) { - return new Stateful(state, DataflowError.withTrace(e.getPayload(), this, e.getStackTrace())); + return new Stateful(state, DataflowError.withTrace(e.getPayload(), e)); } catch (Throwable e) { if (exceptions.isException(e)) { return new Stateful( state, DataflowError.withTrace( - ctx.getBuiltins().error().makePolyglotError(e), this, e.getStackTrace())); + ctx.getBuiltins().error().makePolyglotError(e), (AbstractTruffleException) e)); } unknownExceptionProfile.enter(); throw e; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/IsLanguageInstalledNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/IsLanguageInstalledNode.java new file mode 100644 index 000000000000..eb168aff06ae --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/IsLanguageInstalledNode.java @@ -0,0 +1,36 @@ +package org.enso.interpreter.node.expression.builtin.interop.generic; + +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.nodes.Node; +import org.enso.interpreter.Language; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.node.expression.builtin.interop.java.AddToClassPathNodeGen; +import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode; +import org.enso.interpreter.runtime.Context; + +import java.io.File; + +@BuiltinMethod( + type = "Polyglot", + name = "is_language_installed", + description = "Checks if a polyglot language is installed in the runtime environment.") +public abstract class IsLanguageInstalledNode extends Node { + + static IsLanguageInstalledNode build() { + return IsLanguageInstalledNodeGen.create(); + } + + @Specialization + boolean doExecute( + Object _this, + Object language_name, + @CachedContext(Language.class) Context context, + @Cached ExpectStringNode expectStringNode) { + String name = expectStringNode.execute(language_name); + return context.getEnvironment().getPublicLanguages().get(name) != null; + } + + abstract boolean execute(Object _this, Object language_name); +} 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 de2ef9775a7d..2b11bc1303ba 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 @@ -29,6 +29,8 @@ private void createPolyglot(Language language, ModuleScope scope) { 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)); + scope.registerMethod( + polyglot, "is_language_installed", IsLanguageInstalledMethodGen.makeFunction(language)); } /** @return the atom constructor for polyglot */ diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java index 70eb67c71fa2..cdcbd7d40411 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java @@ -41,14 +41,11 @@ public static DataflowError withoutTrace(Object payload, Node location) { * want to point to the original location of the panic. * * @param payload the user-provided value carried by the error - * @param location the node in which the error was located - * @param trace a specific stack trace + * @param prototype the exception to derive the stacktrace from * @return a new dataflow error */ - public static DataflowError withTrace(Object payload, Node location, StackTraceElement[] trace) { - var error = new DataflowError(payload, location); - error.setStackTrace(trace); - return error; + public static DataflowError withTrace(Object payload, AbstractTruffleException prototype) { + return new DataflowError(payload, prototype); } DataflowError(Object payload, Node location) { @@ -56,6 +53,11 @@ public static DataflowError withTrace(Object payload, Node location, StackTraceE this.payload = payload; } + DataflowError(Object payload, AbstractTruffleException prototype) { + super(prototype); + this.payload = payload; + } + /** * Returns the user provided payload carried by this object. * diff --git a/test/Tests/src/Main.enso b/test/Tests/src/Main.enso index 100ec47ea966..4bd7ab942e90 100644 --- a/test/Tests/src/Main.enso +++ b/test/Tests/src/Main.enso @@ -7,11 +7,13 @@ import Tests.Semantic.Case_Spec 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 +import Tests.Semantic.Java_Interop_Spec +import Tests.Semantic.Js_Interop_Spec +import Tests.Semantic.Python_Interop_Spec + import Tests.Data.Interval_Spec import Tests.Data.Json_Spec import Tests.Data.List_Spec @@ -60,6 +62,7 @@ main = Test.Suite.runMain <| Numbers_Spec.spec Ordering_Spec.spec Process_Spec.spec + Python_Interop_Spec.spec Range_Spec.spec Text_Spec.spec Time_Spec.spec diff --git a/test/Tests/src/Network/Http_Spec.enso b/test/Tests/src/Network/Http_Spec.enso index 5078df2ed1cb..2957c8f465bd 100644 --- a/test/Tests/src/Network/Http_Spec.enso +++ b/test/Tests/src/Network/Http_Spec.enso @@ -19,10 +19,9 @@ polyglot java import java.util.Objects spec = is_ci = System.getenv "CI" == "true" - if is_ci then here.spec_impl else Nothing - -spec_impl = - Test.group "Http" <| + pending = if is_ci then Nothing else """ + The HTTP tests only run when the `CI` environment variable is set to true + Test.group "Http" pending=pending <| Test.specify "should create HTTP client with timeout setting" <| http = Http.new (timeout = 30.seconds) http.timeout.should_equal 30.seconds diff --git a/test/Tests/src/Semantic/Js_Interop_Spec.enso b/test/Tests/src/Semantic/Js_Interop_Spec.enso index a6e9bcedf351..236bc7b8dfa3 100644 --- a/test/Tests/src/Semantic/Js_Interop_Spec.enso +++ b/test/Tests/src/Semantic/Js_Interop_Spec.enso @@ -44,6 +44,12 @@ foreign js make_array = """ foreign js make_str str = """ return "foo " + str + " bar" +foreign js make_int = """ + return 10 + +foreign js make_double = """ + return 10.5 + spec = Test.group "Polyglot JS" <| Test.specify "should allow declaring module-level methods in JS" <| here.my_method 1 2 . should_equal 3 @@ -73,6 +79,20 @@ spec = Test.group "Polyglot JS" <| _ -> False t.should_be_true + Test.specify "should make JS numbers type pattern-matchable" <| + int_match = case here.make_int of + Integer -> True + int_match.should_be_true + double_match = case here.make_double of + Decimal -> True + double_match.should_be_true + num_int_match = case here.make_int of + Number -> True + num_int_match.should_be_true + num_double_match = case here.make_double of + Number -> True + num_double_match.should_be_true + Test.specify "should allow Enso to catch JS exceptions" <| value = My_Type 1 2 result = Panic.recover <| value.my_throw diff --git a/test/Tests/src/Semantic/Python_Interop_Spec.enso b/test/Tests/src/Semantic/Python_Interop_Spec.enso new file mode 100644 index 000000000000..a6f9b4cd7c99 --- /dev/null +++ b/test/Tests/src/Semantic/Python_Interop_Spec.enso @@ -0,0 +1,115 @@ +from Base import all +import Test + +foreign python my_method a b = """ + return a + b + +type My_Type + type My_Type a b + + foreign python my_method = """ + return self.a + self.b + + my_method_2 x = this.my_method * x + + foreign python my_method_3 y = """ + r = self.my_method_2(y) + return r + 1 + + foreign python my_throw = """ + err = RuntimeError('Error!') + raise err + + do_throw = Panic.throw this + + foreign python do_catch = """ + try: + self.do_throw() + except Exception as e: + return e.a + +foreign python make_object = """ + class My: + def __init__(self): + self.x = 10 + self.y = False + def compare(self, guess): + return self.x < guess + return My() + +foreign python make_array = """ + class My: + def __init__(self, x): + self.x = x + def compare(self, guess): + return self.x < guess + return [My(10), My(20), My(30)] + +foreign python make_str str = """ + return ("foo " + str + " bar") + +foreign python make_int = """ + return 10 + +foreign python make_double = """ + return 10.5 + +spec = + pending = if Polyglot.is_language_installed "python" then Nothing else """ + Can't run Python tests, Python is not installed. + Test.group "Polyglot Python" pending=pending <| + Test.specify "should allow declaring module-level methods in Python" <| + 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 Python 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 Python 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 Python strings type pattern-matchable" <| + str = here.make_str "x" + t = case str of + Text -> True + _ -> False + t.should_be_true + + Test.specify "should make Python numbers type pattern-matchable" <| + int_match = case here.make_int of + Integer -> True + int_match.should_be_true + double_match = case here.make_double of + Decimal -> True + double_match.should_be_true + num_int_match = case here.make_int of + Number -> True + num_int_match.should_be_true + num_double_match = case here.make_double of + Number -> True + num_double_match.should_be_true + + Test.specify "should allow Enso to catch Python exceptions" <| + value = My_Type 1 2 + result = Panic.recover <| value.my_throw + err = result.catch + err.cause.args.at 0 . should_equal 'Error!' + err.cause.to_text . should_equal "RuntimeError('Error!')" + + pending_msg = "Graalpython does not support handling interop exceptions" + Test.specify "should allow Python to catch Enso exceptions" pending=pending_msg <| + value = My_Type 7 2 + result = value.do_catch + result . should_equal 7 +