Where next?

In part 1 we used a macro to generate some regular expressions and a case statement that used them:

{
val regex$1 = Predef.augmentString("^/api/author/(.+)").r;
val regex$2 = Predef.augmentString("^/api/book/(.+)").r;
val regex$3 = Predef.augmentString("^/api/category/(.+)").r;
s match {
case regex$1((id @ _)) => Some.apply("Author with ID ".$plus(id))
case regex$2((id @ _)) => Some.apply("Book with ID ".$plus(id))
case regex$3((id @ _)) => Some.apply("Category with ID ".$plus(id))
case _ => None
}
}

What you’ve probably noticed though is that with this code, the regexes are parsed each time through the block, which isn’t going to be very good for performance. What we really want is to be able to put the regular expressions in an object so that they are parsed only once. But we’re using Scala 2.10, and all we have available are def (function) macros - we can’t generate any other types (although type macros do exist in the macro paradise).

Instead we need to create a value which then gets populated by a function that returns an object, like this:

def makeRegexes() = {
object regexContainer {
val regex$1 = Predef.augmentString("^/api/author/(.+)").r;
val regex$2 = Predef.augmentString("^/api/book/(.+)").r;
val regex$3 = Predef.augmentString("^/api/category/(.+)").r;
}
regexContainer
}
object Router {
val regexes = makeRegexes()
def caseMatcher() = s match {
case regexes.regex$1((id @ _)) => Some.apply("Author with ID ".$plus(id))
case regexes.regex$2((id @ _)) => Some.apply("Book with ID ".$plus(id))
case regexes.regex$3((id @ _)) => Some.apply("Category with ID ".$plus(id))
case _ => None
}
}

So now we’ve got two functions for our generated code - and we know how to generate function bodies. However, we have another problem - each function will be generated by a separate macro, and we’ll need to know what the variables created in one macro are called when using them in the other macro. Fortunately we can just avoid that problem - each regular expression is generated from a class name, and each class name (with its package qualifier) is unique, so we can use the class name to generate the variable names (replacing . with $).

Code organisation

Our macro code is getting bigger, and I don’t want it to end up as a pair of unmanageable functions that go on for page after page - so I’m going to split the code up into two classes - the one that has the macro definitions in it and builds the resultant AST for the generated code, and another class that contains all the logic for analysing the compilation environment.

As documented on the def macros page on the Scala site, you can create other classes that use your macro context easily enough - you just have to give the compiler a little helping hand to work out where the context comes from. Of the two examples for doing this on that page, the second is (to me) much more readable, so we’ve got a new class:

import scala.reflect.macros.Context
class Helper[C <: Context](val c: C) {
import c.universe._
}

And obviously we can initialise this just like any other class from within our macro implementations using val helper = new Helper(c). If, for tidiness, you want to import the functions defined in Helper, you can then do import helper.{c => cc, _}, which renames the c value from Helper so that it doesn’t collide with the c parameter from our function signature.

Moving the compilation unit processing code into the Helper, and adding some name processing so that the class name and package name are available to our macro, we end up with:

import scala.reflect.macros.Context
class Helper[C <: Context](val c: C) {
import c.universe._
case class ClassNames(simpleName : String, packageName : String, fullyQualified : String, fieldName : String)
private def packageFinder(t: Tree, packageChecker: String => Boolean) = t match {
case PackageDef(id, _) => packageChecker(id.name.decoded)
case _ => false
}
private def classFinder(t: Tree) = t match {
case ClassDef(mods, _, _, _) => mods.hasFlag(Flag.CASE)
case _ => false
}
private def modelClasses = c.enclosingRun.units.filter(unit => unit.body.find(packageFinder(_, pkg => pkg.equals("model") || pkg.startsWith("model."))).isDefined && unit.body.find(classFinder).isDefined)
private def namesForClass(unit : CompilationUnit) = {
val className = unit.body.find(classFinder).get.asInstanceOf[ClassDef].name.decoded
val packageName = unit.body.find(packageFinder(_, _ => true)).get.asInstanceOf[PackageDef].name.decoded
val fqn = packageName + "." + className
ClassNames(className, packageName, fqn, fqn.replace(".", "$"))
}
def foreachClass(fn : ClassNames => Unit) : Unit = {
modelClasses.foreach(unit => {
val names = namesForClass(unit)
fn(names)
})
}
}
view raw Helper.scala hosted with ❤ by GitHub

Object Creation

When you define an object in normal Scala, you just declare it, object myObject, because the syntactic sugar allows you to leave out a default constructor, and that it extends scala.AnyRef. In a macro you don’t have that luxury, so to define a new object, you do the following:

val constructorBody = Block(List(Apply(Select(Super(This(EMPTY), EMPTY), CONSTRUCTOR), List())), Literal(Constant(())))
val constructor = DefDef(NoMods, CONSTRUCTOR, List(), List(List()), TypeTree(), constructorBody)
val objInheritance = List(Ident(newTypeName("AnyRef")))
val obj = ModuleDef(NoMods, newTermName("REGEXES"), Template(objInheritance, emptyValDef, List(constructor)))

We already know how to put a block of code together from part 1, so all we need to do is merge the two together, and we get:

def macroPaths: Any = macro macroPathsImpl
def macroPathsImpl(c: Context): c.Expr[Any] = {
val helper = new Helper[c.type](c)
import c.universe._
import nme.CONSTRUCTOR
import tpnme.EMPTY
val regexes = ListBuffer[Tree]()
helper.foreachClass(names => {
val regexExpr = c.Expr[String](Literal(Constant("^/api/" + names.simpleName.toLowerCase + "/(.+)")))
regexes += ValDef(Modifiers(), newTermName(names.fieldName), TypeTree(), reify(regexExpr.splice.r).tree)
})
val constructorBody = Block(List(Apply(Select(Super(This(EMPTY), EMPTY), CONSTRUCTOR), List())), Literal(Constant(())))
val constructor = DefDef(Modifiers(), CONSTRUCTOR, List(), List(List()), TypeTree(), constructorBody)
val objInheritance = List(Ident(newTypeName("AnyRef")))
val obj = ModuleDef(Modifiers(), newTermName("REGEXES"), Template(objInheritance, emptyValDef, List(constructor) ++ regexes))
val block = Block(List(obj), Ident(newTermName("REGEXES")))
println(show(block))
c.Expr[Any](block)
}

This can then be used by declaring val regexesUsingMacro = restapi.Generator.macroPaths in our unit test.

But wait - there’s a problem, isn’t there? That function is returning an object of type Any, so all our other generated code will know about it is that it’s an any - it won’t know anything about the vals we’ve added to it. Well actually, it turns out this isn’t a problem, but is intended that the return type should know more than its declaration specifies, if possible, as Eugene Burmako explains here.

Putting it all together

Now that we’ve got a macro that can be used as a val definition, we need to find that val and use it in our match expression. To find the val is simple enough - we just look for a ValDef that uses a Select for our function as the right hand side. However, if the user hasn’t defined such a val, we can’t continue - we need to tell the developer what they’ve done wrong. The macro Context includes functions to provide compiler warnings and errors, so we need to emit an error advising the developer how to fix it. The structure we end up with is as follows:

def pathsValueFinder(t : Tree) = t match {
case ValDef(_, _, _, s: Select) => "restapi.Generator.macroPaths" == s.toString
case _ => false
}
val regexesField = c.enclosingRun.currentUnit.body.find(pathsValueFinder)
regexesField match {
case Some(v: ValDef) => {
// Use value in match block here
}
case None => c.abort(c.enclosingPosition, "Could not find a supporting value. Have you got a value that equals restapi.Generator.macroPaths?")
}

When integrated with the code we had for the match block from part 1, we end up with:

def macroCase(s: String): Option[String] = macro macroCaseImpl
def macroCaseImpl(c: Context)(s: c.Expr[String]): c.Expr[Option[String]] = {
val helper = new Helper[c.type](c)
import c.universe._
def pathsValueFinder(t : Tree) = t match {
case ValDef(_, _, _, s: Select) => "restapi.Generator.macroPaths" == s.toString
case _ => false
}
val regexesField = c.enclosingRun.currentUnit.body.find(pathsValueFinder)
regexesField match {
case Some(v: ValDef) => {
val cases = ListBuffer[CaseDef]()
helper.foreachClass(names => {
cases += CaseDef(
Apply(Select(Ident(newTermName(v.name.decoded)), names.fieldName), List(Bind(newTermName("id"), Ident(nme.WILDCARD)))),
EmptyTree,
Apply(Select(Ident("Some"), newTermName("apply")), List(Apply(Select(Literal(Constant(names.simpleName + " with ID ")), newTermName("$plus")), List(Ident(newTermName("id")))))))
})
cases += CaseDef(Ident(nme.WILDCARD), EmptyTree, Ident("None"))
val block = Match(s.tree, cases.toList)
println(show(block))
c.Expr[Option[String]](block)
}
case None => c.abort(c.enclosingPosition, "Could not find a supporting value. Have you got a value that equals restapi.Generator.macroPaths?")
}
}

So now we’ve got our pattern matching working well, in the next article we can start calling an API to produce our endpoints.