Slide 1

Slide 1 text

Domain-specific names: an application of Scala macros Anthony M. Sloane Programming Languages Research Group Department of Computing Macquarie University [email protected] [email protected] @inkytonik April 11, 2013

Slide 2

Slide 2 text

Simple expression language abstract syntax abstract class Exp case class Num (i : Int) extends Exp case class Var (s : String) extends Exp case class Add (l : Exp , r : Exp) extends Exp case class Mul (l : Exp , r : Exp) extends Exp Add (Num (1), Mul (Num (2), Var ("i")))

Slide 3

Slide 3 text

Domain objects: Kiama attributes val isconst : Exp => Boolean = attr { case Num (_) => true case Var (_) => false case Add (l, r) => isconst (l) && isconst (r) case Mul (l, r) => isconst (l) && isconst (r) }

Slide 4

Slide 4 text

Domain objects: Kiama rewriting strategies def rule (f : Any ==> Any) : Strategy = new Strategy { ... } val mulbytwoToAdd = rule { case Mul (Num (2), x) => Add (x, x) } val simplifiers = mulbytwoToAdd <+ mulbyzeroToZero val simplify = everywherebu (simplifiers)

Slide 5

Slide 5 text

Domain object names: error messages val badattr : Exp => Int = attr { case n : Num => 1 + badattr (n) } badattr (Num (3)) Cycle detected in attribute evaluation ’badattr ’ at Num (3)

Slide 6

Slide 6 text

Domain object names: profiling By strategy for StratEval: Total Total Count Count ms % % 21 30.5 1 0.1 obfuscateProgram 20 28.1 150 13.4 all 20 28.0 148 13.2 topdown 12 17.3 1 0.1 obfuscateUses 10 14.5 150 13.4 attempt 10 14.1 1 0.1 obfuscateDecls 3 5.5 75 6.7 <+ 1 1.9 71 6.3 obfuscateNormalUse 1 1.6 75 6.7 obfuscateDecl 0 0.8 127 11.3 id 0 0.5 75 6.7 preservePredefinedUse

Slide 7

Slide 7 text

User-specified names val mulbytwoToAdd = rule (" mulbytwoToAdd") { case Mul (Num (2), x) => Add (x, x) } val isconst : Exp => Boolean = attr ("isconst") { case Num (_) => true case Var (_) => false case Add (l, r) => isconst (l) && isconst (r) case Mul (l, r) => isconst (l) && isconst (r) } But now we are specifying the names twice!

Slide 8

Slide 8 text

Pass the name to the builder method using a macro def rule (f : Any ==> Any) : Strategy = new Strategy { ... } becomes def rule (f : Any ==> Any) : Strategy = macro RewriterCoreMacros .ruleMacro def rule (name : String , f : Any ==> Any) : Strategy = new Strategy (name) { ... }

Slide 9

Slide 9 text

dsname library def makeCallWithName [T] (c : Context , methodName : String) : c.Expr[T] c is the macro Context methodname is the name of the method to call “foobar”: call a method in the caller’s scope “MyObj.foobar”: call a method of some object “this.foobar”: call a method of the receiver

Slide 10

Slide 10 text

Strategy builder macro: rule def ruleMacro (c : Context) (f : c.Expr[Any ==> Any]) : c.Expr[Strategy] = makeCallWithName (c, "rule") means that val mulbytwoToAdd = rule { ... } becomes val mulbytwoToAdd = rule ("mulbytwoToAdd", { ... })

Slide 11

Slide 11 text

Strategy builder: <+ abstract class Strategy { def <+ (q : Strategy) : Strategy = macro RewriterCoreMacros .detchoiceMacro def <+ (name : String , q : => Strategy) : Strategy = ... }

Slide 12

Slide 12 text

Strategy builder macro: <+ def detchoiceMacro (c : Context) (q : c.Expr[Strategy ]) : c.Expr[Strategy] = makeCallWithName (c, "this.<+") means that val simplifiers = mulbytwoToAdd <+ mulbyzeroToZero becomes val simplifiers = mulbytwoToAdd <+ ("simplifiers", mulbyzeroToZero )

Slide 13

Slide 13 text

makeCallWithName: finding the name Get the method that encloses the macro invocation. Search the method body for a value definition whose right-hand side is the macro invocation and return the name from that definition. Otherwise, if the invocation computes the value of the method, return the name of the method. Otherwise, return the name of the macro. If there is no enclosing method, get the enclosing template (class, trait or object). Search the template body for a value definition whose right-hand side is the macro invocation. Otherwise, report an error.

Slide 14

Slide 14 text

makeCallWithName Simple implementation: about 90 lines of code. The macro invocation is detected by source code position. Detects zero to two argument lists including type application. It’s easy to extend to more but the current version of dsname uses custom patterns to match. We are trying to generalise. We still have issues with detecting values declared inside closures. . . Non-local returns from methods probably don’t work either. . .

Slide 15

Slide 15 text

Future work Default method name will be the macro name Support for passing other data such as the source position More control over how the data will be passed, not just as the first argument of the first argument list

Slide 16

Slide 16 text

Further information dsname library: https://bitbucket.org/inkytonik/dsname Kiama: https://kiama.googlecode.com Scala docs: http://docs.scala-lang.org Reflection: overviews/reflection/overview.html Macros: overviews/macros/overview.html