Skip to content

Commit

Permalink
Accept Array-like objects seamlesly in builtins
Browse files Browse the repository at this point in the history
Most of the time, rather than defining the type of the parameter of the
builtin, we want to accept every Array-like object i.e. Vector, Array,
polyglot Array etc.
Rather than writing all possible combinations, and likely causing bugs
on the way anyway as we already saw, one should use `CoerceArrayNode`
to convert to Java's `Object[]`.
Added various test cases to illustrate the problem.
  • Loading branch information
hubertp committed Oct 20, 2022
1 parent 81e5e77 commit ed5340b
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.*;
import org.enso.interpreter.epb.node.CoercePrimitiveNode;
import org.enso.interpreter.node.expression.builtin.mutable.CoerceArrayNode;
import org.enso.interpreter.node.expression.foreign.CoerceNothing;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.data.Array;
Expand All @@ -19,9 +20,6 @@
name = "from_array",
description = "Creates a Vector by copying Array content.")
public abstract class FromArrayBuiltinVectorNode extends Node {
private @Child CoercePrimitiveNode coercePrimitiveNode = CoercePrimitiveNode.build();
private @Child CoerceNothing coerceNothingNode = CoerceNothing.build();

static FromArrayBuiltinVectorNode build() {
return FromArrayBuiltinVectorNodeGen.create();
}
Expand All @@ -34,24 +32,11 @@ Vector fromVector(Vector arr) {
}

@Specialization(guards = "interop.hasArrayElements(arr)")
Vector fromArrayLikeObject(Object arr, @CachedLibrary(limit = "3") InteropLibrary interop) {
try {
long length = interop.getArraySize(arr);
Object[] target = new Object[(int) length];
for (int i = 0; i < length; i++) {
try {
var value = interop.readArrayElement(arr, i);
target[i] = coerceNothingNode.execute(coercePrimitiveNode.execute(value));
} catch (InvalidArrayIndexException ex) {
var ctx = Context.get(this);
var err = ctx.getBuiltins().error().makeInvalidArrayIndexError(arr, i);
throw new PanicException(err, this);
}
}
return Vector.fromArray(new Array(target));
} catch (UnsupportedMessageException ex) {
throw unsupportedException(arr);
}
Vector fromArrayLikeObject(
Object arr,
@Cached CoerceArrayNode coerce,
@CachedLibrary(limit = "3") InteropLibrary interop) {
return Vector.fromArray(new Array(coerce.execute(arr)));
}

@Fallback
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.enso.interpreter.node.expression.builtin.interop.generic;

import com.oracle.truffle.api.dsl.Cached;
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;
Expand All @@ -9,22 +11,30 @@
import org.enso.interpreter.Constants;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode;
import org.enso.interpreter.node.expression.builtin.mutable.CoerceArrayNode;
import org.enso.interpreter.runtime.data.Array;
import org.enso.interpreter.runtime.error.PanicException;

@BuiltinMethod(
type = "Polyglot",
name = "execute",
description = "Executes a polyglot function object (e.g. a lambda).")
public class ExecuteNode extends Node {
public abstract class ExecuteNode extends Node {
private @Child InteropLibrary library =
InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH);
private @Child HostValueToEnsoNode hostValueToEnsoNode = HostValueToEnsoNode.build();
private final BranchProfile err = BranchProfile.create();

Object execute(Object callable, Array arguments) {
public static ExecuteNode build() {
return ExecuteNodeGen.create();
}

public abstract Object execute(Object callable, Object arguments);

@Specialization
Object execute(Object callable, Object arguments, @Cached("build()") CoerceArrayNode coerce) {
try {
return hostValueToEnsoNode.execute(library.execute(callable, arguments.getItems()));
return hostValueToEnsoNode.execute(library.execute(callable, coerce.execute(arguments)));
} catch (UnsupportedMessageException | ArityException | UnsupportedTypeException e) {
err.enter();
throw new PanicException(e.getMessage(), this);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.enso.interpreter.node.expression.builtin.interop.generic;

import com.oracle.truffle.api.dsl.Cached;
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;
Expand All @@ -8,22 +10,29 @@
import com.oracle.truffle.api.profiles.BranchProfile;
import org.enso.interpreter.Constants;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.runtime.data.Array;
import org.enso.interpreter.node.expression.builtin.mutable.CoerceArrayNode;
import org.enso.interpreter.runtime.error.PanicException;

@BuiltinMethod(
type = "Polyglot",
name = "new",
description = "Instantiates a polyglot constructor.")
public class InstantiateNode extends Node {
public abstract class InstantiateNode extends Node {

private @Child InteropLibrary library =
InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH);
private final BranchProfile err = BranchProfile.create();

Object execute(Object constructor, Array arguments) {
public static InstantiateNode build() {
return InstantiateNodeGen.create();
}

public abstract Object execute(Object constructor, Object arguments);

@Specialization
Object execute(Object constructor, Object arguments, @Cached("build()") CoerceArrayNode coerce) {
try {
return library.instantiate(constructor, arguments.getItems());
return library.instantiate(constructor, coerce.execute(arguments));
} catch (UnsupportedMessageException | ArityException | UnsupportedTypeException e) {
err.enter();
throw new PanicException(e.getMessage(), this);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,42 @@
package org.enso.interpreter.node.expression.builtin.interop.generic;

import com.oracle.truffle.api.interop.*;
import com.oracle.truffle.api.dsl.Cached;
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.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.profiles.BranchProfile;
import org.enso.interpreter.Constants;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.node.expression.builtin.mutable.CoerceArrayNode;
import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode;
import org.enso.interpreter.runtime.data.Array;
import org.enso.interpreter.runtime.error.PanicException;

@BuiltinMethod(
type = "Polyglot",
name = "invoke",
description = "Invokes a polyglot method by name, dispatching by the target argument.")
public class InvokeNode extends Node {
public abstract class InvokeNode extends Node {
private @Child InteropLibrary library =
InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH);
private @Child ExpectStringNode expectStringNode = ExpectStringNode.build();
private final BranchProfile err = BranchProfile.create();

Object execute(Object target, Object name, Array arguments) {
public static InvokeNode build() {
return InvokeNodeGen.create();
}

public abstract Object execute(Object target, Object name, Object arguments);

@Specialization
Object execute(
Object target, Object name, Object arguments, @Cached("build()") CoerceArrayNode coerce) {
try {
return library.invokeMember(target, expectStringNode.execute(name), arguments.getItems());
return library.invokeMember(
target, expectStringNode.execute(name), coerce.execute(arguments));
} catch (UnsupportedMessageException
| ArityException
| UnsupportedTypeException
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
package org.enso.interpreter.node.expression.builtin.meta;

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.node.expression.builtin.mutable.CoerceArrayNode;
import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.data.Array;

@BuiltinMethod(
type = "Meta",
name = "new_atom",
description = "Creates a new atom with given constructor and fields.")
public class NewAtomInstanceNode extends Node {
Atom execute(AtomConstructor constructor, Array fields) {
return constructor.newInstance(fields.getItems());
public abstract class NewAtomInstanceNode extends Node {

static NewAtomInstanceNode build() {
return NewAtomInstanceNodeGen.create();
}

abstract Atom execute(AtomConstructor constructor, Object fields);

@Specialization
Atom execute(AtomConstructor constructor, Object fields, @Cached CoerceArrayNode coerce) {
return constructor.newInstance(coerce.execute(fields));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package org.enso.interpreter.node.expression.builtin.mutable;

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.epb.node.CoercePrimitiveNode;
import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode;
import org.enso.interpreter.node.expression.foreign.CoerceNothing;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.builtin.Builtins;
import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.data.Vector;
import org.enso.interpreter.runtime.data.Array;
import org.enso.interpreter.runtime.error.PanicException;

public abstract class CoerceArrayNode extends Node {
private @Child InteropLibrary library = InteropLibrary.getFactory().createDispatched(10);

public static CoerceArrayNode build() {
return CoerceArrayNodeGen.create();
}

public abstract Object[] execute(Object value);

@Specialization
Object[] doArray(Array arr) {
return arr.getItems();
}

@Specialization
Object[] doVector(Vector arr, @Cached HostValueToEnsoNode hostValueToEnsoNode) {
try {
return convertToArray(arr, hostValueToEnsoNode);
} catch (UnsupportedMessageException e) {
Builtins builtins = Context.get(this).getBuiltins();
Atom err = builtins.error().makeTypeError(builtins.array(), arr, "arr");
throw new PanicException(err, this);

} catch (InvalidArrayIndexException e) {
Builtins builtins = Context.get(this).getBuiltins();
throw new PanicException(
builtins.error().makeInvalidArrayIndexError(arr, e.getInvalidIndex()), this);
}
}

@Specialization(guards = "interop.hasArrayElements(arr)")
Object[] doArrayLike(
Object arr,
@CachedLibrary(limit = "5") InteropLibrary interop,
@Cached HostValueToEnsoNode hostValueToEnsoNode) {
try {
return convertToArray(arr, hostValueToEnsoNode);
} catch (UnsupportedMessageException e) {
Builtins builtins = Context.get(this).getBuiltins();
Atom err = builtins.error().makeTypeError(builtins.array(), arr, "arr");
throw new PanicException(err, this);
} catch (InvalidArrayIndexException e) {
Builtins builtins = Context.get(this).getBuiltins();
throw new PanicException(
builtins.error().makeInvalidArrayIndexError(arr, e.getInvalidIndex()), this);
}
}

@ExplodeLoop
private Object[] convertToArray(Object arr, HostValueToEnsoNode hostValueToEnsoNode)
throws UnsupportedMessageException, InvalidArrayIndexException {
int argsLength = (int) library.getArraySize(arr);
Object[] arr1 = new Object[argsLength];
for (int i = 0; i < argsLength; i++) {
arr1[i] = hostValueToEnsoNode.execute(library.readArrayElement(arr, i));
}
return arr1;
}

@Fallback
public Object[] doOther(Object arr) {
Builtins builtins = Context.get(this).getBuiltins();
Atom error = builtins.error().makeTypeError("array", arr, "arr");
throw new PanicException(error, this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.io.TruffleProcessBuilder;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import org.apache.commons.lang3.SystemUtils;
import org.enso.interpreter.dsl.Builtin;
import org.enso.interpreter.node.expression.builtin.mutable.CoerceArrayNode;
import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.data.Array;
import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.error.PanicException;

Expand Down Expand Up @@ -53,20 +54,23 @@ public static void exit(long code) {
@Builtin.WrapException(from = IOException.class, to = PanicException.class)
@Builtin.WrapException(from = InterruptedException.class, to = PanicException.class)
@CompilerDirectives.TruffleBoundary
@ExplodeLoop
public static Atom createProcess(
Context ctx,
Object command,
Array arguments,
Object arguments,
Object input,
boolean redirectIn,
boolean redirectOut,
boolean redirectErr,
@Cached CoerceArrayNode coerce,
@Cached ExpectStringNode expectStringNode)
throws IOException, InterruptedException {
String[] cmd = new String[arguments.getItems().length + 1];
Object[] arrArguments = coerce.execute(arguments);
String[] cmd = new String[arrArguments.length + 1];
cmd[0] = expectStringNode.execute(command);
for (int i = 1; i <= arguments.getItems().length; i++) {
cmd[i] = expectStringNode.execute(arguments.getItems()[i - 1]);
for (int i = 1; i <= arrArguments.length; i++) {
cmd[i] = expectStringNode.execute(arrArguments[i - 1]);
}
TruffleProcessBuilder pb = ctx.getEnvironment().newProcessBuilder(cmd);

Expand Down
26 changes: 26 additions & 0 deletions test/Tests/src/Data/Polyglot_Spec.enso
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from Standard.Base import all

from Standard.Test import Test, Test_Suite

polyglot java import java.time.LocalDate
polyglot java import java.lang.String
polyglot java import java.util.function.Function

spec = Test.group "Polyglot" <|
Test.specify "should be able to invoke a polyglot method by name and pass arguments" <|
poly_date = LocalDate.now
date = Date.now.to_date_time

Polyglot.invoke poly_date "atStartOfDay" [] . should_equal date
Polyglot.invoke poly_date "atStartOfDay" [].to_array . should_equal date

Test.specify "should be able to crate a new polyglot object using the constructor" <|
Polyglot.new String ["42"] . should_equal "42"
Polyglot.new String ["42"].to_array . should_equal "42"

Test.specify "should be able to execute a polyglot function object along with corresponding arguments" <|
fun = Function.identity
Polyglot.execute fun ["42"] . should_equal "42"
Polyglot.execute fun ["42"].to_array . should_equal "42"

main = Test_Suite.run_main spec
7 changes: 6 additions & 1 deletion test/Tests/src/Semantic/Meta_Spec.enso
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ spec = Test.group "Meta-Value Manipulation" <|
meta_atom.constructor.should_equal My_Type.Value
meta_atom.fields.should_equal [1, "foo", Nothing]
Meta.meta (meta_atom.constructor) . new [1, "foo", Nothing] . should_equal atom
Test.specify "should allow creating atoms from atom constructors" <|
atom_1 = Meta.new_atom My_Type.Value [1,"foo", Nothing]
(Meta.meta atom_1).constructor . should_equal My_Type.Value
atom_2 = Meta.new_atom My_Type.Value [1,"foo", Nothing].to_array
(Meta.meta atom_2).constructor . should_equal My_Type.Value
Test.specify "should correctly return representations of different classes of objects" <|
Meta.meta 1 . should_equal (Meta.Primitive_Data 1)
Meta.meta "foo" . should_equal (Meta.Primitive_Data "foo")
Expand Down Expand Up @@ -148,7 +153,7 @@ spec = Test.group "Meta-Value Manipulation" <|

Test.specify "should allow to get the source location of a frame" pending=location_pending <|
src = Meta.get_source_location 0
loc = "Meta_Spec.enso:150:15-40"
loc = "Meta_Spec.enso:155:15-40"
src.take (Last loc.length) . should_equal loc

Test.specify "should allow to get qualified type names of values" <|
Expand Down
Loading

0 comments on commit ed5340b

Please sign in to comment.