Scala marcos: Calculator example

Preparation: Add function support to calculator

 1 trait CalculatorFunction {
 2   def name: String
 3 
 4   def call(parameters: Seq[Int]): Int
 5 }
 6 
 7 class CalculatorContext {
 8 ...
 9   private val functions = mutable.Map.empty[String, CalculatorFunction]
10 
11   def getFunction(name: String): Option[CalculatorFunction] = functions.get(name)
12 
13   def registerFunction(calculatorFunction: CalculatorFunction) = functions.put(calculatorFunction.name, calculatorFunction)
14 }
1 case class CallFunctionExpr(name: String, parameters: List[Expr]) extends Expr {
2   override def eval(implicit context: CalculatorContext) = context.getFunction(name).map {
3     function =>
4       function.call(parameters.map(_.eval))
5   }.getOrElse {
6     throw new RuntimeException(s"Function $name not defined")
7   }
8 }
1 object ASTParser extends StdTokenParsers {
2 ...
3   def term: Parser[Expr] = "(" ~> expr <~ ")" | "-" ~> expr ^^ NegExpr |
4     ident ~ "(" ~ repsep(expr, ",") ~ ")" ^^ {
5       case name ~ _ ~ parameters ~ _ => CallFunctionExpr(name, parameters)
6     } | 
7     ident ^^ VarGetExpr | 
8     numericLit ^^ (str => LiteralExpr(str.toInt))
9 ...

Step 1: Very first macro that is actually compiling

 1 case class AdaptedFunction() extends StaticAnnotation
 2 
 3 object FunctionAdapter {
 4   def generateCalculatorFunctions[T]: Seq[CalculatorFunction] = macro generateCalculatorFunctions_impl[T]
 5 
 6   def generateCalculatorFunctions_impl[T: c.WeakTypeTag](c: Context): c.Expr[Seq[CalculatorFunction]] = {
 7     import c.universe._
 8 
 9     reify {
10       Seq()
11     }
12   }
13 }

Step 2: Try to figure out how to fill a Seq dynamically

 1 object FunctionAdapter {
 2   def generateCalculatorFunctions[T]: Seq[CalculatorFunction] = macro generateCalculatorFunctions_impl[T]
 3 
 4   def generateCalculatorFunctions_impl[T: c.WeakTypeTag](c: Context): c.Expr[Seq[CalculatorFunction]] = {
 5     import c.universe._
 6 
 7     def createFunction(): c.Expr[CalculatorFunction] = {
 8 
 9       reify {
10         new CalculatorFunction {
11           override def name = ???
12 
13           override def call(parameters: Seq[Int]) = ???
14         }
15       }
16     }
17 
18     val func1 = createFunction()
19     val func2 = createFunction()
20 
21     reify {
22       Seq(func1.splice, func2.splice)
23     }
24   }
25 }
scalac: Seq.apply({
  final class $anon extends CalculatorFunction {
    def <init>() = {
      super.<init>();
      ()
    };
    override def name = Predef.$qmark$qmark$qmark;
    override def call(parameters: `package`.Seq[Int]) = Predef.$qmark$qmark$qmark
  };
  new $anon()
}, {
  final class $anon extends CalculatorFunction {
    def <init>() = {
      super.<init>();
      ()
    };
    override def name = Predef.$qmark$qmark$qmark;
    override def call(parameters: `package`.Seq[Int]) = Predef.$qmark$qmark$qmark
  };
  new $anon()
})
Apply(Select(Ident(scala.collection.Seq), newTermName("apply")), List(Block(List(ClassDef(Modifiers(FINAL), newTypeName("$anon"), List(), Template(List(Ident(de.leanovate.bedcom.examples.astbase.context.CalculatorFunction)), emptyValDef, List(DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), List())), Literal(Constant(())))), DefDef(Modifiers(OVERRIDE), newTermName("name"), List(), List(), TypeTree(), Select(Ident(scala.Predef), newTermName("$qmark$qmark$qmark"))), DefDef(Modifiers(OVERRIDE), newTermName("call"), List(), List(List(ValDef(Modifiers(PARAM), newTermName("parameters"), AppliedTypeTree(Select(Ident(scala.package), newTypeName("Seq")), List(Ident(scala.Int))), EmptyTree))), TypeTree(), Select(Ident(scala.Predef), newTermName("$qmark$qmark$qmark"))))))), Apply(Select(New(Ident(newTypeName("$anon"))), nme.CONSTRUCTOR), List())), Block(List(ClassDef(Modifiers(FINAL), newTypeName("$anon"), List(), Template(List(Ident(de.leanovate.bedcom.examples.astbase.context.CalculatorFunction)), emptyValDef, List(DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), List())), Literal(Constant(())))), DefDef(Modifiers(OVERRIDE), newTermName("name"), List(), List(), TypeTree(), Select(Ident(scala.Predef), newTermName("$qmark$qmark$qmark"))), DefDef(Modifiers(OVERRIDE), newTermName("call"), List(), List(List(ValDef(Modifiers(PARAM), newTermName("parameters"), AppliedTypeTree(Select(Ident(scala.package), newTypeName("Seq")), List(Ident(scala.Int))), EmptyTree))), TypeTree(), Select(Ident(scala.Predef), newTermName("$qmark$qmark$qmark"))))))), Apply(Select(New(Ident(newTypeName("$anon"))), nme.CONSTRUCTOR), List()))))

Step 3: Guess, try, guess again, try again …

 1 object FunctionAdapter {
 2   def generateCalculatorFunctions[T]: Seq[CalculatorFunction] = macro generateCalculatorFunctions_impl[T]
 3 
 4   def generateCalculatorFunctions_impl[T: c.WeakTypeTag](c: Context): c.Expr[Seq[CalculatorFunction]] = {
 5     import c.universe._
 6 
 7     def createFunction(): c.Expr[CalculatorFunction] = {
 8 
 9       reify {
10         new CalculatorFunction {
11           override def name = ???
12 
13           override def call(parameters: Seq[Int]) = ???
14         }
15       }
16     }
17 
18     val funcs = List(createFunction(), createFunction())
19     c.Expr[Seq[CalculatorFunction]](Apply(Select(Ident(newTermName("Seq")), newTermName("apply")), funcs.map(_.tree)))
20   }
21 }
 1 object FunctionAdapter {
 2   def generateCalculatorFunctions[T]: Seq[CalculatorFunction] = macro generateCalculatorFunctions_impl[T]
 3 
 4   def generateCalculatorFunctions_impl[T: c.WeakTypeTag](c: Context): c.Expr[Seq[CalculatorFunction]] = {
 5     import c.universe._
 6 
 7     val companioned = weakTypeOf[T].typeSymbol
 8     val companionSymbol = companioned.companionSymbol
 9 
10     def createFunction(method: MethodSymbol): c.Expr[CalculatorFunction] = {
11 
12       val nameExpr = c.literal(method.name.encoded)
13       reify {
14         new CalculatorFunction {
15           override def name = nameExpr.splice
16 
17           override def call(parameters: Seq[Int]) = ???
18         }
19       }
20     }
21 
22     val funcs = companioned.typeSignature.members.filter {
23       member =>
24         member.isMethod && member.annotations.exists(_.tpe == typeOf[AdaptedFunction])
25     }.map {
26       member =>
27         createFunction(member.asMethod)
28     }.toList
29     c.Expr[Seq[CalculatorFunction]](Apply(Select(Ident(newTermName("Seq")), newTermName("apply")), funcs.map(_.tree)))
30   }
31 }
 1     def createFunction(method: MethodSymbol): c.Expr[CalculatorFunction] = {
 2 
 3       val nameExpr = c.literal(method.name.encoded)
 4 
 5       if (method.returnType != definitions.IntTpe) {
 6         c.abort(method.pos, "Method has to return an Int")
 7       }
 8       method.paramss.flatten.foreach {
 9         param =>
10           if (param.typeSignature != definitions.IntTpe) {
11             c.abort(param.pos, "Only Int parameters are supported")
12           }
13       }
14 
15       reify {
16         new CalculatorFunction {
17           override def name = nameExpr.splice
18 
19           override def call(parameters: Seq[Int]) = {
20 
21             ???
22           }
23         }
24       }
25     }
 1     def createFunction(method: MethodSymbol): c.Expr[CalculatorFunction] = {
 2 
 3       val nameExpr = c.literal(method.name.encoded)
 4 
 5       if (method.returnType != definitions.IntTpe) {
 6         c.abort(method.pos, "Method has to return an Int")
 7       }
 8       val parameterNames = method.paramss.flatten.map {
 9         param =>
10           if (param.typeSignature != definitions.IntTpe) {
11             c.abort(param.pos, "Only Int parameters are supported")
12           }
13           param.name.toTermName
14       }
15 
16       val parameterItName = newTermName("parameterIt")
17       val parameterItDecl = ValDef(Modifiers(), parameterItName, TypeTree(),
18                                     Select(Ident(newTermName("parameters")), newTermName("iterator")))
19       val parameterItNext = Apply(Select(Ident(parameterItName), newTermName("next")), Nil)
20       val decls = parameterNames.map {
21         parameterName =>
22           ValDef(Modifiers(), parameterName, TypeTree(), parameterItNext)
23 
24       }
25 
26       val call = Apply(Select(Ident(companionSymbol), method.name.toTermName), parameterNames.map(Ident(_)))
27       val callImpl = c.Expr[Int](Block(parameterItDecl :: decls, call))
28       reify {
29         new CalculatorFunction {
30           override def name = nameExpr.splice
31 
32           override def call(parameters: Seq[Int]) = callImpl.splice
33         }
34       }
35     }
scalac: Seq.apply({
  final class $anon extends CalculatorFunction {
    def <init>() = {
      super.<init>();
      ()
    };
    override def name = "sum2";
    override def call(parameters: `package`.Seq[Int]) = {
      val parameterIt = parameters.iterator;
      val a = parameterIt.next();
      val b = parameterIt.next();
      BuildinFunctions.sum2(a, b)
    }
  };
  new $anon()
})
Apply(Select(Ident(newTermName("Seq")), newTermName("apply")), List(Block(List(ClassDef(Modifiers(FINAL), newTypeName("$anon"), List(), Template(List(Ident(de.leanovate.bedcom.examples.astbase.context.CalculatorFunction)), emptyValDef, List(DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), List())), Literal(Constant(())))), DefDef(Modifiers(OVERRIDE), newTermName("name"), List(), List(), TypeTree(), Literal(Constant("sum2"))), DefDef(Modifiers(OVERRIDE), newTermName("call"), List(), List(List(ValDef(Modifiers(PARAM), newTermName("parameters"), AppliedTypeTree(Select(Ident(scala.package), newTypeName("Seq")), List(Ident(scala.Int))), EmptyTree))), TypeTree(), Block(List(ValDef(Modifiers(), newTermName("parameterIt"), TypeTree(), Select(Ident(newTermName("parameters")), newTermName("iterator"))), ValDef(Modifiers(), newTermName("a"), TypeTree(), Apply(Select(Ident(newTermName("parameterIt")), newTermName("next")), List())), ValDef(Modifiers(), newTermName("b"), TypeTree(), Apply(Select(Ident(newTermName("parameterIt")), newTermName("next")), List()))), Apply(Select(Ident(de.leanovate.bedcom.examples.astexample.BuildinFunctions), newTermName("sum2")), List(Ident(newTermName("a")), Ident(newTermName("b")))))))))), Apply(Select(New(Ident(newTypeName("$anon"))), nme.CONSTRUCTOR), List()))))