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 }
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()))))