graaljs icon indicating copy to clipboard operation
graaljs copied to clipboard

Value.as to support classes

Open lburgazzoli opened this issue 6 years ago • 7 comments

I'm working to use graaljs as embedded script engine for a custom defined DSL and was looking for a method to achieve something like what's possible with with groovy's DelegatingScript (http://docs.groovy-lang.org/latest/html/api/groovy/util/DelegatingScript.html) so basically having the script engine delegating properties and method invocation to a delegate object.

As pointed out by @chumer on gitter, there is a way to do a similar thing like:

   @HostAccess.Implementable
    static interface MyInterface {
        int foo();
    }
    public static void main(String[] args) {
        try (Context context = Context.create()) {
            MyInterface v = context.getBindings("js").as(MyInterface.class);
            context.eval("js", "function foo() { return 42; }");
            System.out.println(v.foo());
        }
    }

This however supports interface only but would be nice to have support for classes so one can use this functionality to create DSLs

lburgazzoli avatar Jul 15 '19 15:07 lburgazzoli

I am sorry, I am not sure that I understand your request. Value.as() allows you to use JavaScript object as if it was Java object but it seems that you need the opposite, i.e., to use Java object (that implements the semantics of your DSL) as if it was JavaScript object (preferably the global object), no?

Maybe you can use the following approach:

        try (Context context = Context.newBuilder().allowAllAccess(true).build()) {
            Object dsl = new java.awt.Point(42, 211);
            context.getBindings("js").putMember("dsl", dsl);
            String dslScript = "x + getY()";
            String wrappedScript = "with (dsl) { " + dslScript + " }";
            Value result = context.eval("js", wrappedScript);
            System.out.println(result);
        }

DSL is represented by methods/fields of java.awt.Point (for simplicity) here. This Java object is exposed as dsl global variable to JavaScript. The script that is using the DSL is wrapped by with (dsl) so that the properties defined on dsl object look like defined globally.

Let me know if this makes sense and works for you or whether you need something else (but provide more details then). Thank you in advance.

iamstolis avatar Jul 17 '19 09:07 iamstolis

@iamstolis yes I could have misunderstood the meaning of Value.as(...) and yes what you wrote make perfectly sense and I will give it a try.

However it would be nice to have a cleaner way to do it and avoid the wrappedScript trick.

lburgazzoli avatar Jul 17 '19 09:07 lburgazzoli

@lburgazzoli I think it depends on whether you expect functions to be declared on the host or the guest (js) side. If you want to expose functions from the guest to the host with Java signatures then Value.as(Interface.class) is useful. If you want to expose functions from from the host to the guest with is a solution as @iamstolis pointed out.

chumer avatar Jul 17 '19 09:07 chumer

Understood.

Would be indeed nice to have something like context.getBindings("js").with(object)

lburgazzoli avatar Jul 17 '19 09:07 lburgazzoli

Would be indeed nice to have something like context.getBindings("js").with(object)

I don't think we can justify to change the Java language for this ;-).

chumer avatar Jul 17 '19 11:07 chumer

I guess this does not require a change to the Java language but use the object as JS's global object, not sure indeed how complex and feasible it is.

btw, the with trick does work

lburgazzoli avatar Jul 17 '19 11:07 lburgazzoli

Ah. Got it. .with(object) sounds like an interesting feature when other languages are involved as well.

chumer avatar Jul 17 '19 11:07 chumer