Skip to content

Commit

Permalink
Python interop (#1541)
Browse files Browse the repository at this point in the history
  • Loading branch information
kustosz authored Mar 5, 2021
1 parent b5e9895 commit 03fa549
Show file tree
Hide file tree
Showing 16 changed files with 361 additions and 90 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/scala.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
34 changes: 21 additions & 13 deletions distribution/std-lib/Test/src/Test.enso
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`.
Expand All @@ -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`.

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -56,10 +60,15 @@ private void ensureParsed(ContextReference<EpbContext> 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 {
Expand All @@ -84,7 +93,37 @@ private void parseJs(ContextReference<EpbContext> 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<EpbContext> 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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Loading

0 comments on commit 03fa549

Please sign in to comment.