When a method is defined to be a macro, the compiler takes the code that is passed as its argument and turns it into an AST. It then invokes the macro implementation with that AST, and it returns a new AST that is then spliced back to its call site.
import reflect.macros.blackbox.Context
object Macros {
// This macro simply sees if the argument is the result of an addition expression.
// E.g. isAddition(1+1) and isAddition("a"+1).
// but !isAddition(1+1-1), as the addition is underneath a subtraction, and also
// !isAddition(x.+), and !isAddition(x.+(a,b)) as there must be exactly one argument.
def isAddition(x: Any): Boolean = macro isAddition_impl
// The signature of the macro implementation is the same as the macro definition,
// but with a new Context parameter, and everything else is wrapped in an Expr.
def isAddition_impl(c: Context)(expr: c.Expr[Any]): c.Expr[Boolean] = {
import c.universe._ // The universe contains all the useful methods and types
val plusName = TermName("+").encodedName // Take the name + and encode it as $plus
expr.tree match { // Turn expr into an AST representing the code in isAddition(...)
case Apply(Select(_, `plusName`), List(_)) => reify(true)
// Pattern match the AST to see whether we have an addition
// Above we match this AST
// Apply (function application)
// / \
// Select List(_) (exactly one argument)
// (selection ^ of entity, basically the . in x.y)
// / \
// _ \
// `plusName` (method named +)
case _ => reify(false)
// reify is a macro you use when writing macros
// It takes the code given as its argument and creates an Expr out of it
}
}
}
It is also possible to have macros that take Tree
s as arguments. Like how reify
is used to create Expr
s, the q
(for quasiquote) string interpolator lets us create and deconstruct Tree
s. Note that we could have used q
above (expr.tree
is, surprise, a Tree
itself) too, but didn't for demonstrative purposes.
// No Exprs, just Trees
def isAddition_impl(c: Context)(tree: c.Tree): c.Tree = {
import c.universe._
tree match {
// q is a macro too, so it must be used with string literals.
// It can destructure and create Trees.
// Note how there was no need to encode + this time, as q is smart enough to do it itself.
case q"${_} + ${_}" => q"true"
case _ => q"false"
}
}