开发者

How do Groovy Scripts interact with propertyMissing?

开发者 https://www.devze.com 2023-04-07 02:12 出处:网络
I writing a DSL that executes as a Script; it has various classes for various bits of syntax. e.g., for the \"foo\" keyword that takes a closure, I have a FooSyntax class and evaluate the closure \"wi

I writing a DSL that executes as a Script; it has various classes for various bits of syntax. e.g., for the "foo" keyword that takes a closure, I have a FooSyntax class and evaluate the closure "with" an instance of that syntax. This works fine, e.g.

bar = thing {} // make a thing 
baz = foo {
    mykeyword bar
}

passes the thing called bar to an invocation of FooSyntax#mykeyword.

I'm trying to add some better error messages for when there is an unknown variable reference. This manifests as a MissingPropertyException so my current approach is to add a propertyMissing method to FooSyntax. This works indeed for variables that are missing.

Unfortunately, it breaks the example above: bar becomes a missing property instead of falling through to the Binding. Why does adding a propertyMissing cause the Binding not to be consulted? (Does this have to do with the Closure's resolve strategy?) How can I fix this?

You can play with this with a sample script at https://g开发者_如何学编程ist.github.com/1237768


I shall delegate my answer to the gist on which I commented. Basically, you should not be using the with() method to execute the closure against the FooSyntax delegate. For future reference, the standard approach is:

def foo(Closure cl) {
    def f = new FooSyntax()
    def c = cl.clone()
    c.delegate = f
    c.call()
}

You can fine tune the behaviour by changing the resolution strategy on the closure like so:

c.resolveStrategy = Closure.DELEGATE_FIRST

but in this case you want the default Closure.OWNER_FIRST to ensure the binding is queried first.


Since bar was not being declared (just assigned), it was hitting the property missing because there wasn't an already defined bar in the script or syntax classes.

In your example, I think you want to implement a methodMissing. In your scenario you are trying to call Foo.myKeyword with a non-Thing type. So that is really a missing method and not a missing property.

I've changed your script, changing propertyMissing to methodMissing and adding def for foo and also defined bar as a String.

class Thing { }

class FooSyntax {
   def myKeyword(Thing t) { println "Hello Foo " + t.toString(); }    
   def methodMissing(String name, args) {
       println "no method named ${name} for ${args} exists"
   }
}

class ScriptSyntax {
    def foo(Closure cl) {
        def f = new FooSyntax();
        f.with cl
    }
    def thing() { new Thing()  }

    def dispatchKeyword(String methodName, Object args) {
       invokeMethod(methodName, args)
    }
}

def runner(String text) {
   def source = new GroovyCodeSource(text, "inlineScript", "inline.groovy")

   def script = new GroovyClassLoader().parseClass(source).newInstance(binding) as Script
   def dsl = new ScriptSyntax()
   script.metaClass.methodMissing = { name, args -> dsl.dispatchKeyword(name, args) }
   script.run()
}

runner("""def bar = thing()
def baz = "not a thing"
foo { myKeyword bar }
foo { myKeyword baz }""")

Output:
Hello Foo Thing@1038de7
no method named myKeyword for [not a thing] exists
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号