treehugger.scala 

treehugger.scala is a library to write Scala source code programmatically. It’s also an implementation of Scala abstract syntax tree based on Reflection API.

Setup 

libraryDependencies += "com.eed3si9n" %% "treehugger" % "0.4.3"

resolvers += Resolver.sonatypeRepo("public")

I heard you like code, so… 

treehugger lets you treat Scala code as data (case classes), and data as code (treehugger DSL) so you can code while you code.

For example, here’s treehugger DSL for Hello World:

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> val tree: Tree = Predef_println APPLY LIT("Hello, world!")
[1m[34mtree[0m: [1m[32mtreehugger.forest.Tree[0m = Apply(Ident(println),List(Literal(Constant(Hello, world!))))

scala> treeToString(tree)
[1m[34mres0[0m: [1m[32mString[0m = println("Hello, world!")

The result of the tree shows the structure, and treeToString(tree) shows the Scala source code represented by the tree. Because it is responsible for formatting of the String, your code generating code would be more clear using treehugger.

Classy example 

Let’s omit the import statements, and println statements from here. Here’s how treehugger DSL defines classes:

scala> :paste
// Entering paste mode (ctrl-D to finish)
object sym {
  val IntQueue = RootClass.newClass("IntQueue")
  val BasicIntQueue = RootClass.newClass("BasicIntQueue")
  val buf = BasicIntQueue.newValue("buf")
}

val tree1 = CLASSDEF(sym.IntQueue) withFlags(Flags.ABSTRACT) := BLOCK(
  DEF("get", IntClass),
  PROC("put") withParams(PARAM("x", IntClass))
)

val tree2 = CLASSDEF(sym.BasicIntQueue) withParents(sym.IntQueue) := BLOCK(
  VAL(sym.buf) withFlags(Flags.PRIVATE) :=
    NEW(ArrayBufferClass TYPE_OF IntClass),
  DEF("get", IntClass) := REF(sym.buf) DOT "remove" APPLY(),
  PROC("put") withParams(PARAM("x", IntClass)) := BLOCK(
    REF(sym.buf) INFIX("+=") APPLY REF("x")
  )
)

// Exiting paste mode, now interpreting.

defined object sym
[1m[34mtree1[0m: [1m[32mtreehugger.forest.ClassDef[0m = ClassDef(Modifiers(abstract, , Map()),Modifiers(, , Map()),IntQueue,List(),List(),Template(List(),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List(DefDef(Modifiers(, , Map()),get,List(),List(),TypeTree(),EmptyTree), ProcDef(Modifiers(, , Map()),put,List(),List(List(ValDef(Modifiers(<param>, , Map()),Typed(Ident(x),TypeTree()),EmptyTree))),EmptyTree))))
[1m[34mtree2[0m: [1m[32mtreehugger.forest.ClassDef[0m = ClassDef(Modifiers(, , Map()),Modifiers(, , Map()),BasicIntQueue,List(),List(),Template(List(TypeTree()),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List(ValDef(Modifiers(private, , Map()),Ident(buf),New(TypeTree())), DefDef(Modifiers(, , Map()),get,List(),List(),TypeTree(),Ap...

Let’s print them out:

scala> treeToString(tree1)
[1m[34mres1[0m: [1m[32mString[0m =
abstract class IntQueue {
  def get: Int
  def put(x: Int)
}

scala> treeToString(tree2)
[1m[34mres3[0m: [1m[32mString[0m =
class BasicIntQueue extends IntQueue {
  private val buf = new scala.collection.mutable.ArrayBuffer[Int]
  def get: Int = buf.remove()
  def put(x: Int) {
    buf += x
  }
}

Notice some of the symbols were defined upfront so we could avoid typing in string names.

Thanks scalac! 

To be clear where the credit is due, the majority of treehugger’s code is borrowed from the Scala compiler (scalac) written by Martin Odersky, Paul Phillips, and others. treehugger just took it further to fit its needs.

Compilers 101 

Let’s go over the basic compiler theory, so we have some kind of foundation even if it’s overly simplified. A compiler is a program that translates a source code in a language to another, often machine code such as Java bytecode.

The compilers have been written so many times that there are names to each phases of a typical compiler.

Scan 

Scanning phase, or Lexical Analysis, is responsible for throwing out the white space and comments, and recognizing literals, identifiers, and keywords as tokens. At this point, there is no structural check, except for making sure that the comments and string quotations match up.

Parse 

Parsing phase, or Syntactic Analysis, is responsible for constructing an abstract syntax tree (AST) from the linear sequence of tokens according to the rules of the grammar of the language.

Semantic Analysis 

Typing phase, or Semantic Analysis, is responsible for adding the symbol table to the syntax tree, by associating variables and references with their definitions, and performing type checking.

Backend 

Analysis and Optimization phase are specific to the language implementation. Some of the common optimizations are inlining and dead code elimination.

Finally, during Code Generation phase the target language is generated either from AST or from an intermediate structure.

Turning the table 

Because our end goal is to get Scala source code, we reverse the flow of scalac to generate the code starting from AST.

Scala 2.10 adds Reflection API, which allows the user peak into AST for a given code. It also includes code that turns AST into Scala source code. All we need now is to generate an AST.

As part of scalac, there’s also a trait called TreeDSL, which can describe AST in code-like way. With both combined seems as though we have everything we need.

Unfortunately that is not the case. Because scalac is focused towards Java bytecode generation, the parser expands syntax sugars such as for loops and infix method applications. In other words, AST contains one of map, flatMap, foreach method in place of a for loop. TreeDSL too is missing a few things, for example, an ability to define classes or objects.

treehugger forks scalac’s code base and extends the AST and TreeDSL so all legal Scala code can be described using the DSL (the only exception is XML literals).

Symbols, Types, and Trees 

Having the scalac lineage comes with the baggage of having to deal with its data structures.

Symbol 

A Symbol represents an entry in the symbol table, such as values, classes, and type aliases. A compiler would eventually bind all identifiers to a symbol during Typing phase. However, since we are interested in generating the code, the use of symbols are often optional in treehugger.

For example, a unit method declaration may be defined as:

DEF(sym.get)

or:

DEF("get")

Both would yield the same source code at the end. There are several places where the use of symbols are recommended.

First, use a built-in symbol if there is one available. Built-in symbols are defined under treehugger.forest.definitions.

Second, consider defining a symbol for repeated reference to a class or a method. A new symbol may be defined off of an existing symbol as follows:

scala> object sym {
  val BasicIntQueue = RootClass.newClass("BasicIntQueue")
  val buf = BasicIntQueue.newValue("buf")  
  val A = ArrowAssocClass.newTypeParameter("A")
  val arrow = ArrowAssocClass.newMethod("->")
  val B = arrow.newTypeParameter("B")
  val T = BasicIntQueue.newAliasType("T")
}
defined object sym

Defining symbols for every identifiers would double the size of the code, and would likely make the experience of writing it cumbersome.

Type 

A Type represents a Scala type. Symbols of class ClassSymbols and TypeSymbols can automatically be promoted to a Type and many of the built-in symbols represent built-in classes.

VAL("foo", IntClass)

In the above code, IntClass is a symbol, but it is automatically promoted to a Type. Similarly, a String can also be promoted to a Type.

VAL("foo", "Int")

Types can also be created using type-level expressions and built-in type constructors:

TYPE_ARRAY(StringClass)
TYPE_REF(REF("board") DOT "Coord")

Tree 

A Tree represents a node in a Scala AST. It could be a simple expression such as a literal, or a combination of other trees.

An expression in treehugger DSL eventually evaluates to a Tree value.

treehugger DSL 

treehugger DSL is an expanded version of TreeDSL in scalac to build Scala AST in code-like fashion. It is able to write all legal Scala expressions except for XML literals.

Conventions used in examples 

There are several conventions used throughout this document.

VAL(sym|"x")

In the above, sym|"x" denotes that either a Symbol or a String is accepted as a parameter to VAL(...).

TYPE_LIST(typ)

In the above, typ indicates that TYPE_LIST(...) accepts a Type.

DEF("x"|sym) withTypeParams(TYPEVAR(...), ...)

In the above, TYPEVAR(...), ... denotes that withTypeParams(...) can accept either a vararg list or an Iterable of TYPEVAR(...).

(DEF("get"|sym, [typ])
  [withParams(PARAM("x"|sym, typ|"C")[ := arg], ...)]*
  [withTypeParams(TYPEVAR(...), ...)]).tree

In the above, [typ] indicates that typ is optional; and [withParams(PARAM("x"|sym, typ|"C")[ := arg], ...)]* indicates that the withParams clause is optional and may be repeated.

Forest 

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> val tree: Tree = Predef_println APPLY LIT("Hello, world!")
[1m[34mtree[0m: [1m[32mtreehugger.forest.Tree[0m = Apply(Ident(println),List(Literal(Constant(Hello, world!))))

scala> treeToString(tree)
[1m[34mres0[0m: [1m[32mString[0m = println("Hello, world!")

The entire treehugger system is bundled up as treehugger.Forest class. The package object for treehugger defines an instance of Forest called forest for convenience. Under the forest, definitions object defines built-in symbols and treehuggerDSL object defines the DSL.

In the above code, object sym defines optional symbols. By wrapping in sym we can avoid conflicting with the real println function. Then, the line defining val tree: Tree is an example of treehugger DSL.

Finally, forest defines treeToString method to convert AST into a String:

def treeToString(args: Any*): String

treesToString takes a vararg of Any, and pretty prints Tree as Scala source code and everything else using toString with a new line in between.

Literals and Comments 

We will begin looking into treehugger DSL from the literals and comments since they are the simplest forms of Tree. Also having these allows us to gradually intoroduce more complex forms off of others.

Literals 

Literals are the basic foundation of treehugger DSL. Numeric literals, String, and symbols are written by wrapping a Scala literal with LIT():

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> LIT(1)     // Int
[1m[34mres0[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant(1))

scala> LIT(1L)    // Long
[1m[34mres1[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant(1))

scala> LIT(1.23)  // Double
[1m[34mres2[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant(1.23))

scala> LIT(1.23F) // Float
[1m[34mres3[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant(1.23))

scala> LIT('H')   // Char
[1m[34mres4[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant(H))

scala> LIT("H")   // String
[1m[34mres5[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant(H))

scala> LIT('Sym)  // scala.Symbol
[1m[34mres6[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant('Sym))

Boolean literals, (), and null are written as follows:

scala> TRUE       // true
[1m[34mres7[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant(true))

scala> FALSE      // false
[1m[34mres8[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant(false))

scala> NULL       // null
[1m[34mres9[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant(null))

scala> UNIT       // ()
[1m[34mres10[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant(()))

Comments 

Single line comments 

Single line comments are added to an arbitrary tree as follows:

tree withComment("comments here", ...)

For example,

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> val tree = LIT(2) withComment("comments are useful",
  "only if they provide more info than the code")
[1m[34mtree[0m: [1m[32mtreehugger.forest.Commented[0m = Commented(Modifiers(, , Map()),List(comments are useful, only if they provide more info than the code),Literal(Constant(2)))

scala> treeToString(tree)
[1m[34mres0[0m: [1m[32mString[0m =
// comments are useful
// only if they provide more info than the code
2

Scaladoc style comments 

Scaladoc style comments are written using withDoc as follows:

tree withDoc("comments" | doctag, ...)

where doctag is a doctag defined as DocTag.See(IntClass), DocTag.Author("Somebody"), etc.

For example,

scala> val tree2 = (DEF("x") := LIT(0)) withDoc(
  "does something",
  DocTag.See(IntClass))
[1m[34mtree2[0m: [1m[32mtreehugger.forest.Commented[0m = Commented(Modifiers(protected, , Map()),List(does something, @see scala.Int),DefDef(Modifiers(, , Map()),x,List(),List(),TypeTree(),Literal(Constant(0))))

scala> treeToString(tree2)
[1m[34mres1[0m: [1m[32mString[0m =
/**
 * does something
 * @see scala.Int
 */
def x = 0

Basic Declarations and Definitions 

A definition, such as a function defnition, introduces names that are bound to some expression, block, or a type.

def foo: Int = 1

On the other hand, an abstract declaration introduces only the names and their types. It can be a part of an abstract class definition.

def foo: Int

Values 

Abstract value declarations 

Abstract value declarations are written by wrapping the name and the type with VAL(...):

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> val tree = (VAL("foo", IntClass): Tree)
[1m[34mtree[0m: [1m[32mtreehugger.forest.Tree[0m = ValDef(Modifiers(, , Map()),Typed(Ident(foo),TypeTree()),EmptyTree)

scala> treeToString(tree)
[1m[34mres0[0m: [1m[32mString[0m = val foo: Int

or in general:

VAL(sym|"x", typ|"C").tree

where sym is a symbol and typ is a type of the value. In the above code, sym|"x" denotes that either a symbol or String is accepted as the name of the value, and type|"C" denotes that either a type or String is accepted as the type of the value.

Calling tree method creates a Tree without a right-hand side (rhs) expression. Because there’s an implicit conversion, forcing VAL(...) as a Tree automatically calls tree method.

Value definitions 

Value definitions are written by appending right-hand side tree after := as follows:

scala> val tree2 = VAL("foo", IntClass) := LIT(0)
[1m[34mtree2[0m: [1m[32mtreehugger.forest.ValDef[0m = ValDef(Modifiers(, , Map()),Typed(Ident(foo),TypeTree()),Literal(Constant(0)))

scala> treeToString(tree2)
[1m[34mres1[0m: [1m[32mString[0m = val foo: Int = 0

Like Scala, the type annotation can be omitted when rhs is provided:

scala> val tree3 = VAL("foo") := LIT(0)
[1m[34mtree3[0m: [1m[32mtreehugger.forest.ValDef[0m = ValDef(Modifiers(, , Map()),Ident(foo),Literal(Constant(0)))

scala> treeToString(tree3)
[1m[34mres2[0m: [1m[32mString[0m = val foo = 0

In addition, a symbol could be used to define a value instead of using String names.

scala> object sym {
  val foo = RootClass.newValue("foo")
}
defined object sym

scala> val tree4 = VAL(sym.foo) := LIT(0)
[1m[34mtree4[0m: [1m[32mtreehugger.forest.ValDef[0m = ValDef(Modifiers(, , Map()),Ident(foo),Literal(Constant(0)))

scala> treeToString(tree4)
[1m[34mres3[0m: [1m[32mString[0m = val foo = 0

For a larger code base, using symbols makes the code more readable.

The general form of constant value definitions are:

VAL(sym|"x", [typ]) := rhs

Lazy values 

Lazy value declarations are written using LAZYVAL(...):

scala> val tree5 = (LAZYVAL("foo", IntClass): Tree)
[1m[34mtree5[0m: [1m[32mtreehugger.forest.Tree[0m = ValDef(Modifiers(lazy, , Map()),Typed(Ident(foo),TypeTree()),EmptyTree)

scala> treeToString(tree5)
[1m[34mres4[0m: [1m[32mString[0m = lazy val foo: Int

and lazy value definitions are written as:

scala> val tree6 = LAZYVAL("foo", IntClass) := LIT(0)
[1m[34mtree6[0m: [1m[32mtreehugger.forest.ValDef[0m = ValDef(Modifiers(lazy, , Map()),Typed(Ident(foo),TypeTree()),Literal(Constant(0)))

scala> treeToString(tree6)
[1m[34mres5[0m: [1m[32mString[0m = lazy val foo: Int = 0

Final values 

Final value definitions are written by appending withFlags(...) after VAL(...):

scala> val tree7 = VAL("foo", IntClass) withFlags(Flags.FINAL) := LIT(0)
[1m[34mtree7[0m: [1m[32mtreehugger.forest.ValDef[0m = ValDef(Modifiers(final, , Map()),Typed(Ident(foo),TypeTree()),Literal(Constant(0)))

scala> treeToString(tree7)
[1m[34mres6[0m: [1m[32mString[0m = final val foo: Int = 0

Pattern values 

There is another form of value definitions called pattern values, but it will be discussed later.

Variables 

Abstract variable declarations 

Abstract variable declarations are written using VAR(...):

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> val tree = (VAR("foo", IntClass): Tree)
[1m[34mtree[0m: [1m[32mtreehugger.forest.Tree[0m = ValDef(Modifiers(<mutable>, , Map()),Typed(Ident(foo),TypeTree()),EmptyTree)

scala> treeToString(tree)
[1m[34mres0[0m: [1m[32mString[0m = var foo: Int

or in general:

VAR(sym|"x", typ|"C").tree

Variable definitions 

Value definitions are written as:

scala> val tree2 = VAR("foo", IntClass) := LIT(0)
[1m[34mtree2[0m: [1m[32mtreehugger.forest.ValDef[0m = ValDef(Modifiers(<mutable>, , Map()),Typed(Ident(foo),TypeTree()),Literal(Constant(0)))

scala> treeToString(tree2)
[1m[34mres1[0m: [1m[32mString[0m = var foo: Int = 0

as a special form of initializing the variable with the default value of the type, WILDCARD can be used as follows:

scala> val tree3 = VAR("foo", IntClass) := WILDCARD
[1m[34mtree3[0m: [1m[32mtreehugger.forest.ValDef[0m = ValDef(Modifiers(<mutable>, , Map()),Typed(Ident(foo),TypeTree()),Ident(_))

scala> treeToString(tree3)
[1m[34mres2[0m: [1m[32mString[0m = var foo: Int = _

Type members 

Abstract type declarations 

Abstract type declarations are written using TYPEVAR(sym|"T"). Optionally, lower bounds and higher bounds can be specified:

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> val tree = (TYPEVAR("T"): Tree)
[1m[34mtree[0m: [1m[32mtreehugger.forest.Tree[0m = TypeDef(Modifiers(, , Map()),T,List(),EmptyTree)

scala> treeToString(tree)
[1m[34mres0[0m: [1m[32mString[0m = type T

scala> val tree2 = (TYPEVAR("T") LOWER(IntClass): Tree)
[1m[34mtree2[0m: [1m[32mtreehugger.forest.Tree[0m = TypeDef(Modifiers(, , Map()),T,List(),TypeTree())

scala> treeToString(tree2)
[1m[34mres1[0m: [1m[32mString[0m = type T >: Int

scala> object sym {
  val T = RootClass.newAliasType("T")
  val A = RootClass.newAliasType("A")
  val B = RootClass.newAliasType("B")
}
defined object sym

scala> val tree3 = (TYPEVAR(sym.T) UPPER(ComparableClass TYPE_OF sym.T): Tree)
[1m[34mtree3[0m: [1m[32mtreehugger.forest.Tree[0m = TypeDef(Modifiers(, , Map()),T,List(),TypeTree())

scala> treeToString(tree3)
[1m[34mres2[0m: [1m[32mString[0m = type T <: Comparable[T]

or in general:

(TYPEVAR(sym|"T") [LOWER(lo|"C")] [UPPER(hi|"D")]).tree

where lo denotes an optional lower bound type and hi upper bound type.

Type alias definitions 

Type alias definitions are written by appending := and right-hand side Type. The alias may accompany type parameters given by withTypeParams(TYPEVAR(...), ...):

scala> val tree4 = TYPEVAR("IntList") := TYPE_LIST(IntClass)
[1m[34mtree4[0m: [1m[32mtreehugger.forest.TypeDef[0m = TypeDef(Modifiers(, , Map()),IntList,List(),TypeTree())

scala> treeToString(tree4)
[1m[34mres3[0m: [1m[32mString[0m = type IntList = List[Int]

scala> val tree5 = TYPEVAR("Two") withTypeParams(TYPEVAR(sym.A)) := TYPE_TUPLE(sym.A, sym.A)
[1m[34mtree5[0m: [1m[32mtreehugger.forest.TypeDef[0m = TypeDef(Modifiers(, , Map()),Two,List(TypeDef(Modifiers(, , Map()),A,List(),EmptyTree)),TypeTree())

scala> treeToString(tree5)
[1m[34mres4[0m: [1m[32mString[0m = type Two[A] = (A, A)

or in general:

TYPEVAR(sym|"T") [withTypeParams(TYPEVAR(typ1), ...)] := typ2

Variance annotations 

Variance annotations for the type parameters are set using COVARIANT(...) and CONTRAVARIANT(...):

scala> val A = RootClass.newTypeParameter("A")
[1m[34mA[0m: [1m[32mtreehugger.forest.TypeSymbol[0m = A

scala> val tree6 = (TYPEVAR("M") withTypeParams(TYPEVAR(COVARIANT(A))): Tree)
[1m[34mtree6[0m: [1m[32mtreehugger.forest.Tree[0m = TypeDef(Modifiers(, , Map()),M,List(TypeDef(Modifiers(, , Map()),A,List(),EmptyTree)),EmptyTree)

scala> val tree7 = (TYPEVAR("M") withTypeParams(TYPEVAR(CONTRAVARIANT(A))): Tree)
[1m[34mtree7[0m: [1m[32mtreehugger.forest.Tree[0m = TypeDef(Modifiers(, , Map()),M,List(TypeDef(Modifiers(, , Map()),A,List(),EmptyTree)),EmptyTree)

The above examples print as:

scala> treeToString(tree6)
[1m[34mres5[0m: [1m[32mString[0m = type M[+A]

scala> treeToString(tree7)
[1m[34mres6[0m: [1m[32mString[0m = type M[-A]

Functions 

Abstract function declarations 

Abstract function declarations are written using DEF(...) or PROC(...). Optionally, parameter lists can be specified using withParams(PARAM(...), ...) and type parameters can be specified using withTypeParams(TYPEVAR(...), ...):

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> val tree = DEF("get", IntClass).tree
[1m[34mtree[0m: [1m[32mtreehugger.forest.DefDef[0m = DefDef(Modifiers(, , Map()),get,List(),List(),TypeTree(),EmptyTree)

scala> val tree2 = (DEF("sideEffect", UnitClass) withParams()).tree
[1m[34mtree2[0m: [1m[32mtreehugger.forest.DefDef[0m = DefDef(Modifiers(, , Map()),sideEffect,List(),List(List()),TypeTree(),EmptyTree)

scala> val tree3 = (PROC("put") withParams(PARAM("x", IntClass))).tree
[1m[34mtree3[0m: [1m[32mtreehugger.forest.ProcDef[0m = ProcDef(Modifiers(, , Map()),put,List(),List(List(ValDef(Modifiers(<param>, , Map()),Typed(Ident(x),TypeTree()),EmptyTree))),EmptyTree)

scala> object sym {
  val A = RootClass.newAliasType("A")
  val B = RootClass.newAliasType("B")
}
defined object sym

scala> val tree4 = (DEF("compare", BooleanClass)
  withTypeParams(TYPEVAR(sym.A))
  withParams(PARAM("a", sym.A) := LIT(0))
  withParams(PARAM("b", sym.A) := LIT(0)): Tree)
[1m[34mtree4[0m: [1m[32mtreehugger.forest.Tree[0m = DefDef(Modifiers(, , Map()),compare,List(TypeDef(Modifiers(, , Map()),A,List(),EmptyTree)),List(List(ValDef(Modifiers(<param>, , Map()),Typed(Ident(a),TypeTree()),Literal(Constant(0)))), List(ValDef(Modifiers(<param>, , Map()),Typed(Ident(b),TypeTree()),Literal(Constant(0))))),TypeTree(),EmptyTree)

The above examples print as:

scala> treeToString(tree)
[1m[34mres0[0m: [1m[32mString[0m = def get: Int

scala> treeToString(tree2)
[1m[34mres1[0m: [1m[32mString[0m = def sideEffect(): Unit

scala> treeToString(tree3)
[1m[34mres2[0m: [1m[32mString[0m = def put(x: Int)

scala> treeToString(tree4)
[1m[34mres3[0m: [1m[32mString[0m = def compare[A](a: A = 0)(b: A = 0): Boolean

In genral, for functions with return type:

(DEF("get"|sym, typ)
  [withParams(PARAM("x"|sym, typ|"C")[ := arg], ...)]*
  [withTypeParams(TYPEVAR(...), ...)]).tree

For procedures:

(PROC("get"|sym)
  [withParams(PARAM("x"|sym, typ|"C")[ := arg], ...)]*
  [withTypeParams(TYPEVAR(...), ...)]).tree

Function definition 

Function definitions are written by appending right-hand side tree after := as follows:

scala> val tree5 = DEF("get", IntClass) := LIT(0)
[1m[34mtree5[0m: [1m[32mtreehugger.forest.DefDef[0m = DefDef(Modifiers(, , Map()),get,List(),List(),TypeTree(),Literal(Constant(0)))

scala> treeToString(tree5)
[1m[34mres4[0m: [1m[32mString[0m = def get: Int = 0

The result type can be omitted using DEF(...) as follows:

scala> val tree6 = DEF("get") := LIT(0)
[1m[34mtree6[0m: [1m[32mtreehugger.forest.DefDef[0m = DefDef(Modifiers(, , Map()),get,List(),List(),TypeTree(),Literal(Constant(0)))

scala> treeToString(tree6)
[1m[34mres5[0m: [1m[32mString[0m = def get = 0

When rhs is a BLOCK(tree, ...) you need to use DEFINFER(...) to differentiate from procedures (PROC was added in 0.4.0).

scala> val tree7 = DEFINFER("get") := BLOCK(LIT(0))
[1m[34mtree7[0m: [1m[32mtreehugger.forest.DefDef[0m = DefDef(Modifiers(, , Map()),get,List(),List(),TypeTree(),Block(List(),Literal(Constant(0))))

scala> treeToString(tree7)
[1m[34mres6[0m: [1m[32mString[0m =
def get = {
  0
}

Procedures 

Procedure definitions are written using PROC(...) with BLOCK(tree, ...) for the rhs:

scala> val tree8 = PROC("write") withParams(PARAM("str", StringClass)) := BLOCK(
  LIT(0)
)
[1m[34mtree8[0m: [1m[32mtreehugger.forest.ProcDef[0m = ProcDef(Modifiers(, , Map()),write,List(),List(List(ValDef(Modifiers(<param>, , Map()),Typed(Ident(str),TypeTree()),EmptyTree))),Block(List(),Literal(Constant(0))))

scala> treeToString(tree8)
[1m[34mres7[0m: [1m[32mString[0m =
def write(str: String) {
  0
}

Currying 

Note withParams(...) clause may be added multiple times, each forming a parameter list:

scala> val tree9 = (DEF("compare")
  withTypeParams(TYPEVAR(sym.A))
  withParams(PARAM("a", sym.A))
  withParams(PARAM("b", sym.A))) := FALSE
[1m[34mtree9[0m: [1m[32mtreehugger.forest.DefDef[0m = DefDef(Modifiers(, , Map()),compare,List(TypeDef(Modifiers(, , Map()),A,List(),EmptyTree)),List(List(ValDef(Modifiers(<param>, , Map()),Typed(Ident(a),TypeTree()),EmptyTree)), List(ValDef(Modifiers(<param>, , Map()),Typed(Ident(b),TypeTree()),EmptyTree))),TypeTree(),Literal(Constant(false)))

scala> treeToString(tree9)
[1m[34mres8[0m: [1m[32mString[0m = def compare[A](a: A)(b: A) = false

Default parameters 

Similar to VAL(...), PARAM(...) can be followed by := and rhs to specify the default argument:

scala> val tree10 = (DEF("compare")
  withTypeParams(TYPEVAR(sym.A))
  withParams(PARAM("a", sym.A) := LIT(0))
  withParams(PARAM("b", sym.A) := LIT(0))) := FALSE
[1m[34mtree10[0m: [1m[32mtreehugger.forest.DefDef[0m = DefDef(Modifiers(, , Map()),compare,List(TypeDef(Modifiers(, , Map()),A,List(),EmptyTree)),List(List(ValDef(Modifiers(<param>, , Map()),Typed(Ident(a),TypeTree()),Literal(Constant(0)))), List(ValDef(Modifiers(<param>, , Map()),Typed(Ident(b),TypeTree()),Literal(Constant(0))))),TypeTree(),Literal(Constant(false)))

scala> treeToString(tree10)
[1m[34mres9[0m: [1m[32mString[0m = def compare[A](a: A = 0)(b: A = 0) = false

By-name parameters 

By-name parameters are written using TYPE_BYNAME(typ) as follows:

scala> val tree11 = (PROC("whileLoop")
  withParams(PARAM("cond", TYPE_BYNAME(BooleanClass)))
  withParams(PARAM("stat", TYPE_BYNAME(UnitClass))): Tree)
[1m[34mtree11[0m: [1m[32mtreehugger.forest.Tree[0m = ProcDef(Modifiers(, , Map()),whileLoop,List(),List(List(ValDef(Modifiers(<param>, , Map()),Typed(Ident(cond),TypeTree()),EmptyTree)), List(ValDef(Modifiers(<param>, , Map()),Typed(Ident(stat),TypeTree()),EmptyTree))),EmptyTree)

scala> treeToString(tree11)
[1m[34mres10[0m: [1m[32mString[0m = def whileLoop(cond: => Boolean)(stat: => Unit)

Repeated parameters 

Repeated parameters are written using TYPE_*(typ) as follows:

scala> val tree12 = (DEF("sum", IntClass)
  withParams(PARAM("args", TYPE_*(IntClass))): Tree)
[1m[34mtree12[0m: [1m[32mtreehugger.forest.Tree[0m = DefDef(Modifiers(, , Map()),sum,List(),List(List(ValDef(Modifiers(<param>, , Map()),Typed(Ident(args),TypeTree()),EmptyTree))),TypeTree(),EmptyTree)

scala> treeToString(tree12)
[1m[34mres11[0m: [1m[32mString[0m = def sum(args: Int*): Int

Import Clauses 

Import clauses are written using IMPORT(...). Optionally, import selectors may be specified:

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> val tree  = IMPORT(MutablePackage)
[1m[34mtree[0m: [1m[32mtreehugger.forest.Import[0m = Import(Ident(mutable),List())

scala> val tree2 = IMPORT("scala.collection.mutable")
[1m[34mtree2[0m: [1m[32mtreehugger.forest.Import[0m = Import(Ident(mutable),List())

scala> val tree3 = IMPORT(MutablePackage, "_")
[1m[34mtree3[0m: [1m[32mtreehugger.forest.Import[0m = Import(Ident(mutable),List(ImportSelector(_,-1,_,-1)))

scala> val tree4 = IMPORT(MutablePackage, "Map", "Set")
[1m[34mtree4[0m: [1m[32mtreehugger.forest.Import[0m = Import(Ident(mutable),List(ImportSelector(Map,-1,Map,-1), ImportSelector(Set,-1,Set,-1)))

scala> val tree5 = IMPORT(MutablePackage, RENAME("Map") ==> "MutableMap")
[1m[34mtree5[0m: [1m[32mtreehugger.forest.Import[0m = Import(Ident(mutable),List(ImportSelector(Map,-1,MutableMap,-1)))

The above examples print as:

scala> treeToString(tree)
[1m[34mres0[0m: [1m[32mString[0m = import scala.collection.mutable

scala> treeToString(tree2)
[1m[34mres1[0m: [1m[32mString[0m = import scala.collection.mutable

scala> treeToString(tree3)
[1m[34mres2[0m: [1m[32mString[0m = import scala.collection.mutable._

scala> treeToString(tree4)
[1m[34mres3[0m: [1m[32mString[0m = import scala.collection.mutable.{Map, Set}

scala> treeToString(tree5)
[1m[34mres4[0m: [1m[32mString[0m = import scala.collection.mutable.{Map => MutableMap}

In general:

IMPORT(sym|"x.y.z", ["X" | RENAME("X") ==> "Y"]*)

The only odd thing is ==> operator used for RENAME(...). Because => is already taken by Scala, treehugger DSL uses ==> instead.

Classes and Objects 

Templates 

Classes and objects are both defined in terms of templates, whose body is represented using BLOCK(...) in treehugger DSL.

In general, treehugger DSL uses BLOCK(...) wherever curly braces ({}) appear in Scala. BLOCK(...) accepts vararg of trees, such as class member definitions and expressions:

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> object sym {
  val IntQueue: ClassSymbol = RootClass.newClass("IntQueue")  
}
defined object sym

scala> val tree = CLASSDEF(sym.IntQueue) withFlags(Flags.ABSTRACT) := BLOCK(
  DEF("get", IntClass),
  PROC("put") withParams(PARAM("x", IntClass))
)
[1m[34mtree[0m: [1m[32mtreehugger.forest.ClassDef[0m = ClassDef(Modifiers(abstract, , Map()),Modifiers(, , Map()),IntQueue,List(),List(),Template(List(),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List(DefDef(Modifiers(, , Map()),get,List(),List(),TypeTree(),EmptyTree), ProcDef(Modifiers(, , Map()),put,List(),List(List(ValDef(Modifiers(<param>, , Map()),Typed(Ident(x),TypeTree()),EmptyTree))),EmptyTree))))

This example prints as:

scala> treeToString(tree)
[1m[34mres0[0m: [1m[32mString[0m =
abstract class IntQueue {
  def get: Int
  def put(x: Int)
}

Classes 

Class definitions 

Class definitions are written using CLASSDEF(...):

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> val tree = (CLASSDEF("C"): Tree)
[1m[34mtree[0m: [1m[32mtreehugger.forest.Tree[0m = ClassDef(Modifiers(, , Map()),Modifiers(, , Map()),C,List(),List(),Template(List(),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List()))

scala> treeToString(tree)
[1m[34mres0[0m: [1m[32mString[0m = class C

scala> val tree2 = CLASSDEF("C") := BLOCK(
  VAL("x") := LIT(0)
)
[1m[34mtree2[0m: [1m[32mtreehugger.forest.ClassDef[0m = ClassDef(Modifiers(, , Map()),Modifiers(, , Map()),C,List(),List(),Template(List(),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List(ValDef(Modifiers(, , Map()),Ident(x),Literal(Constant(0))))))

scala> treeToString(tree2)
[1m[34mres1[0m: [1m[32mString[0m =
class C {
  val x = 0
}

The general form of the first example is:

CLASSDEF(sym|"C").empty

As with value and function names, CLASSDEF can accept either a symbol or a String.

Constructor parameters 

Primary constructor parameters are written using withParams(...) similar to function definitions. Except, it could use VAL(...) and VAR(...) in addition to PARAM(...):

scala> val tree3 = (CLASSDEF("C")
  withParams(PARAM("x", IntClass),
    VAL("y", StringClass),
    VAR("z", TYPE_LIST(StringClass))) := BLOCK(
  DEF("hi") := LIT("hi") 
))
[1m[34mtree3[0m: [1m[32mtreehugger.forest.ClassDef[0m = ClassDef(Modifiers(, , Map()),Modifiers(, , Map()),C,List(),List(ValDef(Modifiers(<param>, , Map()),Typed(Ident(x),TypeTree()),EmptyTree), ValDef(Modifiers(, , Map()),Typed(Ident(y),TypeTree()),EmptyTree), ValDef(Modifiers(<mutable>, , Map()),Typed(Ident(z),TypeTree()),EmptyTree)),Template(List(),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List(DefDef(Modifiers(, , Map()),hi,List(),List(),TypeTree(),Literal(Constant(hi))))))

scala> treeToString(tree3)
[1m[34mres2[0m: [1m[32mString[0m =
class C(x: Int, val y: String, var z: List[String]) {
  def hi = "hi"
}

Constructor definitions 

Auxiliary constructors are defined using DEFTHIS := BLOCK(stat, ...). Optionally, DEFTHIS may take withParams(...):

scala> val tree4 = (CLASSDEF("C")
  withParams(PARAM("s", StringClass)) := BLOCK(
  DEFTHIS withParams(PARAM("x", IntClass)) := BLOCK(
    THIS APPLY(REF("x") TOSTRING)
  )
))
[33mwarning: [0mthere was one feature warning; for details, enable `:setting -feature' or `:replay -feature'
[1m[34mtree4[0m: [1m[32mtreehugger.forest.ClassDef[0m = ClassDef(Modifiers(, , Map()),Modifiers(, , Map()),C,List(),List(ValDef(Modifiers(<param>, , Map()),Typed(Ident(s),TypeTree()),EmptyTree)),Template(List(),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List(DefDef(Modifiers(, , Map()),_$this,List(),List(List(ValDef(Modifiers(<param>, , Map()),Typed(Ident(x),TypeTree()),EmptyTree))),TypeTree(),Block(List(),Apply(This(),List(Select(Ident(x),toString))))))))

scala> treeToString(tree4)
[1m[34mres3[0m: [1m[32mString[0m =
class C(s: String) {
  def this(x: Int) = {
    this(x.toString)
  }
}

Extending classes 

To define classes by extending super classes use withParents(tree|typ|"T"):

scala> val tree5 = CLASSDEF("C") withParents("B") := BLOCK(
  DEF("x") := LIT(0)
)
[1m[34mtree5[0m: [1m[32mtreehugger.forest.ClassDef[0m = ClassDef(Modifiers(, , Map()),Modifiers(, , Map()),C,List(),List(),Template(List(TypeTree()),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List(DefDef(Modifiers(, , Map()),x,List(),List(),TypeTree(),Literal(Constant(0))))))

scala> treeToString(tree5)
[1m[34mres4[0m: [1m[32mString[0m =
class C extends B {
  def x = 0
}

Self type annotations 

To define self type annotations use withSelf(sym|"self", [typ1, ...]):

scala> val tree6 = CLASSDEF("C") withSelf("self", "T1", "T2") := BLOCK(
  VAL("x") := REF("self")
)
[1m[34mtree6[0m: [1m[32mtreehugger.forest.ClassDef[0m = ClassDef(Modifiers(, , Map()),Modifiers(, , Map()),C,List(),List(),Template(List(),ValDef(Modifiers(, , Map()),Typed(Ident(self),TypeTree()),EmptyTree),List(ValDef(Modifiers(, , Map()),Ident(x),Ident(self)))))

scala> treeToString(tree6)
[1m[34mres5[0m: [1m[32mString[0m =
class C { self: T1 with T2 =>
  val x = self
}

Early denifitions 

To define field values before supertype constructor is called add early definitions using withEarlyDefs(tree, ...):

scala> val tree7 = CLASSDEF("C") withEarlyDefs(
  VAL("name") := LIT("Bob")
) withParents("B") := BLOCK(
  LIT(0)
)
[1m[34mtree7[0m: [1m[32mtreehugger.forest.ClassDef[0m = ClassDef(Modifiers(, , Map()),Modifiers(, , Map()),C,List(),List(),Template(List(Block(List(),ValDef(Modifiers(, , Map()),Ident(name),Literal(Constant(Bob)))), TypeTree()),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List(Literal(Constant(0)))))

scala> treeToString(tree7)
[1m[34mres6[0m: [1m[32mString[0m =
class C extends {
  val name = "Bob"
} with B {
  0
}

Modifiers 

Many of the Tree objects has Modifier field, which adds extra attribute about the tree such as access level and mutability. For both classes and their members modifier flags can be given using withFlags(...), which takes a PRIVATEWITHIN or vararg of Long.

Access modifiers 

To define class members with access modifiers use Flags.PRIVATE, Flags.PROTECTED, and PRIVATEWITHIN("scope"):

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> val tree = CLASSDEF("C") := BLOCK(
  DEF("x") withFlags(Flags.PRIVATE) := LIT(0),
  DEF("y") withFlags(Flags.PROTECTED) := LIT(0),
  DEF("z") withFlags(PRIVATEWITHIN("this")) := LIT(0) 
)
[1m[34mtree[0m: [1m[32mtreehugger.forest.ClassDef[0m = ClassDef(Modifiers(, , Map()),Modifiers(, , Map()),C,List(),List(),Template(List(),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List(DefDef(Modifiers(private, , Map()),x,List(),List(),TypeTree(),Literal(Constant(0))), DefDef(Modifiers(protected, , Map()),y,List(),List(),TypeTree(),Literal(Constant(0))), DefDef(Modifiers(private[this], , Map()),z,List(),List(),TypeTree(),Literal(Constant(0))))))

scala> treeToString(tree)
[1m[34mres0[0m: [1m[32mString[0m =
class C {
  private def x = 0
  protected def y = 0
  private[this] def z = 0
}

Overriding 

To override class members use Flags.OVERRIDE:

scala> val tree2 = CLASSDEF("C") withParents("B") := BLOCK(
  DEF("x") withFlags(Flags.OVERRIDE) := LIT(0)
)
[1m[34mtree2[0m: [1m[32mtreehugger.forest.ClassDef[0m = ClassDef(Modifiers(, , Map()),Modifiers(, , Map()),C,List(),List(),Template(List(TypeTree()),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List(DefDef(Modifiers(override, , Map()),x,List(),List(),TypeTree(),Literal(Constant(0))))))

scala> treeToString(tree2)
[1m[34mres1[0m: [1m[32mString[0m =
class C extends B {
  override def x = 0
}

Final members 

To prohibit overriding by subclasses class members are marked final using Flags.FINAL:

scala> val tree3 = CLASSDEF("C") := BLOCK(
  DEF("x") withFlags(Flags.FINAL) := LIT(0)
)
[1m[34mtree3[0m: [1m[32mtreehugger.forest.ClassDef[0m = ClassDef(Modifiers(, , Map()),Modifiers(, , Map()),C,List(),List(),Template(List(),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List(DefDef(Modifiers(final, , Map()),x,List(),List(),TypeTree(),Literal(Constant(0))))))

scala> treeToString(tree3)
[1m[34mres2[0m: [1m[32mString[0m =
class C {
  final def x = 0
}

Abstract classes 

To define abstract classes use Flags.ABSTRACT on CLASSDEF(...):

scala> object sym {
  val IntQueue: ClassSymbol = RootClass.newClass("IntQueue")  
}
defined object sym

scala> val tree4 = CLASSDEF(sym.IntQueue) withFlags(Flags.ABSTRACT) := BLOCK(
  DEF("get", IntClass),
  PROC("put") withParams(PARAM("x", IntClass))
)
[1m[34mtree4[0m: [1m[32mtreehugger.forest.ClassDef[0m = ClassDef(Modifiers(abstract, , Map()),Modifiers(, , Map()),IntQueue,List(),List(),Template(List(),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List(DefDef(Modifiers(, , Map()),get,List(),List(),TypeTree(),EmptyTree), ProcDef(Modifiers(, , Map()),put,List(),List(List(ValDef(Modifiers(<param>, , Map()),Typed(Ident(x),TypeTree()),EmptyTree))),EmptyTree))))

scala> treeToString(tree4)
[1m[34mres3[0m: [1m[32mString[0m =
abstract class IntQueue {
  def get: Int
  def put(x: Int)
}

Final classes 

To define final classes, which prohibits extension, use Flags.FINAL on CLASSDEF(...):

scala> val tree5 = (CLASSDEF("C") withFlags(Flags.FINAL): Tree)
[1m[34mtree5[0m: [1m[32mtreehugger.forest.Tree[0m = ClassDef(Modifiers(final, , Map()),Modifiers(, , Map()),C,List(),List(),Template(List(),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List()))

scala> treeToString(tree5)
[1m[34mres4[0m: [1m[32mString[0m = final class C

Sealed classes 

To define sealed classes use Flags.SEALED on CLASSDEF(...):

scala> val tree6 = CLASSDEF("Animal") withFlags(Flags.ABSTRACT, Flags.SEALED)
[1m[34mtree6[0m: [1m[32mtreehugger.forest.treehuggerDSL.ClassDefStart[0m = treehugger.TreehuggerDSLs$treehuggerDSL$ClassDefStart@428d18c6

scala> treeToString(tree6)
[1m[34mres5[0m: [1m[32mString[0m = treehugger.TreehuggerDSLs$treehuggerDSL$ClassDefStart@428d18c6

Private constructors 

To define private constructors use Flags.PRIVATE with withCtorFlags(...) on CLASSDEF(...):

scala> val tree7 = (CLASSDEF("C") withCtorFlags(Flags.PRIVATE)
  withParams(PARAM("x", IntClass)): Tree)
[1m[34mtree7[0m: [1m[32mtreehugger.forest.Tree[0m = ClassDef(Modifiers(, , Map()),Modifiers(private, , Map()),C,List(),List(ValDef(Modifiers(<param>, , Map()),Typed(Ident(x),TypeTree()),EmptyTree)),Template(List(),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List()))

scala> treeToString(tree7)
[1m[34mres6[0m: [1m[32mString[0m = class C private (x: Int)

Polymorphic Classes 

Polymorphic classes are defined using withTypeParams(...):

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> val tree = (CLASSDEF("Queue") withTypeParams(TYPEVAR("A"))
  withParams(VAL("leading", "A"), VAL("trailing", "A")): Tree)
[1m[34mtree[0m: [1m[32mtreehugger.forest.Tree[0m = ClassDef(Modifiers(, , Map()),Modifiers(, , Map()),Queue,List(TypeDef(Modifiers(, , Map()),A,List(),EmptyTree)),List(ValDef(Modifiers(, , Map()),Typed(Ident(leading),TypeTree()),EmptyTree), ValDef(Modifiers(, , Map()),Typed(Ident(trailing),TypeTree()),EmptyTree)),Template(List(),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List()))

scala> treeToString(tree)
[1m[34mres0[0m: [1m[32mString[0m = class Queue[A](val leading: A, val trailing: A)

Case Classes 

Case classes are defined using CASECLASSDEF(...):

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> object sym {
  val Expr = RootClass.newClass("Expr")
  val Var  = RootClass.newClass("Var")
}
defined object sym

scala> val tree = (CLASSDEF(sym.Expr) withFlags(Flags.SEALED, Flags.ABSTRACT): Tree)
[1m[34mtree[0m: [1m[32mtreehugger.forest.Tree[0m = ClassDef(Modifiers(sealed abstract, , Map()),Modifiers(, , Map()),Expr,List(),List(),Template(List(),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List()))

scala> treeToString(tree)
[1m[34mres0[0m: [1m[32mString[0m = sealed abstract class Expr

scala> val tree2 = (CASECLASSDEF(sym.Var)
  withParams(PARAM("name", StringClass)) withParents(sym.Expr): Tree)
[1m[34mtree2[0m: [1m[32mtreehugger.forest.Tree[0m = ClassDef(Modifiers(case, , Map()),Modifiers(, , Map()),Var,List(),List(ValDef(Modifiers(<param>, , Map()),Typed(Ident(name),TypeTree()),EmptyTree)),Template(List(TypeTree()),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List()))

scala> treeToString(tree2)
[1m[34mres1[0m: [1m[32mString[0m = case class Var(name: String) extends Expr

Traits 

Traits are defined using TRAITDEF(...):

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> val tree = TRAITDEF("Philosophical") := BLOCK(
  PROC("philosophize") := BLOCK(
    LIT(0)
  ) 
)
[1m[34mtree[0m: [1m[32mtreehugger.forest.ClassDef[0m = ClassDef(Modifiers(<defaultparam/trait>, , Map()),Modifiers(, , Map()),Philosophical,List(),List(),Template(List(),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List(ProcDef(Modifiers(, , Map()),philosophize,List(),List(),Block(List(),Literal(Constant(0)))))))

scala> val tree2 = (CLASSDEF("Animal"): Tree)
[1m[34mtree2[0m: [1m[32mtreehugger.forest.Tree[0m = ClassDef(Modifiers(, , Map()),Modifiers(, , Map()),Animal,List(),List(),Template(List(),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List()))

scala> val tree3 = (TRAITDEF("HasLegs"): Tree)
[1m[34mtree3[0m: [1m[32mtreehugger.forest.Tree[0m = ClassDef(Modifiers(<defaultparam/trait>, , Map()),Modifiers(, , Map()),HasLegs,List(),List(),Template(List(),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List()))

scala> val tree4 = (CLASSDEF("Frog")
    withParents("Animal", "HasLegs", "Philosophical") := BLOCK(
  DEF(Any_toString) withFlags(Flags.OVERRIDE) := LIT("green")
))
[1m[34mtree4[0m: [1m[32mtreehugger.forest.ClassDef[0m = ClassDef(Modifiers(, , Map()),Modifiers(, , Map()),Frog,List(),List(),Template(List(TypeTree(), TypeTree(), TypeTree()),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List(DefDef(Modifiers(override <method>, , Map()),toString,List(),List(),TypeTree(),Literal(Constant(green))))))

These print as:

scala> treeToString(tree)
[1m[34mres0[0m: [1m[32mString[0m =
trait Philosophical {
  def philosophize {
    0
  }
}

scala> treeToString(tree2)
[1m[34mres1[0m: [1m[32mString[0m = class Animal

scala> treeToString(tree3)
[1m[34mres2[0m: [1m[32mString[0m = trait HasLegs

scala> treeToString(tree4)
[1m[34mres3[0m: [1m[32mString[0m =
class Frog extends Animal with HasLegs with Philosophical {
  override def toString = "green"
}

Object Definitions 

Object definitions are written using OBJECTDEF(...):

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> val tree = OBJECTDEF("Main") withParents("App") := BLOCK(
  LIT(0)
)
[1m[34mtree[0m: [1m[32mtreehugger.forest.ModuleDef[0m = ModuleDef(Modifiers(, , Map()),Main,Template(List(TypeTree()),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List(Literal(Constant(0)))))

scala> treeToString(tree)
[1m[34mres0[0m: [1m[32mString[0m =
object Main extends App {
  0
}

Case object definitions 

Case object definitions are written using CASEOBJECTDEF(...):

scala> val tree2 = (CASEOBJECTDEF("C"): Tree)
[1m[34mtree2[0m: [1m[32mtreehugger.forest.Tree[0m = ModuleDef(Modifiers(case, , Map()),C,Template(List(),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List()))

scala> treeToString(tree2)
[1m[34mres1[0m: [1m[32mString[0m = case object C

Expressions 

Now that we’ve covered classes and objects, we should look into expressions in depth.

Basic Expressions 

Literals 

As we’ve seen earlier, literals are written as follows:

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> LIT(1)                       // 1
[1m[34mres0[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant(1))

scala> LIT(1L)                      // 1L
[1m[34mres1[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant(1))

scala> LIT(1.23)                    // 1.23
[1m[34mres2[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant(1.23))

scala> LIT(1.23F)                   // 1.23F
[1m[34mres3[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant(1.23))

scala> LIT('H')                     // 'H'
[1m[34mres4[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant(H))

scala> LIT("H")                     // "H"
[1m[34mres5[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant(H))

scala> LIT('Sym)                    // 'Sym
[1m[34mres6[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant('Sym))

scala> TRUE                         // true
[1m[34mres7[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant(true))

scala> FALSE                        // false
[1m[34mres8[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant(false))

scala> NULL                         // null
[1m[34mres9[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant(null))

scala> UNIT                         // ()
[1m[34mres10[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant(()))

Simple Names 

Simple names are written using REF(sym|"x") to refer to values and methods that immediately available in the current scope:

scala> object sym {
  val x = RootClass.newValue("x")
  val y = RootClass.newValue("y")
  val Address = RootClass.newClass("Address")
}
defined object sym

scala> REF("x")                     // x
[1m[34mres11[0m: [1m[32mtreehugger.forest.Ident[0m = Ident(x)

scala> REF(sym.x)                   // x
[1m[34mres12[0m: [1m[32mtreehugger.forest.Tree[0m = Ident(x)

Selection 

To refer to other values and methods, selections are written by calling DOT(sym|"y") either on a symbol or on a REF(sym|"x"). This returns an intermediate structure that can turn into a Tree by calling tree method or by implicit conversion:

scala> (sym.x DOT sym.y).tree       // x.y
[1m[34mres13[0m: [1m[32mtreehugger.forest.Select[0m = Select(Ident(x),y)

scala> (sym.x DOT "y": Tree)        // x.y
[1m[34mres14[0m: [1m[32mtreehugger.forest.Tree[0m = Select(Ident(x),y)

scala> (REF("x") DOT "y": Tree)     // x.y
[1m[34mres15[0m: [1m[32mtreehugger.forest.Tree[0m = Select(Ident(x),y)

scala> val tree = (REF("x") DOT "y").tree
[1m[34mtree[0m: [1m[32mtreehugger.forest.Select[0m = Select(Ident(x),y)

scala> treeToString(tree)
[1m[34mres16[0m: [1m[32mString[0m = x.y

This 

References to this are written using THIS or THIS(sym|"C"):

scala> THIS                         // this
[1m[34mres17[0m: [1m[32mtreehugger.forest.This[0m = This()

scala> val tree2 = THIS(sym.Address)
[1m[34mtree2[0m: [1m[32mtreehugger.forest.Tree[0m = This(Address)

scala> treeToString(tree2)
[1m[34mres18[0m: [1m[32mString[0m = Address.this

Super 

References to super are written using SUPER or SUPER(sym|"C"). This also returns an intermediate structure that can turn into a Tree by calling tree method or via implicit conversion:

scala> SUPER.tree                   // super
[1m[34mres19[0m: [1m[32mtreehugger.forest.Super[0m = Super(EmptyTree,)

scala> (SUPER("C"): Tree)           // C.super
[1m[34mres20[0m: [1m[32mtreehugger.forest.Tree[0m = Super(This(C),)

To add type parameter to super, call APPLYTYPE(sym|"T"):

scala> val tree3 = SUPER APPLYTYPE "T"
[1m[34mtree3[0m: [1m[32mtreehugger.forest.Super[0m = Super(EmptyTree,T)

scala> treeToString(tree3)
[1m[34mres21[0m: [1m[32mString[0m = super[T]

Function Applications 

There are two ways to write function applications. The first way is calling APPLY(arg, ...) on a symbol or a tree. Here, arg denotes a Tree:

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> val tree = Predef_println APPLY LIT("Hello, world!")
[1m[34mtree[0m: [1m[32mtreehugger.forest.Apply[0m = Apply(Ident(println),List(Literal(Constant(Hello, world!))))

scala> treeToString(tree)
[1m[34mres0[0m: [1m[32mString[0m = println("Hello, world!")

scala> val tree2 = (REF("x") DOT "y") APPLY (LIT(0), LIT(1))
[1m[34mtree2[0m: [1m[32mtreehugger.forest.Apply[0m = Apply(Select(Ident(x),y),List(Literal(Constant(0)), Literal(Constant(1))))

scala> treeToString(tree2)
[1m[34mres1[0m: [1m[32mString[0m = x.y(0, 1)

Function application on selections 

The second way is to apply arg, ... on intermediate structure returned by DOT(sym|"y"):

scala> val tree3 = (REF("x") DOT "y")(LIT(0), LIT(1))
[1m[34mtree3[0m: [1m[32mtreehugger.forest.Apply[0m = Apply(Select(Ident(x),y),List(Literal(Constant(0)), Literal(Constant(1))))

scala> treeToString(tree3)
[1m[34mres2[0m: [1m[32mString[0m = x.y(0, 1)

Sequence arguments 

To pass sequence into a vararg parameter, use SEQARG(arg):

scala> val tree4 = THIS APPLY (SEQARG(REF("list")))
[1m[34mtree4[0m: [1m[32mtreehugger.forest.Apply[0m = Apply(This(),List(Typed(Ident(list),Ident(_*))))

scala> treeToString(tree4)
[1m[34mres3[0m: [1m[32mString[0m = this((list: _*))

Named arguments 

To pass named arguments into a function, specify the parameter using REF(sym|"x") as follows:

scala> val tree5 = REF("put") APPLY (REF("x") := LIT(0))
[1m[34mtree5[0m: [1m[32mtreehugger.forest.Apply[0m = Apply(Ident(put),List(Assign(Ident(x),Literal(Constant(0)))))

scala> treeToString(tree5)
[1m[34mres4[0m: [1m[32mString[0m = put(x = 0)

Partially applied functions 

Partially applied functions are written by calling APPLY with PARTIALLY:

scala> val tree6 = REF("put") APPLY PARTIALLY
[1m[34mtree6[0m: [1m[32mtreehugger.forest.Apply[0m = Apply(Ident(put),List(Ident(<partially>)))

scala> treeToString(tree6)
[1m[34mres5[0m: [1m[32mString[0m = put _

Note this is different from APPLYing WILDCARD since PARTIALLY applies to the entire parameter list.

Type Applications 

Type applications are written by calling APPLYTYPE(typ|"C", ...) on a symbol or a tree:

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> val tree = REF("put") APPLYTYPE(IntClass) APPLY(LIT(0))
[1m[34mtree[0m: [1m[32mtreehugger.forest.Apply[0m = Apply(TypeApply(Ident(put),List(TypeTree())),List(Literal(Constant(0))))

scala> treeToString(tree)
[1m[34mres0[0m: [1m[32mString[0m = put[Int](0)

Tuples and Parentheses 

There are three ways to write tuple expressions. The most general form is TUPLE(tree, ...):

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> TUPLE()                      // ()
[1m[34mres0[0m: [1m[32mtreehugger.forest.Tree[0m = Literal(Constant(()))

scala> TUPLE(REF("x"))              // (x)
[1m[34mres1[0m: [1m[32mtreehugger.forest.Tree[0m = Apply(Ident(Tuple1),List(Ident(x)))

scala> TUPLE(LIT(0), LIT(1))        // (0, 1)
[1m[34mres2[0m: [1m[32mtreehugger.forest.Tree[0m = Apply(Ident(Tuple2),List(Literal(Constant(0)), Literal(Constant(1))))

The second way is to use UNIT literal:

scala> UNIT                         // ()
[1m[34mres3[0m: [1m[32mtreehugger.forest.Literal[0m = Literal(Constant(()))

Finally, PAREN(tree, ...) can also be used to write a tuple expression:

scala> PAREN(REF("x"))              // (x)
[1m[34mres4[0m: [1m[32mtreehugger.forest.Tree[0m = Apply(Ident(Tuple1),List(Ident(x)))

Semantically speaking the actual scala.Tuplen are formed only when two or more arguments are passed, but as a syntactic expression, PAREN is just an alias to TUPLE.

Instance Creation Expressions 

Constructor invocations 

Instance creations are written using NEW(path|typ|"C"):

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> object sym {
  val A = RootClass.newClass("A")
}
defined object sym

scala> NEW(sym.A)                   // new A
[1m[34mres0[0m: [1m[32mtreehugger.forest.Tree[0m = New(TypeTree())

scala> NEW("C")                     // new C
[1m[34mres1[0m: [1m[32mtreehugger.forest.Tree[0m = New(TypeTree())

scala> NEW(REF("B") DOT "C")        // new B.C
[1m[34mres2[0m: [1m[32mtreehugger.forest.Tree[0m = New(Select(Ident(B),C))

Optionally, arguments may be passed into the constructor using NEW(path|typ|"C", arg, ...):

scala> val tree = NEW("C", LIT(0), LIT(1))
[1m[34mtree[0m: [1m[32mtreehugger.forest.Tree[0m = Apply(Select(New(TypeTree()),<init>),List(Literal(Constant(0)), Literal(Constant(1))))

scala> treeToString(tree)
[1m[34mres3[0m: [1m[32mString[0m = new C(0, 1)

Anonymous classes 

Anonymous classes are created by passing ANONDEF(parent|"C", ...) into NEW(...):

scala> val tree2 = NEW(ANONDEF() := BLOCK(
  DEF("name") := LIT("Robert")
))
[1m[34mtree2[0m: [1m[32mtreehugger.forest.Tree[0m = New(ClassDef(Modifiers(, , Map()),Modifiers(, , Map()),$anon,List(),List(),Template(List(),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List(DefDef(Modifiers(, , Map()),name,List(),List(),TypeTree(),Literal(Constant(Robert)))))))

scala> val tree3 = NEW(ANONDEF("C") := BLOCK(
  DEF("name") := LIT("Robert")
))
[1m[34mtree3[0m: [1m[32mtreehugger.forest.Tree[0m = New(ClassDef(Modifiers(, , Map()),Modifiers(, , Map()),$anon,List(),List(),Template(List(TypeTree()),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List(DefDef(Modifiers(, , Map()),name,List(),List(),TypeTree(),Literal(Constant(Robert)))))))

scala> val tree4 = NEW(ANONDEF("C") withEarlyDefs(
  VAL("name") := LIT("Robert")
))
[1m[34mtree4[0m: [1m[32mtreehugger.forest.Tree[0m = New(ClassDef(Modifiers(, , Map()),Modifiers(, , Map()),$anon,List(),List(),Template(List(Block(List(),ValDef(Modifiers(, , Map()),Ident(name),Literal(Constant(Robert)))), TypeTree()),ValDef(Modifiers(private, , Map()),Ident(_),EmptyTree),List())))

These examples print as:

scala> treeToString(tree2)
[1m[34mres4[0m: [1m[32mString[0m =
new {
  def name = "Robert"
}

scala> treeToString(tree3)
[1m[34mres5[0m: [1m[32mString[0m =
new C {
  def name = "Robert"
}

scala> treeToString(tree4)
[1m[34mres6[0m: [1m[32mString[0m =
new {
  val name = "Robert"
} with C

Blocks 

Blocks are written using BLOCK(tree, ...):

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> val tree = BLOCK(
  VAL("x") := LIT(0),
  REF("x")
)
[1m[34mtree[0m: [1m[32mtreehugger.forest.Block[0m = Block(List(ValDef(Modifiers(, , Map()),Ident(x),Literal(Constant(0)))),Ident(x))

scala> treeToString(tree)
[1m[34mres0[0m: [1m[32mString[0m =
{
  val x = 0
  x
}

Prefix, Infix, and Postfix Operations 

Expressions can be constructed from operands and operators.

Prefix operations 

Prefix operations are written using one of PLUS(tree), MINUS(tree), NOT(tree), or TILDE(tree):

PLUS(LIT(1))                 // +(1)
MINUS(LIT(1))                // -(1)
NOT(FALSE)                   // !(false)
TILDE(LIT(1))                // ~(1)

Postfix operations 

Postfix operations are written by calling POSFIX(sym|"x") on a tree or a symbol:

LIT(1) POSTFIX(Any_toString) // 1 toString

Infix operations 

Infix operations are written using INFIX(sym|"op") as follows:

LIT(1) INFIX("+") APPLY(LIT(2))

This prints as:

1 + 2

Alternatively, INFIX(sym|"op", arg, ...) can be called with righ-hand side arguments:

LIT(1) INFIX("+", LIT(2))    // 1 + 2

For commonly used operations treehugger DSL defines some built-in operators, which will be covered later:

LIT(1) INT_+ LIT(2)          // 1 + 2

Infix chaining 

treehugger DSL provides a way to chain infix operations using INFIX_CHAIN(sym|"op", tree, ...):

INFIX_CHAIN("+", LIT(1), LIT(2), LIT(3))

INFIX_CHAIN(Any_==, LIT(1) :: LIT(2) :: LIT(3) :: Nil)

The above print as:

1 + 2 + 3

1 == 2 == 3

Types, Annotations, and Assignments 

Typed expressions 

Typed expressions are written using withType(typ|"C"):

LIT(0) withType(LongClass)   // (0: Long)

Annotated expressions 

Annotated expressions are written using withAnnots(annot, ...):

REF("e") withAnnots(ANNOT(UncheckedClass))

This prints as:

(e: @unchecked)

Annotations are covered later in details.

Assignments 

Assignments are written using :=:

REF("x") := LIT(0)           // x = 0 

If Expressions 

If expressions are written using IF(...) THEN(...) ELSE(...)|ENDIF as follows:

(IF (REF("x") ANY_== REF("y")) THEN REF("x")
ELSE LIT(0))

IF (REF("sunny")) THEN (Predef_println APPLY LIT("Hi!")) ENDIF

These examples print as:

if (x == y) x
else 0

if (sunny) println("Hi!")

While loops 

While loops are written using WHILE(...) DO BLOCK(...):

WHILE(TRUE) DO BLOCK(
  Predef_println APPLY LIT("Hello")
)

This prints as:

```scala while (true) { println(“Hello”) }

Do loops 

Do loops are written by calling DO_WHILE(...) on a block:

BLOCK(
  Predef_println APPLY LIT("Hello")
) DO_WHILE(TRUE)

This prints as:

do {
  println("Hello")
} while (true)

For expressions 

For expressions are written using FOR(enum, ...) DO tree as follows:

FOR(VALFROM("i") := LIT(0) INT_TO LIT(2)) DO (
  Predef_println APPLY LIT("Hello"))

FOR(
  VALFROM("i") := LIT(0) INT_TO LIT(2),
  IF(REF("x") INT_< LIT(10))
) DO (Predef_println APPLY LIT("Hello"))

FOR(
  VALFROM("i") := LIT(0) INT_TO LIT(2),
  VAL("x") := REF("i")
) DO BLOCK(
  Predef_println APPLY LIT("Hello")
)

These examples print as:

for (i <- 1 to 2)
  println("Hello")

for {
  i <- 0 to 2
  if x < 10
} println("Hello")

for {
  i <- 0 to 2
  x = i
} {
  println("Hello")
}

FOR(...) takes vararg of enumerators which are constructed using VALFROM(sym|"x") := tree, IF(tree), or VAL(sym|"x") := tree.

For comprehension 

For comprehensions are written using FOR(enum, ...) YEILD tree:

FOR(VALFROM("i") := LIT(0) INT_TO LIT(2)) YIELD (
  REF("i"))

This prints as:

for (i <- 0 to 2)
  yield i

Return Expressions and Exception Handling 

Return expression 

Return exressions are written using RETURN(tree):

RETURN(LIT(0))               // return 0

Throwing exceptions 

Exceptions are thrown using THROW(tree) or THROW(sym|c, [tree|"message"]):

THROW(REF("x"))
THROW(IllegalArgumentExceptionClass)
THROW(IllegalArgumentExceptionClass, "oh no")
THROW(IllegalArgumentExceptionClass, REF("x"))

These examples print as:

throw x
throw new IllegalArgumentException()
throw new IllegalArgumentException("oh no")
throw new IllegalArgumentException(x.toString)

Catching exceptions 

Exceptions are caught using TRY(stat, ...) CATCH(CASE(pat), ...) [ENDTRY|FINALLY(...)]. CASE(...) accepts a pattern matching expression, which is shown briefly here, and covered in detail later:

(TRY (REF("something") APPLY LIT(0))
CATCH (
  CASE(WILDCARD) ==> (Predef_println APPLY LIT("error"))
) ENDTRY)

(TRY (REF("something") APPLY LIT(0))
CATCH (
  CASE(WILDCARD) ==> (Predef_println APPLY LIT("error"))
) FINALLY(Predef_println APPLY LIT("finally")))

In the above examples, WILDCARD is a pattern expression that matches to anything. The examples print as:

try {
  something(0)
} catch {
  case _ => println("error")
}

try {
  something(0)
} catch {
  case _ => println("error")
} finally println("finally")

Anonymous Functions 

Anonymous functions are written using LAMBDA(PARAM(...), ...) ===> rhs:

LAMBDA(PARAM("x")) ==> (REF("x") INT_+ LIT(1))

LAMBDA(PARAM("x", IntClass)) ==> BLOCK(
  REF("x") INT_+ LIT(1))

These examples print as:

x => x + 1

{ (x: Int) =>
  x + 1
}

As the second example shows, when the right-hand side tree is a block, the entire anonymous function is wrapped in a block, which makes it convenient to be used with higher-order functions.

Wildcard parameter 

The parameter list of anonymous functions may be WILDCARD:

LAMBDA(PARAM(WILDCARD)) ==> (REF("x") INT_+ LIT(1))

This prints as:

_ => x + 1

Placeholder syntax 

In addition, an anonymous functions are formed when an expression contains WILDCARD.

WILDCARD INT_+ WILDCARD      // _ + _

String Interpolation 

String interpolations are written using INTERP(sym|"x", arg, ...):

INTERP(StringContext_s, LIT("Jello"), LIT(1), REF("x"))

INTERP("s", LIT("Hello"), LIT(1), REF("x"))

These examples print as:

s"Jello${1}$x"

s"Hello${1}$x"

Type-Level Expressions 

Type references 

Use TYPE_REF(tree|sym|"C") to explicitly convert symbols, names, and trees into types:

(VAL("pos", TYPE_REF(REF("board") DOT "Coord")): Tree)

This prints as:

val pos: board.Coord

Applied types 

Applied types are written by calling TYPE_OF(typ|"C", ...) on a type:

REF("x") withType(ListClass TYPE_OF IntClass)

This prints as:

(x: List[Int])

Type constructors 

treehugger DSL provides built-in type constructors, which will be covered more later:

REF("x") withType(TYPE_LIST(IntClass))
REF("y") withType(TYPE_TUPLE(IntClass, IntClass))
REF("z") withType(IntClass TYPE_=> IntClass)

These examples print as:

(x: List[Int])
(y: (Int, Int))
(z: Int => Int)

Refined types 

Refined types are written by calling TYPE_WITH (typ|"C", ...) on a type:

(VAL("x", TYPE_REF("A") TYPE_WITH "B"): Tree)

This prints as:

val x: A with B

Singleton types 

Singleton type are written using TYPE_SINGLETON(tree):

(VAL("x", TYPE_SINGLETON(THIS)): Tree)

This prints as:

val x: this.type

Structural types 

Structural types are written using TYPE_STRUCT(tree, ...):

REF("x") withType(TYPE_STRUCT(
  DEF("close", UnitClass)
))

This prints as:

(x: ({ def close: Unit }))

Type projections 

Type projections are written by calling TYPE_# (typ|"C") on a type:

REF("x") withType(TYPE_STRUCT(
  TYPEVAR("L") withTypeParams(TYPEVAR("A")) :=
    REF("Const") APPLYTYPE ("M", "A")
) TYPE_#("L"))

This prints as:

(foo: ({ type L[A] = Const[M, A] })#L)

Existential types 

Existential types are written by calling TYPE_FORSOME(tree, ...) on a type:

(DEF("foo")
  withParams(PARAM("arg", TYPE_LIST(
    TYPE_REF(REF("x") DOT "T")) TYPE_FORSOME(
    VAL("x", "Outer")
)))).tree

This prints as:

def foo(arg: List[x.T] forSome { val x: Outer })

Implicits 

Implicit modifier 

Implicit values are written using withFlags(Flags.IMPLICIT):

(DEF("intToRational") withFlags(Flags.IMPLICIT)
  withParams(PARAM("x", IntClass)) := NEW("Rational", REF("x")))

This print as:

implicit def intToRational(x: Int) = new Rational(x)

Implicit parameter 

Implicit parameters are also written using withFlags(Flags.IMPLICIT):

(DEF("greet")
    withParams(PARAM("name", StringClass))
    withParams(PARAM("config", "Config")
      withFlags(Flags.IMPLICIT)) := BLOCK(
  Predef_println APPLY(REF("config") APPLY REF("name"))
))

This prints as:

def greet(name: String)(implicit config: Config) {
  println(config(name))
}

View bounds 

View bounds are written by calling VIEWBOUNDS(typ|"T") on TYPEVAR(...):

(DEF("maxList", "T")
  withTypeParams(TYPEVAR("T") VIEWBOUNDS TYPE_ORDERED("T"))
  withParams(PARAM("elements", TYPE_LIST("T"))): Tree)

This prints as:

def maxList[T <% Ordered[T]](elements: List[T]): T

Context bounds 

Context bounds are written by calling CONTEXTBOUNDS(typ|"T") on TYPEVAR(...):

(DEF("put", UnitClass)
  withTypeParams(TYPEVAR(sym.A) CONTEXTBOUNDS FullManifestClass)
  withParams(PARAM("x", sym.A)): Tree)

This prints as:

def put[A : Manifest](x: A): Unit

Pattern Matching 

Pattern matching is written using tree MATCH(CASE(...), ...):

scala> import treehugger.forest._, definitions._, treehuggerDSL._
import treehugger.forest._
import definitions._
import treehuggerDSL._

scala> val tree = (DEF("maxList", "A")
    withTypeParams(TYPEVAR("T") VIEWBOUNDS TYPE_ORDERED("A"))
    withParams(PARAM("elements", TYPE_LIST("A")))) :=
  REF("elements") MATCH (
    CASE(ListClass UNAPPLY()) ==>
      THROW(IllegalArgumentExceptionClass, "empty list!"),
    CASE(ListClass UNAPPLY(ID("x"))) ==> REF("x"),
    CASE(ID("x") UNLIST_:: ID("rest")) ==> BLOCK(
      VAL("maxRest") := REF("maxList") APPLY(REF("rest")),
      IF(REF("x") INT_> REF("maxRest")) THEN REF("x")
      ELSE REF("maxRest") 
    )
  )
[1m[34mtree[0m: [1m[32mtreehugger.forest.DefDef[0m = DefDef(Modifiers(, , Map()),maxList,List(TypeDef(Modifiers(, , Map()),T,List(),TypeTree())),List(List(ValDef(Modifiers(<param>, , Map()),Typed(Ident(elements),TypeTree()),EmptyTree))),TypeTree(),Match(Ident(elements),List(CaseDef(UnApply(Ident(List),List()),EmptyTree,Throw(Apply(Select(New(TypeTree()),<init>),List(Literal(Constant(empty list!)))))), CaseDef(UnApply(Ident(List),List(Ident(x))),EmptyTree,Ident(x)), CaseDef(InfixUnApply(Ident(x),$colon$colon,List(Ident(rest))),EmptyTree,Block(List(ValDef(Modifiers(, , Map()),Ident(maxRest),Apply(Ident(maxList),List(Ident(rest))))),If(Infix(Ident(x),>,List(Ident(maxRest))),Ident(x),Ident(maxRest)))))))

scala> treeToString(tree)
[1m[34mres0[0m: [1m[32mString[0m =
def maxList[T <% Ordered[A]](elements: List[A]): A =
  elements match {
    case List() => throw new IllegalArgumentException("empty list!")
    case List(x) => x
    case x :: rest => {
      val maxRest = maxList(rest)
      if (x > maxRest) x
      else maxRest
    }
  }

Patterns 

Let’s look into the pattern expressions.

Variable patterns 

Variable patterns are written as ID(sym|"x") and WILDCARD. They both match any value:

ID("x")                      // x
WILDCARD                     // _

Typed patterns 

Typed patterns are written as pat withType(typ|"C"):

ID("x") withType(IntClass)   // (x: Int)
WILDCARD withType(IntClass)  // (_: Int)

Pattern binders 

Pattern binders are written as pat withBinder(sym|"x"). Binders are used to give names to patterns:

WILDCARD withBinder("x")     // (x @ _)

Literal patterns 

Literal patterns are written using LIT(...):

LIT("X")                     // "X"

Stable identifier patterns 

Stable identifier patterns are written using BACKQUOTED(sym|"x"):

BACKQUOTED("x")              // `x`

Constructor patterns 

Constructor patterns are written by calling UNAPPLY(pattern, ...) to a symbol or a tree:

REF("Address") UNAPPLY(WILDCARD, WILDCARD, WILDCARD)

This prints as:

Address(_, _, _)

Tuple patterns 

Tuple patterns are written using TUPLE(pattern, ...):

TUPLE(LIT(0), LIT(1))        // (0, 1)

Pattern sequences 

Sequence wildcards are written using SEQ_WILDCARD withBinder(sym|"x"):

REF("C") UNAPPLY(SEQ_WILDCARD withBinder("xs"))

This prints as:

C((xs @ _*))

Infix operation patterns 

Infix operations patterns are written by calling INFIX(op) UNAPPLY(pat, ...) to a symbol or a tree:

LIT(0) INFIX(ConsClass) UNAPPLY (NIL)

This prints as:

0 :: Nil

Because this pattern appears frequently, treehugger DSL provides a built-in constructor UNLIST_:::

LIT(0) UNLIST_:: NIL         // 0 :: Nil

This works because an infix operation pattern p op (q, …) is a shorthand for the constructor pattern op(p, q, …).

Pattern alternatives 

Pattern alternatives are written as pat1 OR_PATTERN pat2:

LIT("New York") OR_PATTERN LIT("Paris")

This prints as:

"New York" | "Paris"

Pattern Matching Expressions 

Pattern matching expressions are written by calling MATCH(CASE(pattern), ...) on a symbol or a tree:

REF("x") MATCH(
  CASE (LIT(0) OR_PATTERN LIT(1)) ==> TRUE,
  CASE (WILDCARD) ==> FALSE
)

This prints as:

x match {
  case 0 | 1 => true
  case _ => false
}

Just as we saw in anonymous functions, treehugger DSL uses ==> to denote Scala’s =>.

Guarded patterns 

Optinally, a guard clause may be added to the case clause using IF(...):

REF("x") MATCH(
  CASE (ID("x"),
    IF(REF("x") INT_< LIT(10))) ==> TRUE,
  CASE (WILDCARD) ==> FALSE
)

This prints as:

x match {
  case x if x < 10 => true
  case _ => false
}

Case Sequence Functions 

Case sequence functions are defined by listing CASE(...) clauses in a BLOCK(...):

BLOCK(
  CASE(SOME(ID("x"))) ==> REF("x"),
  CASE(NONE) ==> LIT(0)
)

This prints as:

{
  case Some(x) => x
  case None => 0
}

Pattern Values 

Pattern values are defined by placing a pattern in VAL(...) or VAR(...):

VAL(REF("Address") UNAPPLY
  (ID("name"), ID("street"), ID("city"))) := REF("x")
VAR(SOME(ID("y"))) := SOME(LIT(1))

These examples print as:

val Address(name, street, city) = x
var Some(y) = Some(1)

Top-Level Definitions 

Top-level definitions consists of compilation units, packagings, and package objects.

Compilation Units 

Compilation units are written by calling inPackage(sym|"p") or withoutPackage on BLOCK(...):

BLOCK(
  OBJECTDEF("M")
) inPackage("p")

This prints as:

package p

object M
BLOCK(
  OBJECTDEF("M1"),
  OBJECTDEF("M2")
) withoutPackage

This prints as:

object M1

object M2

Packaging 

Packagings are written using PACKAGE(sym|"p") as follows:

PACKAGE("p") := BLOCK(
  OBJECTDEF("M")
)

This prints as:

package p {
  object M
}

Package Objects 

Package objects are written using PACKAGEOBJECTDEF(...):

PACKAGEOBJECTDEF("p") := BLOCK(
  OBJECTDEF("M")
)

This prints as:

package object p {
  object M 
}

Annotations 

Annotations are applied to definitions, declarations, types, or expressions.

Declaration annotations 

Annotations are applied to declarations using withAnnots(annot, ...). This takes vararg of AnnotationInfo, which is created using ANNOT(typ|"C", arg, ...).

CLASSDEF("C")
    withAnnots(ANNOT(SerializableAttr)) := BLOCK(
  DEF("get", IntClass) := LIT(0)
)

This prints as:

@serializable class C {
  def get: Int = 0
}

Type annotations 

Type annotations are written by calling withAnnots(annot, ...) on TYPEVAR(...).

val annot = ANNOT(SpecializedClass, REF(IntClass))

TRAITDEF("Function0")
    withTypeParams(TYPEVAR("T") withAnnots(annot)) := BLOCK(
  DEF("apply", "T")
)

This prints as:

trait Function0[@specialized(Int) T] {
  def apply: T
}

Annotated expressions 

Annotated expressions are written as tree withAnnots(annot, ...):

REF("e") withAnnots(ANNOT(UncheckedClass))

This prints as:

(e: @unchecked)

Standard Library 

treehugger DSL provides some built-in functions for commonly used functions.

Root Class 

Any methods 

tree TOSTRING                // x.toString
tree GETCLASS                // x.getClass
tree IS typ                  // x.isInstanceOf[Int]
tree AS typ                  // x.asInstanceOf[Int]

Any operators 

lhs ANY_== rhs               // ==
lhs ANY_!= rhs               // !=
lhs ANY_-> rhs               // ->

AnyRef operators 

lhs OBJ_EQ rhs               // eq
lhs OBJ_NE rhs               // ne

Value Class Operators 

Here are the operators that form infix application tree when called on a symbol or a tree.

Boolean operators 

lhs OR rhs                   // ||
lhs AND rhs                  // &&

Numeric operators 

lhs INT_| rhs                // |
lhs INT_& rhs                // &
lhs INT_< rhs                // <
lhs INT_> rhs                // >
lhs INT_<= rhs               // <=
lhs INT_>= rhs               // >=
lhs INT_+ rhs                // +
lhs INT_- rhs                // -
lhs INT_* rhs                // *
lhs INT_/ rhs                // /
lhs INT_TO rhs               // TO

Collection Class Operators 

List operators 

lhs LIST_:: rhs              // ::
lhs LIST_::: rhs             // :::
lhs SEQ_++ rhs               // ++
lhs SEQ_/: rhs               // /:
lhs SEQ_:\ rhs               // :\

Collection operators 

When the right-hand side expression is an anonymous function block or a case sequence function, the collection operators form an infix application tree. Otherwise, they form a selection or an application tree:

REF("list") MAP LAMBDA(PARAM("x")) ==> BLOCK(
  REF("x") INT_+ LIT(1)
)

REF("list") MAP (WILDCARD INT_+ LIT(1))

These print as:

list map { x =>
  x + 1
}

foo.map(_ + 1)

Here are the operators:

lhs FOREACH rhs              // foreach
lhs MAP rhs                  // map
lhs FLATMAP rhs              // flatMap
lhs COLLECT rhs              // collect
lhs FIND rhs                 // find
lhs SLICE (from, to)         // slice
lhs TAKE rhs                 // take
lhs DROP rhs                 // drop
lhs TAKEWHILE rhs            // take
lhs DROPWHILE rhs            // drop
lhs FILTER rhs               // filter
lhs WITHFILTER rhs           // withFilter
lhs FILTERNOT rhs            // filterNot
lhs SPLITAT rhs              // splitAt
lhs SPAN rhs                 // span
lhs PARTITION rhs            // partition
lhs GROUPBY rhs              // groupBy
lhs FORALL rhs               // forall
lhs EXISTS rhs               // exists
lhs COUNT rhs                // count
lhs FOLDLEFT rhs             // foldLeft
lhs FOLDRIGHT rhs            // foldRight
lhs REDUCELEFT rhs           // reduceLeft
lhs REDUCERIGHT rhs          // reduceRight

Collection Constructors 

Here are built-in constructors and values:

LIST(arg, ...)               // List
NIL                          // Nil
pat1 UNLIST_:: pat2          // ::
SOME(arg, ...)               // Some
NONE                         // None
RIGHT(arg)                   // Right
LEFT(arg)                    // Left
ARRAY(arg, ...)              // Array
SEQ(arg, ...)                // Seq
VECTOR(arg, ...)             // Vector
MAKE_MAP(k ANY_-> v, ...)    // Map

Type Constructors 

Here are built-in type constructors:

TYPE_ITERATOR(typ)           // Iterator[A]
TYPE_TUPLE(typ, ...)         // Tuple2[A, B, ...]
TYPE_ARRAY(typ)              // Array[A]
TYPE_LIST(typ)               // List[A]
TYPE_SEQ(typ)                // Seq[A]
TYPE_VECTOR(typ)             // Vector[A]
TYPE_MAP(typ1, typ2)         // Map[A, B]
TYPE_OPTION(typ)             // Option[A]
TYPE_EITHER(typ1, typ2)      // Either[A, B]
TYPE_RIGHT(typ1, typ2)       // Right[A, B]
TYPE_LEFT(typ1, typ2)        // Left[A, B]
TYPE_SOME(typ)               // Some[A]
TYPE_ORDERED(typ)            // Ordered[A]
typ1 TYPE_=> typ2            // Function1[A, R]
TYPE_FUNCTION(typ, ..., r)   // Function1[A, R]
TYPE_=:=(typ1, typ2)         // =:=[A, B]
TYPE_<:<(typ1, typ2)         // <:<[A, B]
TYPE_<%<(typ1, typ2)         // <%<[A, B]