Jérémie B
Jérémie B

Reputation: 11022

How to annotate builder-like DSL structure?

I have in my application severals DSL implemented with groovy builders. I'd like to support annotations in some node, by example :

builder.define {

  @Secure
  checkService {
    ... 
  }

}

However, this script is not syntaxically correct and it doesn't compile (I can't annotate an invokation of a method).

Is it possible to implement this kind of DSL in Groovy, with an AST transformation or any other methods ? If yes, does anybody have an example ?

I use temporarily properties, like checkService(secure:true), but it's not really pretty...!

Upvotes: 0

Views: 141

Answers (1)

Mene
Mene

Reputation: 3799

Annotation cannot be placed at a MethodCallExpression. Even the new Groovy 3 Syntax won't allow this.

As cfrick said, you'll have to look for other options.

Plain groovy with closures and omitted braces

You can create a similar syntax by defining secure as a method, returning Closure from each method and use groovys optional braces:

class Builder {
    def define(@DelegatesTo(InnerBuilder) Closure defineClosure) {
        println("start define")
        def innerBuilder = new InnerBuilder()
        defineClosure.delegate = innerBuilder
        Closure returnedClosure = defineClosure()

        // also call the returned Closure
        returnedClosure.delegate = innerBuilder
        returnedClosure()
        println("end define")
    }

    class InnerBuilder {
        def secure(Closure c) {
            return { ->
                println("start being secure")
                c()
                println("stop being secure")
            }
        }

        def elegant(Closure c) {
            return { ->
                println("start being elegant")
                c()
                println("stop being elegant")
            }
        }

        def checkService(Closure c) {
            return { ->
                println("start checkService")
                c()
                println("stop checkService")
            }
        }
    }

}

def builder = new Builder()
builder.define {
    checkService {
        println "running a service check"
    }
}

println()
println("with secure")
println()

builder.define {
    secure checkService {
        println "running a service check"
    }
}

//    NOT WORKING
//    println()
//    println("with new-line but missing the backslash")
//    println()
//
//    builder.define {
//        secure // this will NOT be secure!
//        checkService {
//            println "running a service check"
//        }
//    }

println()
println("with new-line and backslash")
println()

builder.define {
    // this WILL be secure!
    secure \
    checkService {
        println "running a service check"
    }
}

println()
println("two")
println()

builder.define {
    // needs braces
    elegant secure(checkService {
        println "running a service check"
    })
}

println()
println("Both orders work")
println()

builder.define {
    secure elegant(checkService {
        println "running a service check"
    })
}

println()
println("Multiple with line-break")
println()

builder.define {
    secure \
    elegant(
    checkService {
        println "running a service check"
    })
}

Will print

start define
start checkService
running a service check
stop checkService
end define

with secure

start define
start being secure
start checkService
running a service check
stop checkService
stop being secure
end define

with new-line and backslash

start define
start being secure
start checkService
running a service check
stop checkService
stop being secure
end define

two

start define
start being elegant
start being secure
start checkService
running a service check
stop checkService
stop being secure
stop being elegant
end define

Both orders work

start define
start being secure
start being elegant
start checkService
running a service check
stop checkService
stop being elegant
stop being secure
end define

Multiple with line-break

start define
start being secure
start being elegant
start checkService
running a service check
stop checkService
stop being elegant
stop being secure
end define

AST transform

This is a bit more complex and I don't think it delivers the value worth the effort, but you could use an AST transform to mitigate the syntax issues of the previous version.

class Builder {
    def define(@DelegatesTo(InnerBuilder) Closure defineClosure) {
        println("start define")
        def innerBuilder = new InnerBuilder()
        defineClosure.delegate = innerBuilder
        defineClosure()
        println("end define")
    }

    class InnerBuilder {
        def secure = "just a string"

        def elegant = "just a string"

        def checkService(Closure c) {
            println("start checkService")
            c()
            println("stop checkService")
        }
    }

}

def builder = new Builder()
builder.define {
    secure
    elegant
    checkService {
        println "running a service check"
    }
}

Thus far secure has no meaning (it's a NOP). You'll need to write an AST Transform to change that. Here secure and elegant should (i.e. not verified) be VariableExpression that are inside a ExpressionStatement as part of a BlockStatement that is the code property of the surrounding ClosureExpression. Actual implementation is left as an excercise for the reader ;)

Upvotes: 1

Related Questions