Groovy under macroscope

Sergei Egorov / @bsideup

Baruch Sadogursky / @jbaruch

GORM

/* Before */
def persons = Person.findAll {
    name =~ /Serg/
}

/* After */
def persons = Person.executeQuery '''
    FROM Person WHERE name LIKE '%Serg%'
'''

Poor man's GORM

@Macro
def findAll(def context, Expression exp, ClosureExpression cl) {
  return callX(
    exp,
    "executeQuery",
    constX("FROM ${exp.name} " +
      cl.code.statements.collect {
        if(it instanceof BinaryExpression
          && it.operation.type == REGEX_COMPARISON_OPERATOR)
            "$it.leftExpression LIKE '%$it.rightExpression.text%'"
      }.filter{ it != null }.join(" AND ")
    )
  )
}

Starting simple

def hello = assertNulls foo.bar.messages.hello.toUpperCase()

println hello

Will print

Assertion failed:

assert foo?.bar?.messages?.hello?.toUpperCase() != null
        |    |    |         |      |             |
        |    |    null      null   null          false
        |    [hello:World!]
        [bar:[hello:World!]]

Look Ma so easy

@Macro def assertNulls(def context, Expression self, Expression exp){
    makeNullSafe(exp)
    def cl = closureX(params(), block(
            new AssertStatement(notNullX(exp)),
            returnS(exp)
    ))
    return callX(cl, "call", args())
}

def makeNullSafe(Expression exp) {
    if (exp instanceof PropertyExpression
            || exp instanceof MethodCallExpression) {
        exp.safe = true
        makeNullSafe(exp.objectExpression)
    }
}

Result

def hello = {
    assert foo?.bar?.messages?.hello?.toUpperCase() != null

    return foo.bar.messages.hello.toUpperCase()
}()

println hello

Classic AST nodes creation

def returnStatement = new ReturnStatement(
        new ConstructorCallExpression(
                ClassHelper.make(SomeCoolClass),
                new ArgumentListExpression(
                        new ConstantExpression("someValue")
                )
        )
)

MacroGroovy

def returnStatement = macro {
    return new SomeCoolClass("someValue")
}

Compare

def returnStatement = new ReturnStatement(
        new ConstructorCallExpression(
                ClassHelper.make(SomeCoolClass),
                new ArgumentListExpression(
                        new ConstantExpression("someValue")
                )
        )
)
def returnStatement = macro {
    return new SomeCoolClass("someValue")
}

How it's possible?

Macro methods!

Example

def returnStatement = macro {
    return new SomeCoolClass("someValue")
}

After Macro transformation

def returnStatement = MacroBuilder.INSTANCE.macro(
        '''return new SomeCoolClass("someValue")''',
        [:],
        ReturnStatement
)

Where:

public enum MacroBuilder {
    public <T> T macro(
            String source,
            Map<MacroSubstitutionKey, Closure<Expression>> context,
            Class<T> resultClass
    )
}