Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python interop #1541

Merged
merged 9 commits into from
Mar 5, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you implement the JsForeignNode using @NodeField? Consistency matters for this, otherwise it might be seen as intentional or important.

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