Question about the new polyglot API and its `Value` class

Christian Humer christian.humer at gmail.com
Fri Jan 12 17:50:56 UTC 2018


Hi Peter,

This is an excellent question.

Just to clarify: You want to expose Guest language objects to the user
through the Evaluator, but you don't want them to use the polyglot API to
access those objects so you stay independent of the runtime in use?

Interestingly we spent the past weeks to solve exactly this issue, as it
came up in one of our use-cases as well. We are about to introduce API to
support executing `value.as(Object.class)` to return a representation that
only uses core JDK classes to represent the value. So for example we would
return a Map<String, Object> for JavaScript objects and a Number subclass
representation for JS numbers. Unfortunately we need to return Map<Long,
Object> for arrays, as Java objects cannot implement List and Map at the
same time (its incompatible because of the signature of the remove method).
The same mapping is performed when you call host Java methods with Object
parameters. To get back to the polyglot API you can either use
polyglot.Value in the method signature or convert it using the new
Context#asValue method.

Here is how you would specify such an evaluator:

interface Evaluator {
  Object evaluate(String language, String script);
}

class PolyglotJSEvaluator implements Evaluator {
  final Context context = Context.create();
  Object evaluate(String language, String script) {
    return context.eval(language, script).as(Object.class);
  }
}

Here is our work in progress javadoc specification for this:

 * <h1>Object target type mapping</h1>

*

* The following rules apply when <code>Object</code> is used as a target
type:

* <ol>

* <li>If the value represents {@link #isNull() null} then <code>null</code>
is returned.

* <li>If the value is a {@link #isHostObject() host object} then the value
is coerced to

* {@link #asHostObject() host object value}.

* <li>If the value is a {@link #isString() string} then the value is
coerced to {@link String} or

* {@link Character}.

* <li>If the value is a {@link #isBoolean() boolean} then the value is
coerced to {@link Boolean}.

* <li>If the value is a {@link #isNumber() number} then the value is
coerced to {@link Number}. The

* specific sub type of the {@link Number} is not specified. Users need to
be prepared for any

* Number subclass including {@link BigInteger} or {@link BigDecimal}. It is
recommended to cast to

* {@link Number} and then convert to a Java primitive like with {@link
Number#longValue()}.

* <li>If the value {@link #hasMembers() has members} or has {@link
#hasArrayElements() array

* elements} then the result value will implement {@link Map}. If this value
{@link #hasMembers()

* has members} then all members are accessible using {@link String} keys.
If this value

* {@link #hasArrayElements() has array elements} then all members are
accessible using {@link Long}

* keys. The {@link Map#size() size} of the returned {@link Map} will
contain the sum of the count

* of members and array elements. The returned value will implement {@link
Function} if the

* value can be {@link #canExecute() executed}. The equality and hashCode
contract of the returned

* map does not comply with the Map specification. Instead it uses the
identity of the guest

* language value.

* <li>If the value can be {@link #canExecute() executed} or {@link
#canInstantiate() instantiated}

* then the result value implements {@link Function Function}. By default
the argument of the

* function will be used as single argument to the function when executed.
If a value of type

* {@link Object Object[]} is provided then the function will executed with
those arguments. The

* returned function may also implement {@link Map} if the value has {@link
#hasArrayElements()

* array elements} or {@link #hasMembers() members}.

* <li>If none of the above rules apply then this {@link Value} instance is
returned.

* <b>JavaScript Usage Examples:</b>

*

* <pre>

* Context context = Context.create();

* assert context.eval("js", "undefined").as(Object.class) == null;

* assert context.eval("js", "'foobar'").as(Object.class) instanceof String;

* assert context.eval("js", "42").as(Object.class) instanceof Number;

* assert context.eval("js", "[]").as(Object.class) instanceof Map;

* assert context.eval("js", "{}").as(Object.class) instanceof Map;

* assert ((Map<Object, Object>) context.eval("js",
"[{}]").as(Object.class)).get(0) instanceof Map;

* assert context.eval("js", "(function(){})").as(Object.class) instanceof
Function;

* </pre>* </ol>


Does this cover your use-case as well?

Thanks.

- Christian Humer





On Thu, Jan 11, 2018 at 11:53 PM Peter Niederwieser <pniederwieser at apple.com>
wrote:

> Hello,
>
> I have a broader question related to the new polyglot API and its `Value`
> class:
>
> Say we have a Truffle language that comes with an `Evaluator` Java API for
> evaluating language scripts from Java code. The result of evaluating a
> language script is an object (set of named properties). Say the `Evaluator`
> API has its own external object representation (which it returns to the
> caller) that is fully decoupled from the language's internal object
> representation and the Truffle/Polyglot API.
>
> Even though it doesn’t have a need for language interop, the `Evaluator`
> implementation may want to use the `org.graalvm.polyglot` API, as this is
> the only way to leverage Truffle features such as language contexts and
> node instrumentation. In this case, script evaluation will produce an
> `org.graalvm.polyglot.Value`. Given that the internal language object
> underlying the `Value` cannot be accessed, and that every message send
> returns another `Value`, is there any way for the `Evaluator`
> implementation to easily/efficiently perform the conversion from internal
> to external object representation? (Evaluating a script may produce an
> object that is the root of a large object graph.)
>
> Thanks,
> Peter


More information about the graal-dev mailing list