Reputation: 11022
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
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.
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
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