kaakaa Blog

この世は極楽 空にはとんぼ

GStringの評価タイミングを勘違いしてた

GStringヒアドキュメント?で記述したスクリプトをGroovyShell#evaluateで評価しようとした時に、GString内のプレースホルダーが評価される場所を勘違いしていてハマったという話。

GString内のプレースホルダーはevaluateの前で評価される。
GString interpolation not working with GroovyShell ?


以下、経緯。

GroovyDSLを作ろうとしてGStringでハマった。

あとから要素を追加しやすいよう、methodMissingを使って動的にプロパティを追加できるようにしました。

def dsl = """\
person {
    name('Foo')
    age('28')
}
"""

def script = """\
def person(Closure closure) {
    def person = new Person()
    closure.delegate = person
    closure()
    
    println person.profile()
}

class Person {
    Person() {
        def mc = new ExpandoMetaClass( Person, false, true)
        mc.initialize()
        this.metaClass = mc
    }
    
    // ここで動的にプロパティを追加したい
    def methodMissing(String name, args) {
        this.metaClass."${name}" = args[0]
    }
    
    def profile(){
        this.properties
    }
}
"""

new GroovyShell().evaluate("""\
${dsl}
${script}
""")

実行するとエラー。

Exception thrown

groovy.lang.MissingPropertyException: No such property: methodName for class: ConsoleScript30

	at ConsoleScript30.run(ConsoleScript30:8)

methodMissingに引数methodNameが上手く渡って無いのが原因かと思い、

    def methodMissing(String methodName, args) {
        def methodName = 'hello'
        this.metaClass."${methodName}" = args[0]
    }

とかしてみたけど変わらず。

エラーの原因は一番上に書いた通りなので、変数dsl,scriptの宣言をGStringでなくシングルクォートのヒアドキュメントにしたら動きました。

def dsl = '''\
person {
    name('Foo')
    age('28')
}
'''

def script = '''\
def person(Closure closure) {
    def person = new Person()
    closure.delegate = person
    closure()
    
    println person.profile()
}

class Person {
    Person() {
        def mc = new ExpandoMetaClass( Person, false, true)
        mc.initialize()
        this.metaClass = mc
    }
    
    // ここで動的にプロパティを追加したい
    def methodMissing(String methodName, args) {
        this.metaClass."${methodName}" = args[0]
    }
    
    def profile(){
        this.properties
    }
}
'''

new GroovyShell().evaluate("""\
${dsl}
${script}
""")
[class:class Person, age:28, name:Foo]