diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index 9695a5ccb0cc..79141445c893 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -205,10 +205,11 @@ jobs: go get -v github.com/ahmetb/go-httpbin/cmd/httpbin $(go env GOPATH)/bin/httpbin -host :8080 & - - name: Install Graalpython + - name: Install Graalpython & FastR if: runner.os != 'Windows' run: | gu install python + gu install r - name: Test Engine Distribution (Unix) shell: bash diff --git a/distribution/std-lib/Test/src/Test.enso b/distribution/std-lib/Test/src/Test.enso index 389d64f4d6bb..c3792a192512 100644 --- a/distribution/std-lib/Test/src/Test.enso +++ b/distribution/std-lib/Test/src/Test.enso @@ -49,6 +49,11 @@ type Verbs be subject argument = this.equal subject argument + contain subject argument = + if subject.contains argument then Success else + msg = subject.to_text + " did not contain " + argument.to_text + "." + here.fail msg + Any.should verb argument = verb Verbs this argument ## Fail a test with the given message. 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 b266704220c3..0c21286d9153 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 @@ -11,7 +11,8 @@ public class EpbParser { /** Lists all the languages supported in polyglot eval. */ public enum ForeignLanguage { JS("js", "js"), - PY("python", "python"); + PY("python", "python"), + R("R", "r"); private final String truffleId; private final String syntacticTag; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextRewrapNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextRewrapNode.java index 179bc5322520..f58960fb9228 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextRewrapNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextRewrapNode.java @@ -4,6 +4,9 @@ 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; @@ -61,6 +64,47 @@ Object doAlreadyProxied( return proxy; } + + @Specialization(guards = "bools.isBoolean(b)") + boolean doWrappedBoolean( + Object b, + GuardedTruffleContext origin, + GuardedTruffleContext target, + @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, + @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, + @CachedLibrary(limit = "5") InteropLibrary numbers) { + try { + return numbers.asDouble(d); + } catch (UnsupportedMessageException e) { + throw new IllegalStateException("Impossible, `l` is checked to be a long"); + } + } + @Fallback Object doWrapProxy(Object o, GuardedTruffleContext origin, GuardedTruffleContext target) { return new PolyglotProxy(o, origin, target); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignEvalNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignEvalNode.java index 526f87671586..4921c27590fb 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 @@ -4,6 +4,7 @@ import com.oracle.truffle.api.TruffleLanguage.ContextReference; import com.oracle.truffle.api.dsl.CachedContext; import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.exception.AbstractTruffleException; import com.oracle.truffle.api.frame.FrameDescriptor; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.interop.InteropLibrary; @@ -25,6 +26,9 @@ public abstract class ForeignEvalNode extends RootNode { private final EpbParser.Result code; private @Child ForeignFunctionCallNode foreign; private @Child ContextRewrapNode rewrapNode = ContextRewrapNodeGen.create(); + private @Child ContextRewrapExceptionNode rewrapExceptionNode = + ContextRewrapExceptionNodeGen.create(); + private @CompilerDirectives.CompilationFinal AbstractTruffleException parseError; private final String[] argNames; ForeignEvalNode(EpbLanguage language, EpbParser.Result code, List arguments) { @@ -51,11 +55,15 @@ Object doExecute( VirtualFrame frame, @CachedContext(EpbLanguage.class) ContextReference contextRef) { ensureParsed(contextRef); - return foreign.execute(frame.getArguments()); + if (foreign != null) { + return foreign.execute(frame.getArguments()); + } else { + throw parseError; + } } private void ensureParsed(ContextReference ctxRef) { - if (foreign == null) { + if (foreign == null && parseError == null) { getLock().lock(); try { if (foreign == null) { @@ -67,6 +75,9 @@ private void ensureParsed(ContextReference ctxRef) { case PY: parsePy(ctxRef); break; + case R: + parseR(ctxRef); + break; default: throw new IllegalStateException("Unsupported language resulted from EPB parsing"); } @@ -91,9 +102,16 @@ private void parseJs(ContextReference ctxRef) { + code.getForeignSource() + "\n};poly_enso_eval"; Source source = Source.newBuilder(code.getLanguage().getTruffleId(), wrappedSrc, "").build(); + CallTarget ct = ctxRef.get().getEnv().parseInternal(source); Object fn = rewrapNode.execute(ct.call(), inner, outer); foreign = insert(JsForeignNode.build(argNames.length, fn)); + } catch (Throwable e) { + if (InteropLibrary.getUncached().isException(e)) { + parseError = rewrapExceptionNode.execute((AbstractTruffleException) e, inner, outer); + } else { + throw e; + } } finally { inner.leave(this, p); } @@ -124,6 +142,35 @@ private void parsePy(ContextReference ctxRef) { Object fn = ctxRef.get().getEnv().importSymbol("poly_enso_py_eval"); Object contextWrapped = rewrapNode.execute(fn, inner, outer); foreign = insert(PyForeignNodeGen.create(contextWrapped)); + } catch (Throwable e) { + if (InteropLibrary.getUncached().isException(e)) { + parseError = rewrapExceptionNode.execute((AbstractTruffleException) e, inner, outer); + } else { + throw e; + } + } finally { + inner.leave(this, p); + } + } + + private void parseR(ContextReference ctxRef) { + EpbContext context = ctxRef.get(); + 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(); + CallTarget ct = ctxRef.get().getEnv().parseInternal(source); + Object fn = rewrapNode.execute(ct.call(), inner, outer); + foreign = insert(RForeignNodeGen.create(fn)); + } catch (Throwable e) { + if (InteropLibrary.getUncached().isException(e)) { + parseError = rewrapExceptionNode.execute((AbstractTruffleException) e, inner, outer); + } else { + throw e; + } } finally { inner.leave(this, p); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/RForeignNode.java b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/RForeignNode.java new file mode 100644 index 000000000000..8670a8b7385a --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/epb/node/RForeignNode.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 RForeignNode 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("R 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 58ee4c369401..0b75b81a92e1 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 @@ -466,6 +466,324 @@ public String asString( } } + @ExportMessage + public boolean isNumber( + @CachedLibrary("this.delegate") InteropLibrary numbers, + @CachedLibrary("this") InteropLibrary node, + @CachedLibrary(limit = "5") InteropLibrary errors, + @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, + @Cached @Cached.Exclusive BranchProfile profile) { + Object p = enterOrigin(node); + try { + return numbers.isNumber(this.delegate); + } catch (Throwable e) { + profile.enter(); + if (errors.isException(e)) { + // `isException` means this must be AbstractTruffleException + //noinspection ConstantConditions + throw contextRewrapExceptionNode.execute((AbstractTruffleException) e, origin, target); + } else { + throw e; + } + } finally { + leaveOrigin(node, p); + } + } + + @ExportMessage + public boolean fitsInByte( + @CachedLibrary("this.delegate") InteropLibrary numbers, + @CachedLibrary("this") InteropLibrary node, + @CachedLibrary(limit = "5") InteropLibrary errors, + @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, + @Cached @Cached.Exclusive BranchProfile profile) { + Object p = enterOrigin(node); + try { + return numbers.fitsInByte(this.delegate); + } catch (Throwable e) { + profile.enter(); + if (errors.isException(e)) { + // `isException` means this must be AbstractTruffleException + //noinspection ConstantConditions + throw contextRewrapExceptionNode.execute((AbstractTruffleException) e, origin, target); + } else { + throw e; + } + } finally { + leaveOrigin(node, p); + } + } + + @ExportMessage + public boolean fitsInShort( + @CachedLibrary("this.delegate") InteropLibrary numbers, + @CachedLibrary("this") InteropLibrary node, + @CachedLibrary(limit = "5") InteropLibrary errors, + @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, + @Cached @Cached.Exclusive BranchProfile profile) { + Object p = enterOrigin(node); + try { + return numbers.fitsInShort(this.delegate); + } catch (Throwable e) { + profile.enter(); + if (errors.isException(e)) { + // `isException` means this must be AbstractTruffleException + //noinspection ConstantConditions + throw contextRewrapExceptionNode.execute((AbstractTruffleException) e, origin, target); + } else { + throw e; + } + } finally { + leaveOrigin(node, p); + } + } + + @ExportMessage + public boolean fitsInInt( + @CachedLibrary("this.delegate") InteropLibrary numbers, + @CachedLibrary("this") InteropLibrary node, + @CachedLibrary(limit = "5") InteropLibrary errors, + @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, + @Cached @Cached.Exclusive BranchProfile profile) { + Object p = enterOrigin(node); + try { + return numbers.fitsInInt(this.delegate); + } catch (Throwable e) { + profile.enter(); + if (errors.isException(e)) { + // `isException` means this must be AbstractTruffleException + //noinspection ConstantConditions + throw contextRewrapExceptionNode.execute((AbstractTruffleException) e, origin, target); + } else { + throw e; + } + } finally { + leaveOrigin(node, p); + } + } + + @ExportMessage + public boolean fitsInLong( + @CachedLibrary("this.delegate") InteropLibrary numbers, + @CachedLibrary("this") InteropLibrary node, + @CachedLibrary(limit = "5") InteropLibrary errors, + @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, + @Cached @Cached.Exclusive BranchProfile profile) { + Object p = enterOrigin(node); + try { + return numbers.fitsInLong(this.delegate); + } catch (Throwable e) { + profile.enter(); + if (errors.isException(e)) { + // `isException` means this must be AbstractTruffleException + //noinspection ConstantConditions + throw contextRewrapExceptionNode.execute((AbstractTruffleException) e, origin, target); + } else { + throw e; + } + } finally { + leaveOrigin(node, p); + } + } + + @ExportMessage + public boolean fitsInFloat( + @CachedLibrary("this.delegate") InteropLibrary numbers, + @CachedLibrary("this") InteropLibrary node, + @CachedLibrary(limit = "5") InteropLibrary errors, + @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, + @Cached @Cached.Exclusive BranchProfile profile) { + Object p = enterOrigin(node); + try { + return numbers.fitsInFloat(this.delegate); + } catch (Throwable e) { + profile.enter(); + if (errors.isException(e)) { + // `isException` means this must be AbstractTruffleException + //noinspection ConstantConditions + throw contextRewrapExceptionNode.execute((AbstractTruffleException) e, origin, target); + } else { + throw e; + } + } finally { + leaveOrigin(node, p); + } + } + + @ExportMessage + public boolean fitsInDouble( + @CachedLibrary("this.delegate") InteropLibrary numbers, + @CachedLibrary("this") InteropLibrary node, + @CachedLibrary(limit = "5") InteropLibrary errors, + @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, + @Cached @Cached.Exclusive BranchProfile profile) { + Object p = enterOrigin(node); + try { + return numbers.fitsInDouble(this.delegate); + } catch (Throwable e) { + profile.enter(); + if (errors.isException(e)) { + // `isException` means this must be AbstractTruffleException + //noinspection ConstantConditions + throw contextRewrapExceptionNode.execute((AbstractTruffleException) e, origin, target); + } else { + throw e; + } + } finally { + leaveOrigin(node, p); + } + } + + @ExportMessage + public byte asByte( + @CachedLibrary("this.delegate") InteropLibrary numbers, + @CachedLibrary("this") InteropLibrary node, + @CachedLibrary(limit = "5") InteropLibrary errors, + @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, + @Cached @Cached.Exclusive BranchProfile profile) + throws UnsupportedMessageException { + Object p = enterOrigin(node); + try { + return numbers.asByte(this.delegate); + } catch (Throwable e) { + profile.enter(); + if (errors.isException(e)) { + // `isException` means this must be AbstractTruffleException + //noinspection ConstantConditions + throw contextRewrapExceptionNode.execute((AbstractTruffleException) e, origin, target); + } else { + throw e; + } + } finally { + leaveOrigin(node, p); + } + } + + @ExportMessage + public short asShort( + @CachedLibrary("this.delegate") InteropLibrary numbers, + @CachedLibrary("this") InteropLibrary node, + @CachedLibrary(limit = "5") InteropLibrary errors, + @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, + @Cached @Cached.Exclusive BranchProfile profile) + throws UnsupportedMessageException { + Object p = enterOrigin(node); + try { + return numbers.asShort(this.delegate); + } catch (Throwable e) { + profile.enter(); + if (errors.isException(e)) { + // `isException` means this must be AbstractTruffleException + //noinspection ConstantConditions + throw contextRewrapExceptionNode.execute((AbstractTruffleException) e, origin, target); + } else { + throw e; + } + } finally { + leaveOrigin(node, p); + } + } + + @ExportMessage + public int asInt( + @CachedLibrary("this.delegate") InteropLibrary numbers, + @CachedLibrary("this") InteropLibrary node, + @CachedLibrary(limit = "5") InteropLibrary errors, + @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, + @Cached @Cached.Exclusive BranchProfile profile) + throws UnsupportedMessageException { + Object p = enterOrigin(node); + try { + return numbers.asInt(this.delegate); + } catch (Throwable e) { + profile.enter(); + if (errors.isException(e)) { + // `isException` means this must be AbstractTruffleException + //noinspection ConstantConditions + throw contextRewrapExceptionNode.execute((AbstractTruffleException) e, origin, target); + } else { + throw e; + } + } finally { + leaveOrigin(node, p); + } + } + + @ExportMessage + public long asLong( + @CachedLibrary("this.delegate") InteropLibrary numbers, + @CachedLibrary("this") InteropLibrary node, + @CachedLibrary(limit = "5") InteropLibrary errors, + @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, + @Cached @Cached.Exclusive BranchProfile profile) + throws UnsupportedMessageException { + Object p = enterOrigin(node); + try { + return numbers.asLong(this.delegate); + } catch (Throwable e) { + profile.enter(); + if (errors.isException(e)) { + // `isException` means this must be AbstractTruffleException + //noinspection ConstantConditions + throw contextRewrapExceptionNode.execute((AbstractTruffleException) e, origin, target); + } else { + throw e; + } + } finally { + leaveOrigin(node, p); + } + } + + @ExportMessage + public float asFloat( + @CachedLibrary("this.delegate") InteropLibrary numbers, + @CachedLibrary("this") InteropLibrary node, + @CachedLibrary(limit = "5") InteropLibrary errors, + @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, + @Cached @Cached.Exclusive BranchProfile profile) + throws UnsupportedMessageException { + Object p = enterOrigin(node); + try { + return numbers.asFloat(this.delegate); + } catch (Throwable e) { + profile.enter(); + if (errors.isException(e)) { + // `isException` means this must be AbstractTruffleException + //noinspection ConstantConditions + throw contextRewrapExceptionNode.execute((AbstractTruffleException) e, origin, target); + } else { + throw e; + } + } finally { + leaveOrigin(node, p); + } + } + + @ExportMessage + public double asDouble( + @CachedLibrary("this.delegate") InteropLibrary numbers, + @CachedLibrary("this") InteropLibrary node, + @CachedLibrary(limit = "5") InteropLibrary errors, + @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode, + @Cached @Cached.Exclusive BranchProfile profile) + throws UnsupportedMessageException { + Object p = enterOrigin(node); + try { + return numbers.asDouble(this.delegate); + } catch (Throwable e) { + profile.enter(); + if (errors.isException(e)) { + // `isException` means this must be AbstractTruffleException + //noinspection ConstantConditions + throw contextRewrapExceptionNode.execute((AbstractTruffleException) e, origin, target); + } else { + throw e; + } + } finally { + leaveOrigin(node, p); + } + } + @ExportMessage public Object toDisplayString( boolean allowSideEffects, @@ -564,4 +882,30 @@ boolean isExceptionIncompleteSource( leaveOrigin(node, p); } } + + @ExportMessage + boolean hasExceptionMessage( + @CachedLibrary("this.delegate") InteropLibrary errors, + @CachedLibrary("this") InteropLibrary node) { + Object p = enterOrigin(node); + try { + return errors.hasExceptionMessage(delegate); + } finally { + leaveOrigin(node, p); + } + } + + @ExportMessage + Object getExceptionMessage( + @CachedLibrary("this.delegate") InteropLibrary errors, + @CachedLibrary("this") InteropLibrary node, + @Cached ContextRewrapNode contextRewrapNode) + throws UnsupportedMessageException { + Object p = enterOrigin(node); + try { + return contextRewrapNode.execute(errors.getExceptionMessage(delegate), origin, target); + } finally { + leaveOrigin(node, p); + } + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/GetStackTraceTextNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/GetStackTraceTextNode.java index 05bce7c6e9f2..fa494bdfbe6b 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/GetStackTraceTextNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/GetStackTraceTextNode.java @@ -55,7 +55,13 @@ String printStackTrace(Throwable throwable) { boolean first = true; for (var errorFrame : stack) { + if (errorFrame.getLocation() == null) { + continue; + } var rootNode = errorFrame.getLocation().getRootNode(); + if (rootNode == null) { + continue; + } var languageInfo = rootNode.getLanguageInfo(); var langId = (languageInfo == null) ? "java" : languageInfo.getId(); var fName = rootNode.getName(); @@ -64,9 +70,14 @@ String printStackTrace(Throwable throwable) { if (sourceLoc != null) { var path = sourceLoc.getSource().getPath(); var ident = (path != null) ? path : sourceLoc.getSource().getName(); - var loc = (sourceLoc.getStartLine() == sourceLoc.getEndLine()) ? - (sourceLoc.getStartLine() + ":" + sourceLoc.getStartColumn() + "-" + sourceLoc.getEndColumn()) : - (sourceLoc.getStartLine() + "-" + sourceLoc.getEndLine()); + var loc = + (sourceLoc.getStartLine() == sourceLoc.getEndLine()) + ? (sourceLoc.getStartLine() + + ":" + + sourceLoc.getStartColumn() + + "-" + + sourceLoc.getEndColumn()) + : (sourceLoc.getStartLine() + "-" + sourceLoc.getEndLine()); src = ident + ":" + loc; } if (first) { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/displaytext/PolyglotErrorToDisplayTextNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/displaytext/PolyglotErrorToDisplayTextNode.java index eec8c2e0bd5d..78615297bd82 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/displaytext/PolyglotErrorToDisplayTextNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/displaytext/PolyglotErrorToDisplayTextNode.java @@ -32,7 +32,7 @@ Text doAtom( Object cause = _this.getFields()[0]; String rep; if (exceptions.hasExceptionMessage(cause)) { - rep = strings.asString(exceptions.getExceptionCause(cause)); + rep = strings.asString(exceptions.getExceptionMessage(cause)); } else { rep = displayTypeNode.execute(cause); } diff --git a/test/Tests/src/Main.enso b/test/Tests/src/Main.enso index 4bd7ab942e90..1c525f4bd25f 100644 --- a/test/Tests/src/Main.enso +++ b/test/Tests/src/Main.enso @@ -13,6 +13,7 @@ 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.Semantic.R_Interop_Spec import Tests.Data.Interval_Spec import Tests.Data.Json_Spec @@ -63,6 +64,7 @@ main = Test.Suite.runMain <| Ordering_Spec.spec Process_Spec.spec Python_Interop_Spec.spec + R_Interop_Spec.spec Range_Spec.spec Text_Spec.spec Time_Spec.spec diff --git a/test/Tests/src/Semantic/Js_Interop_Spec.enso b/test/Tests/src/Semantic/Js_Interop_Spec.enso index 236bc7b8dfa3..7ef4889dec15 100644 --- a/test/Tests/src/Semantic/Js_Interop_Spec.enso +++ b/test/Tests/src/Semantic/Js_Interop_Spec.enso @@ -17,7 +17,7 @@ type My_Type return r + 1; foreign js my_throw = """ - var err = new Error("JS EXc"); + var err = new Error("JS Exc"); throw err; do_throw = Panic.throw this @@ -50,6 +50,9 @@ foreign js make_int = """ foreign js make_double = """ return 10.5 +foreign js does_not_parse = """ + return { x + spec = Test.group "Polyglot JS" <| Test.specify "should allow declaring module-level methods in JS" <| here.my_method 1 2 . should_equal 3 @@ -97,7 +100,7 @@ spec = Test.group "Polyglot JS" <| value = My_Type 1 2 result = Panic.recover <| value.my_throw err = result.catch - err.cause.message . should_equal "JS EXc" + err.cause.message . should_equal "JS Exc" err.cause.name . should_equal "Error" Test.specify "should allow JS to catch Enso exceptions" <| @@ -105,3 +108,7 @@ spec = Test.group "Polyglot JS" <| result = value.do_catch result . should_equal 7 + Test.specify "should properly handle parse errors" <| + err = Panic.recover here.does_not_parse . catch + err.cause.message.should .contain "Expected }" + diff --git a/test/Tests/src/Semantic/Python_Interop_Spec.enso b/test/Tests/src/Semantic/Python_Interop_Spec.enso index a6f9b4cd7c99..06e27a67c973 100644 --- a/test/Tests/src/Semantic/Python_Interop_Spec.enso +++ b/test/Tests/src/Semantic/Python_Interop_Spec.enso @@ -54,6 +54,9 @@ foreign python make_int = """ foreign python make_double = """ return 10.5 +foreign python does_not_parse = """ + if? cxcc 531 6 + spec = pending = if Polyglot.is_language_installed "python" then Nothing else """ Can't run Python tests, Python is not installed. @@ -113,3 +116,7 @@ spec = result = value.do_catch result . should_equal 7 + Test.specify "should properly handle parse errors" <| + err = Panic.recover here.does_not_parse . catch + err.cause.args.at 0 . should .contain 'invalid syntax' + diff --git a/test/Tests/src/Semantic/R_Interop_Spec.enso b/test/Tests/src/Semantic/R_Interop_Spec.enso new file mode 100644 index 000000000000..f6fb329b9fab --- /dev/null +++ b/test/Tests/src/Semantic/R_Interop_Spec.enso @@ -0,0 +1,106 @@ +from Base import all +import Test + +foreign js my_method a b = """ + return a + b; + +type My_Type + type My_Type a b + + foreign r my_method = """ + this$a + this$b + + my_method_2 x = this.my_method * x + + foreign r my_method_3 y = """ + r <- this@my_method_2(y) + r + 1 + + foreign r my_throw = """ + stop("error in R code!", call.=FALSE) + + do_throw = Panic.throw "error!" + + foreign r do_catch = """ + tryCatch(this@do_throw(), error = function(e) e$message) + +foreign r make_object = """ + x = 10 + list(x=x, y=FALSE, compare=function(guess) x < guess) + +foreign r make_array = """ + list(list(x=10), list(x=20), list(x=30)) + +foreign r make_str str = """ + paste("foo", str, "bar", sep=" ") + +foreign r does_not_parse = """ + @@ x y z foo + +foreign r make_int = """ + 10 + +foreign r make_double = """ + 10.5 + +spec = + pending = if Polyglot.is_language_installed "R" then Nothing else """ + Can't run R tests, R is not installed. + Test.group "Polyglot R" pending=pending <| + Test.specify "should allow declaring module-level methods in R" <| + 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 R 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 R 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 R strings type pattern-matchable" <| + str = here.make_str "x" + t = case str of + Text -> True + _ -> False + t.should_be_true + + Test.specify "should make R 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 R exceptions" <| + value = My_Type 1 2 + result = Panic.recover <| value.my_throw + err = result.catch + err.to_display_text.should_equal "Polyglot error: Error: error in R code!" + + pending="R does not support catching polyglot exceptions" + Test.specify "should allow R to catch Enso exceptions" pending=pending <| + value = My_Type 7 2 + result = value.do_catch + result . should_equal 7 + + Test.specify "should properly report parse errors" <| + err = Panic.recover here.does_not_parse + err.catch.to_display_text.should .contain 'parse exception'