diff --git a/src/main/scala/leon/purescala/PrettyPrinter.scala b/src/main/scala/leon/purescala/PrettyPrinter.scala
index c8a1f666b4f462a05a61498d29151b9cf5038a78..fb8831f64e929c4755fdf551dc28bd75a5b4702c 100644
--- a/src/main/scala/leon/purescala/PrettyPrinter.scala
+++ b/src/main/scala/leon/purescala/PrettyPrinter.scala
@@ -11,566 +11,463 @@ import Definitions._
 import utils._
 
 import java.lang.StringBuffer
+import PrinterHelpers._
+
+case class PrinterContext(
+  current: Tree,
+  parent: Option[Tree],
+  lvl: Int,
+  printer: PrettyPrinter
+)
+
+object PrinterHelpers {
+  implicit class Printable(val f: PrinterContext => Any) extends AnyVal {
+    def print(ctx: PrinterContext) = f(ctx)
+  }
 
-/** This pretty-printer uses Unicode for some operators, to make sure we
- * distinguish PureScala from "real" Scala (and also because it's cute). */
-class PrettyPrinter(opts: PrinterOptions, val sb: StringBuffer = new StringBuffer) {
-  override def toString = sb.toString
+  implicit class PrintingHelper(val sc: StringContext) extends AnyVal {
 
-  def append(str: String) {
-    sb.append(str)
-  }
+    def p(args: Any*)(implicit ctx: PrinterContext): Unit = {
+      val printer = ctx.printer
+      val sb      = printer.sb
+
+      var strings     = sc.parts.iterator
+      var expressions = args.iterator
+
+      var extraInd = 0;
+      var firstElem = true;
+
+      while(strings.hasNext) {
+        val s = strings.next.stripMargin
+
+        // Compute indentation
+        var start = s.lastIndexOf('\n')
+        if(start >= 0 || firstElem) {
+          var i = start+1;
+          while(i < s.length && s(i) == ' ') {
+            i += 1
+          }
+          extraInd = (i-start-1)/2
+        }
+
+        firstElem = false
+
+        // Make sure new lines are also indented
+        sb.append(s.replaceAll("\n", "\n"+("  "*ctx.lvl)))
+
+        var nctx = ctx.copy(lvl = ctx.lvl + extraInd)
+
+        if (expressions.hasNext) {
+          val e = expressions.next
+
+          e match {
+            case (t1, t2) =>
+              nary(Seq(t1, t2), " -> ").print(nctx)
+
+            case ts: Seq[Any] =>
+              nary(ts).print(nctx)
+
+            case t: Tree =>
+              nctx = nctx.copy(current = t, parent = Some(nctx.current))
+              printer.pp(t)(nctx)
 
-  def ind(implicit lvl: Int) {
-    sb.append("  " * lvl)
+            case p: Printable =>
+              p.print(nctx)
+
+            case e =>
+              sb.append(e.toString)
+          }
+        }
+      }
+    }
   }
-  def nl(implicit lvl: Int) {
-    sb.append("\n")
-    ind(lvl)
+
+  def nary(ls: Seq[Any], sep: String = ", "): Printable = {
+    val strs = List("") ::: List.fill(ls.size-1)(sep)
+
+    implicit pctx: PrinterContext =>
+      new StringContext(strs: _*).p(ls: _*)
   }
 
-  // EXPRESSIONS
-  // all expressions are printed in-line
-  def ppUnary(expr: Tree, op1: String, op2: String)(implicit parent: Option[Tree], lvl: Int) {
-    sb.append(op1)
-    pp(expr, parent)
-    sb.append(op2)
+  def typed(t: Tree with Typed): Printable = {
+    implicit pctx: PrinterContext =>
+      p"$t: ${t.getType}"
   }
 
-  def ppBinary(left: Tree, right: Tree, op: String)(implicit  parent: Option[Tree], lvl: Int) {
-    sb.append("(")
-    pp(left, parent)
-    sb.append(op)
-    pp(right, parent)
-    sb.append(")")
+  def typed(ts: Seq[Tree with Typed]): Printable = {
+    nary(ts.map(typed))
   }
+}
+
+/** This pretty-printer uses Unicode for some operators, to make sure we
+ * distinguish PureScala from "real" Scala (and also because it's cute). */
+class PrettyPrinter(opts: PrinterOptions, val sb: StringBuffer = new StringBuffer) {
 
-  def ppNary(exprs: Seq[Tree], pre: String, op: String, post: String)(implicit  parent: Option[Tree], lvl: Int) {
-    sb.append(pre)
-    val sz = exprs.size
-    var c = 0
+  override def toString = sb.toString
 
-    exprs.foreach(ex => {
-      pp(ex, parent) ; c += 1 ; if(c < sz) sb.append(op)
-    })
+  protected def optP(body: => Any)(implicit ctx: PrinterContext) = {
+    if (requiresParentheses(ctx.current, ctx.parent)) {
+      sb.append("(")
+      body
+      sb.append(")")
+    } else {
+      body
+    }
+  }
 
-    sb.append(post)
+  protected def optB(body: => Any)(implicit ctx: PrinterContext) = {
+    if (requiresBraces(ctx.current, ctx.parent)) {
+      sb.append("{")
+      body
+      sb.append("}")
+    } else {
+      body
+    }
   }
 
-  def pp(tree: Tree, parent: Option[Tree])(implicit lvl: Int): Unit = {
-    implicit val p = Some(tree)
+  def pp(tree: Tree)(implicit ctx: PrinterContext): Unit = {
 
     tree match {
       case id: Identifier =>
         if (opts.printUniqueIds) {
-          sb.append(id.uniqueName)
+          p"${id.uniqueName}"
         } else {
-          sb.append(id.toString)
+          p"${id.toString}"
         }
 
       case Variable(id) =>
-        if (opts.printTypes) {
-          sb.append("(")
-          pp(id, p)
-          sb.append(": ")
-          pp(id.getType, p)
-          sb.append(")")
-        } else {
-          pp(id, p)
-        }
+        p"$id"
 
       case LetTuple(bs,d,e) =>
-        sb.append("(let ")
-        ppNary(bs, "(", ",", " :=");
-        pp(d, p)
-        sb.append(") in")
-        nl(lvl+1)
-        pp(e, p)(lvl+1)
-        sb.append(")")
+        p"""|val ($bs) = $d;
+            |$e"""
 
       case Let(b,d,e) =>
-        sb.append("(let (")
-        pp(b, p)
-        sb.append(" := ");
-        pp(d, p)
-        sb.append(") in")
-        nl(lvl+1)
-        pp(e, p)(lvl+1)
-        sb.append(")")
+        p"""|val $b = $d;
+            |$e"""
 
       case LetDef(fd,body) =>
-        sb.append("\n")
-        pp(fd, p)(lvl+1)
-        sb.append("\n")
-        sb.append("\n")
-        nl
-        pp(body, p)
-
-      case And(exprs) => ppNary(exprs, "(", " \u2227 ", ")")            // \land
-      case Or(exprs) => ppNary(exprs, "(", " \u2228 ", ")")             // \lor
-      case Not(Equals(l, r)) => ppBinary(l, r, " \u2260 ")    // \neq
-      case Iff(l,r) => ppBinary(l, r, " <=> ")              
-      case Implies(l,r) => ppBinary(l, r, " ==> ")              
-      case UMinus(expr) => ppUnary(expr, "-(", ")")
-      case Equals(l,r) => ppBinary(l, r, " == ")
-      case IntLiteral(v) => sb.append(v)
-      case BooleanLiteral(v) => sb.append(v)
-      case StringLiteral(s) => sb.append("\"" + s + "\"")
-      case UnitLiteral() => sb.append("()")
-      case GenericValue(tp, id) =>
-        pp(tp, p)
-        sb.append("#"+id)
-
-      case t@Tuple(exprs) => ppNary(exprs, "(", ", ", ")")
-      case s@TupleSelect(t, i) =>
-        pp(t, p)
-        sb.append("._" + i)
-
-      case h @ Hole(o) =>
-        sb.append("???[")
-        pp(h.getType, p)
-        sb.append("](")
-        pp(o, p)
-        sb.append(")")
-
-      case c@Choose(vars, pred) =>
-        sb.append("choose(")
-        ppNary(vars, "", ", ", "")
-        sb.append(" => ")
-        pp(pred, p)
-        sb.append(")")
+        p"""|$fd
+            |$body"""
+
+      case Require(pre, body) =>
+        p"""|require($pre)
+            |$body"""
+
+      case Assert(const, Some(err), body) =>
+        p"""|assert($const, "$err")
+            |$body"""
+
+      case Assert(const, None, body) =>
+        p"""|assert($const)
+            |$body"""
+
+      case Ensuring(body, id, post) =>
+        p"""|{
+            |  $body
+            |} ensuring {
+            |  (${typed(id)}) => $post
+            |}"""
 
       case CaseClass(cct, args) =>
-        pp(cct, p)
         if (cct.classDef.isCaseObject) {
-          ppNary(args, "", "", "")
+          p"$cct"
         } else {
-          ppNary(args, "(", ", ", ")")
+          p"$cct($args)"
         }
 
-      case CaseClassInstanceOf(cct, e) =>
-        pp(e, p)
-        sb.append(".isInstanceOf[")
-        pp(cct, p)
-        sb.append("]")
-
-      case CaseClassSelector(_, cc, id) =>
-        pp(cc, p)
-        sb.append(".")
-        pp(id, p)
-
+      case And(exprs)           => optP { p"${nary(exprs, " && ")}" }
+      case Or(exprs)            => optP { p"${nary(exprs, "| || ")}" }
+      case Not(Equals(l, r))    => optP { p"$l \u2260 $r" }
+      case Iff(l,r)             => optP { p"$l <=> $r" }
+      case Implies(l,r)         => optP { p"$l ==> $r" }
+      case UMinus(expr)         => p"-$expr"
+      case Equals(l,r)          => optP { p"$l == $r" }
+      case IntLiteral(v)        => p"$v"
+      case BooleanLiteral(v)    => p"$v"
+      case StringLiteral(s)     => p""""$s""""
+      case UnitLiteral()        => p"()"
+      case GenericValue(tp, id) => p"$tp#$id"
+      case Tuple(exprs)         => p"($exprs)"
+      case TupleSelect(t, i)    => p"${t}._$i"
+      case h @ Hole(o)          => p"???[${h.getType}]($o)"
+      case Choose(vars, pred)   => p"choose($vars => $pred)"
+      case CaseClassInstanceOf(cct, e)         => p"$e.isInstanceOf[$cct]"
+      case CaseClassSelector(_, e, id)         => p"$e.$id"
       case MethodInvocation(rec, _, tfd, args) =>
-        pp(rec, p)
-        sb.append(".")
-        pp(tfd.id, p)
+        p"$rec.${tfd.id}"
 
         if (tfd.tps.nonEmpty) {
-          ppNary(tfd.tps, "[", ",", "]")
+          p"[${tfd.tps}]"
         }
 
-        ppNary(args, "(", ", ", ")")
+        p"($args)"
 
       case FunctionInvocation(tfd, args) =>
-        pp(tfd.id, p)
+        p"${tfd.id}"
 
         if (tfd.tps.nonEmpty) {
-          ppNary(tfd.tps, "[", ",", "]")
+          p"[${tfd.tps}]"
         }
 
-        ppNary(args, "(", ", ", ")")
-
-      case Plus(l,r) => ppBinary(l, r, " + ")
-      case Minus(l,r) => ppBinary(l, r, " - ")
-      case Times(l,r) => ppBinary(l, r, " * ")
-      case Division(l,r) => ppBinary(l, r, " / ")
-      case Modulo(l,r) => ppBinary(l, r, " % ")
-      case LessThan(l,r) => ppBinary(l, r, " < ")
-      case GreaterThan(l,r) => ppBinary(l, r, " > ")
-      case LessEquals(l,r) => ppBinary(l, r, " \u2264 ")      // \leq
-      case GreaterEquals(l,r) => ppBinary(l, r, " \u2265 ")   // \geq
-      case FiniteSet(rs) => if(rs.isEmpty) sb.append("\u2205") /* Ø */ else ppNary(rs, "{", ", ", "}")
-      case FiniteMultiset(rs) => ppNary(rs, "{|", ", ", "|}")
-      case EmptyMultiset(_) => sb.append("\u2205")                     // Ø
-      case Not(ElementOfSet(s,e)) => ppBinary(s, e, " \u2209 ") // \notin
-      case ElementOfSet(s,e) => ppBinary(s, e, " \u2208 ")    // \in
-      case SubsetOf(l,r) => ppBinary(l, r, " \u2286 ")        // \subseteq
-      case Not(SubsetOf(l,r)) => ppBinary(l, r, " \u2288 ")        // \notsubseteq
-      case SetMin(s) => pp(s, p); sb.append(".min")
-      case SetMax(s) => pp(s, p); sb.append(".max")
-      case SetUnion(l,r) => ppBinary(l, r, " \u222A ")        // \cup
-      case MultisetUnion(l,r) => ppBinary(l, r, " \u222A ")   // \cup
-      case MapUnion(l,r) => ppBinary(l, r, " \u222A ")        // \cup
-      case SetDifference(l,r) => ppBinary(l, r, " \\ ") 
-      case MultisetDifference(l,r) => ppBinary(l, r, " \\ ")       
-      case SetIntersection(l,r) => ppBinary(l, r, " \u2229 ") // \cap
-      case MultisetIntersection(l,r) => ppBinary(l, r, " \u2229 ") // \cap
-      case SetCardinality(t) => ppUnary(t, "|", "|")
-      case MultisetCardinality(t) => ppUnary(t, "|", "|")
-      case MultisetPlus(l,r) => ppBinary(l, r, " \u228E ")    // U+
-      case MultisetToSet(e) => pp(e, p); sb.append(".toSet")
+        p"($args)"
+
+      case Plus(l,r)                 => optP { p"$l + $r" }
+      case Minus(l,r)                => optP { p"$l - $r" }
+      case Times(l,r)                => optP { p"$l * $r" }
+      case Division(l,r)             => optP { p"$l / $r" }
+      case Modulo(l,r)               => optP { p"$l % $r" }
+      case LessThan(l,r)             => optP { p"$l < $r" }
+      case GreaterThan(l,r)          => optP { p"$l > $r" }
+      case LessEquals(l,r)           => optP { p"$l <= $r" }
+      case GreaterEquals(l,r)        => optP { p"$l >= $r" }
+      case FiniteSet(rs)             => p"{$rs}"
+      case FiniteMultiset(rs)        => p"{|$rs|)"
+      case EmptyMultiset(_)          => p"\u2205"
+      case Not(ElementOfSet(e,s))    => p"$e \u2209 $s"
+      case ElementOfSet(e,s)         => p"$e \u2208 $s"
+      case SubsetOf(l,r)             => p"$l \u2286 $r"
+      case Not(SubsetOf(l,r))        => p"$l \u2288 $r"
+      case SetMin(s)                 => p"$s.min"
+      case SetMax(s)                 => p"$s.max"
+      case SetUnion(l,r)             => p"$l \u222A $r"
+      case MultisetUnion(l,r)        => p"$l \u222A $r"
+      case MapUnion(l,r)             => p"$l \u222A $r"
+      case SetDifference(l,r)        => p"$l \\ $r"
+      case MultisetDifference(l,r)   => p"$l \\ $r"
+      case SetIntersection(l,r)      => p"$l \u2229 $r"
+      case MultisetIntersection(l,r) => p"$l \u2229 $r"
+      case SetCardinality(s)         => p"|$s|"
+      case MultisetCardinality(s)    => p"|$s|"
+      case MultisetPlus(l,r)         => p"$l \u228E $r"
+      case MultisetToSet(e)          => p"$e.toSet"
+      case MapGet(m,k)               => p"$m($k)"
+      case MapIsDefinedAt(m,k)       => p"$m.isDefinedAt($k)"
+      case ArrayLength(a)            => p"$a.length"
+      case ArrayClone(a)             => p"$a.clone"
+      case ArrayFill(size, v)        => p"Array.fill($size)($v)"
+      case ArrayMake(v)              => p"Array.make($v)"
+      case ArraySelect(a, i)         => p"$a($i)"
+      case ArrayUpdated(a, i, v)     => p"$a.updated($i, $v)"
+      case FiniteArray(exprs)        => p"Array($exprs)"
+      case Distinct(exprs)           => p"distinct($exprs)"
+      case Not(expr)                 => p"\u00AC$expr"
+      case ValDef(id, tpe)           => p"${typed(id)}"
+      case This(_)                   => p"this"
+      case (tfd: TypedFunDef)        => p"typed def ${tfd.id}[${tfd.tps}]"
+      case TypeParameterDef(tp)      => p"$tp"
+      case TypeParameter(id)         => p"$id"
+
       case FiniteMap(rs) =>
-        sb.append("{")
-        val sz = rs.size
-        var c = 0
-        rs.foreach{case (k, v) => {
-          pp(k, p); sb.append(" -> "); pp(v, p); c += 1 ; if(c < sz) sb.append(", ")
-        }}
-        sb.append("}")
-
-      case MapGet(m,k) =>
-        pp(m, p)
-        ppNary(Seq(k), "(", ",", ")")
-
-      case MapIsDefinedAt(m,k) =>
-        pp(m, p)
-        sb.append(".isDefinedAt")
-        ppNary(Seq(k), "(", ",", ")")
-
-      case ArrayLength(a) =>
-        pp(a, p)
-        sb.append(".length")
-
-      case ArrayClone(a) => 
-        pp(a, p)
-        sb.append(".clone")
-
-      case fill@ArrayFill(size, v) => 
-        sb.append("Array.fill(")
-        pp(size, p)
-        sb.append(")(")
-        pp(v, p)
-        sb.append(")")
-
-      case am@ArrayMake(v) =>
-        sb.append("Array.make(")
-        pp(v, p)
-        sb.append(")")
-
-      case sel@ArraySelect(ar, i) =>
-        pp(ar, p)
-        sb.append("(")
-        pp(i, p)
-        sb.append(")")
-
-      case up@ArrayUpdated(ar, i, v) =>
-        pp(ar, p)
-        sb.append(".updated(")
-        pp(i, p)
-        sb.append(", ")
-        pp(v, p)
-        sb.append(")")
-      
-      case FiniteArray(exprs) =>
-        ppNary(exprs, "Array(", ", ", ")")
-
-      case Distinct(exprs) =>
-        sb.append("distinct")
-        ppNary(exprs, "(", ", ", ")")
-      
+        p"{$rs}"
+
+
       case IfExpr(c, t, e) =>
-        sb.append("if (")
-        pp(c, p)
-        sb.append(")")
-        nl(lvl+1)
-        pp(t, p)(lvl+1)
-        nl
-        sb.append("else")
-        nl(lvl+1)
-        pp(e, p)(lvl+1)
-
-      case mex @ MatchExpr(s, csc) =>
-        pp(s, p)
-        sb.append(" match {\n")
-
-        csc.foreach(cs => {
-          nl(lvl+1)
-          pp(cs, p)
-          sb.append("\n")
-        })
-        nl(lvl)
-        sb.append("}")
-
-      case Not(expr) => ppUnary(expr, "\u00AC(", ")")               // \neg
-
-      case e @ Error(desc) =>
-        sb.append("error(\"" + desc + "\")[")
-        pp(e.getType, p)
-        sb.append("]")
-
-      case (tree: PrettyPrintable) => tree.printWith(this)
+        optP {
+          p"""|if ($c) {
+              |  $t
+              |} else {
+              |  $e
+              |}"""
+        }
+
+      case MatchExpr(s, csc) =>
+        optP {
+          p"""|$s match {
+              |  ${nary(csc, "\n")}
+              |}"""
+        }
 
       // Cases
       case SimpleCase(pat, rhs) =>
-          sb.append("case ")
-          pp(pat, p)
-          sb.append(" =>\n")
-          ind(lvl+1)
-          pp(rhs, p)(lvl+1)
+        p"""|case $pat =>
+            |  $rhs"""
+
       case GuardedCase(pat, guard, rhs) =>
-          sb.append("case ")
-          pp(pat, p)
-          sb.append(" if ")
-          pp(guard, p)
-          sb.append(" =>\n")
-          ind(lvl+1)
-          pp(rhs, p)(lvl+1)
+        p"""|case $pat if $guard =>
+            |  $rhs"""
 
       // Patterns
-      case CaseClassPattern(bndr, cct, subps) =>
-        bndr.foreach(b => sb.append(b + " @ "))
-        pp(cct, p)
-        sb.append("(")
-        var c = 0
-        val sz = subps.size
-        subps.foreach(sp => {
-          pp(sp, p)
-          if(c < sz - 1)
-            sb.append(", ")
-          c = c + 1
-        })
-        sb.append(")")
-
-      case WildcardPattern(None)     => sb.append("_")
-      case WildcardPattern(Some(id)) => pp(id.toVariable, p)
-      case InstanceOfPattern(bndr, cct) =>
-        bndr.foreach(b => sb.append(b + " : "))
-        pp(cct, p)
-
-      case TuplePattern(bndr, subPatterns) =>
-        bndr.foreach(b => sb.append(b + " @ "))
-        sb.append("(")
-        subPatterns.init.foreach(pat => {
-          pp(pat, p)
-          sb.append(", ")
-        })
-        pp(subPatterns.last, p)
-        sb.append(")")
+      case WildcardPattern(None)     => p"_"
+      case WildcardPattern(Some(id)) => p"$id"
 
+      case CaseClassPattern(ob, cct, subps) =>
+        ob.foreach { b => p"$b @ " }
+        p"${cct.id}($subps)"
 
-      // Types
-      case Untyped => sb.append("<untyped>")
-      case UnitType => sb.append("Unit")
-      case Int32Type => sb.append("Int")
-      case BooleanType => sb.append("Boolean")
-      case ArrayType(bt) =>
-        sb.append("Array[")
-        pp(bt, p)
-        sb.append("]")
-      case SetType(bt) =>
-        sb.append("Set[")
-        pp(bt, p)
-        sb.append("]")
-      case MapType(ft,tt) =>
-        sb.append("Map[")
-        pp(ft, p)
-        sb.append(",")
-        pp(tt, p)
-        sb.append("]")
-      case MultisetType(bt) =>
-        sb.append("Multiset[")
-        pp(bt, p)
-        sb.append("]")
-      case TupleType(tpes) => ppNary(tpes, "(", ", ", ")")
-      case FunctionType(fts, tt) =>
-        if (fts.size > 1) {
-          ppNary(fts, "(", ", ", ")")
-        } else if (fts.size == 1) {
-          pp(fts.head, p)
-        }
-        sb.append(" => ")
-        pp(tt, p)
+      case InstanceOfPattern(ob, cct) =>
+        ob.foreach { b => p"$b : " }
+        p"$cct"
 
+      case TuplePattern(ob, subps) =>
+        ob.foreach { b => p"$b @ " }
+        p"($subps)"
+
+      // Types
+      case Untyped               => p"<untyped>"
+      case UnitType              => p"Unit"
+      case Int32Type             => p"Int"
+      case BooleanType           => p"Boolean"
+      case ArrayType(bt)         => p"Array[$bt]"
+      case SetType(bt)           => p"Set[$bt]"
+      case MapType(ft,tt)        => p"Map[$ft, $tt]"
+      case MultisetType(bt)      => p"Multiset[$bt]"
+      case TupleType(tpes)       => p"($tpes)"
+      case FunctionType(fts, tt) => p"($fts) => $tt"
       case c: ClassType =>
-        pp(c.classDef.id, p)
+        p"${c.classDef.id}"
         if (c.tps.nonEmpty) {
-          ppNary(c.tps, "[", ",", "]")
+          p"[${c.tps}]"
         }
 
 
       // Definitions
       case Program(id, modules) =>
-        assert(lvl == 0)
-        sb.append("package ")
-        pp(id, p)
-        sb.append(" {\n")
-        modules.foreach {
-          m => pp(m, p)(lvl+1)
-        }
-        sb.append("}\n")
+        p"""|package $id {
+            |  ${nary(modules, "\n\n")}
+            |}"""
 
       case ModuleDef(id, defs) =>
-        nl
-        sb.append("object ")
-        pp(id, p)
-        sb.append(" {")
-
-        var c = 0
-        val sz = defs.size
-
-        defs.foreach(df => {
-          pp(df, p)(lvl+1)
-          if(c < sz - 1) {
-            sb.append("\n\n")
-          }
-          c = c + 1
-        })
-
-        nl
-        sb.append("}\n")
+        p"""|object $id {
+            |  ${nary(defs, "\n\n")}
+            |}"""
 
       case acd @ AbstractClassDef(id, tparams, parent) =>
-        sb.append("sealed abstract class ")
-        pp(id, p)
+        p"abstract class $id"
 
         if (tparams.nonEmpty) {
-          ppNary(tparams, "[", ",", "]")
+          p"[$tparams]"
         }
 
         parent.foreach{ par =>
-          sb.append(" extends ")
-          pp(par.id, p)
+          p" extends ${par.id}"
         }
 
         if (acd.methods.nonEmpty) {
-          sb.append(" {\n")
-          for (md <- acd.methods) {
-            ind(lvl+1)
-            pp(md, p)(lvl+1)
-            sb.append("\n\n")
-          }
-          ind
-          sb.append("}")
+          p"""| {
+              |  ${nary(acd.methods, "\n\n")}
+              |}"""
         }
 
       case ccd @ CaseClassDef(id, tparams, parent, isObj) =>
         if (isObj) {
-          sb.append("case object ")
+          p"case object $id"
         } else {
-          sb.append("case class ")
+          p"case class $id"
         }
 
-        pp(id, p)
-
         if (tparams.nonEmpty) {
-          ppNary(tparams, "[", ", ", "]")
+          p"[$tparams]"
         }
 
         if (!isObj) {
-          ppNary(ccd.fields, "(", ", ", ")")
+          p"(${ccd.fields})"
         }
 
         parent.foreach{ par =>
-          sb.append(" extends ")
-          pp(par.id, p)
+          p" extends ${par.id}"
         }
 
         if (ccd.methods.nonEmpty) {
-          sb.append(" {\n")
-          for (md <- ccd.methods) {
-            ind(lvl+1)
-            pp(md, p)(lvl+1)
-            sb.append("\n\n")
-          }
-          ind
-          sb.append("}")
+          p"""| {
+              |  ${nary(ccd.methods, "\n\n")}
+              |}"""
         }
 
-      case th: This =>
-        sb.append("this")
-
-      case tfd: TypedFunDef =>
-        sb.append("typed def ")
-        pp(tfd.id, p)
-        ppNary(tfd.tps, "[", ", ", "]")
-
       case fd: FunDef =>
         for(a <- fd.annotations) {
-          ind
-          sb.append("@" + a + "\n")
+          p"""|@$a
+              |"""
         }
 
-        fd.precondition.foreach(prec => {
-          ind
-          sb.append("@pre : ")
-          pp(prec, p)(lvl)
-          sb.append("\n")
-        })
-
-        fd.postcondition.foreach{ case (id, postc) => {
-          ind
-          sb.append("@post: ")
-          pp(id, p)
-          sb.append(" => ")
-          pp(postc, p)(lvl)
-          sb.append("\n")
-        }}
-
-        ind
-        sb.append("def ")
-        pp(fd.id, p)
-        sb.append("(")
-
-        val sz = fd.params.size
-        var c = 0
-        
-        fd.params.foreach(arg => {
-          sb.append(arg.id)
-          sb.append(" : ")
-          pp(arg.tpe, p)
-
-          if(c < sz - 1) {
-            sb.append(", ")
-          }
-          c = c + 1
-        })
 
-        sb.append(") : ")
-        pp(fd.returnType, p)
-        sb.append(" = ")
+        p"""|def ${fd.id}(${fd.params}): ${fd.returnType} = {
+            |"""
+
+        fd.precondition.foreach { case pre =>
+          p"""|  require($pre)
+              |"""
+        }
+
         fd.body match {
-          case Some(body) =>
-            pp(body, p)(lvl)
+          case Some(b) =>
+            p"  $b"
 
           case None =>
-            sb.append("[unknown function implementation]")
+            p"???"
         }
 
-      case TypeParameterDef(tp) =>
-        pp(tp, p)
+        p"""|
+            |}"""
+
+        fd.postcondition.foreach { case (id, post) =>
+          p"""| ensuring {
+              |  (${typed(id)}) => $post
+              |}"""
+        }
 
-      case TypeParameter(id) =>
-        pp(id, p)
+      case (tree: PrettyPrintable) => tree.printWith(ctx)
 
       case _ => sb.append("Tree? (" + tree.getClass + ")")
     }
-    if (opts.printPositions) {
-      ppos(tree.getPos)
-    }
   }
 
-  def ppos(p: Position) = p match {
-    case op: OffsetPosition =>
-      sb.append("@"+op.toString)
-    case rp: RangePosition =>
-      sb.append("@"+rp.focusBegin.toString+"--"+rp.focusEnd.toString)
-    case _ =>
+  def requiresBraces(ex: Tree, within: Option[Tree]): Boolean = (ex, within) match {
+    case (pa: PrettyPrintable, _) => pa.printRequiresBraces(within)
+    case (_, None) => false
+    case (_: Require, Some(_: Ensuring)) => false
+    case (_: Require, _) => true
+    case (_: Assert, Some(_: Definition)) => true
+    case (_, Some(_: Definition)) => false
+    case (_, Some(_: MatchExpr | _: MatchCase | _: Let | _: LetTuple | _: LetDef)) => false
+    case (_, _) => true
+  }
+
+  def precedence(ex: Expr): Int = ex match {
+    case (pa: PrettyPrintable) => pa.printPrecedence
+    case (_: ElementOfSet) => 0
+    case (_: Or) => 1
+    case (_: And) => 3
+    case (_: GreaterThan | _: GreaterEquals  | _: LessEquals | _: LessThan) => 4
+    case (_: Equals | _: Iff | _: Not) => 5
+    case (_: Plus | _: Minus | _: SetUnion| _: SetDifference) => 6
+    case (_: Times | _: Division | _: Modulo) => 7
+    case _ => 7
+  }
+
+  def requiresParentheses(ex: Tree, within: Option[Tree]): Boolean = (ex, within) match {
+    case (pa: PrettyPrintable, _) => pa.printRequiresParentheses(within)
+    case (_, None) => false
+    case (_, Some(_: Ensuring)) => false
+    case (_, Some(_: Assert)) => false
+    case (_, Some(_: Require)) => false
+    case (_, Some(_: Definition)) => false
+    case (_, Some(_: MatchExpr | _: MatchCase | _: Let | _: LetTuple | _: LetDef | _: IfExpr)) => false
+    case (_, Some(_: FunctionInvocation)) => false
+    case (ie: IfExpr, _) => true
+    case (e1: Expr, Some(e2: Expr)) if precedence(e1) > precedence(e2) => false
+    case (_, _) => true
   }
 }
 
 trait PrettyPrintable {
   self: Tree =>
 
-  def printWith(printer: PrettyPrinter)(implicit lvl: Int): Unit
+  def printWith(implicit pctx: PrinterContext): Unit
+
+  def printPrecedence: Int = 1000
+  def printRequiresParentheses(within: Option[Tree]): Boolean = false
+  def printRequiresBraces(within: Option[Tree]): Boolean = false
 }
 
 class EquivalencePrettyPrinter(opts: PrinterOptions) extends PrettyPrinter(opts) {
-  override def pp(tree: Tree, parent: Option[Tree])(implicit lvl: Int): Unit = {
+  override def pp(tree: Tree)(implicit ctx: PrinterContext): Unit = {
     tree match {
       case id: Identifier =>
-        sb.append(id.name)
+        p"""${id.name}"""
 
       case _ =>
-        super.pp(tree, parent)
+        super.pp(tree)
     }
   }
 }
@@ -580,7 +477,8 @@ abstract class PrettyPrinterFactory {
 
   def apply(tree: Tree, opts: PrinterOptions = PrinterOptions()): String = {
     val printer = create(opts)
-    printer.pp(tree, None)(opts.baseIndent)
+    val ctx = PrinterContext(tree, None, opts.baseIndent, printer)
+    printer.pp(tree)(ctx)
     printer.toString
   }
 
diff --git a/src/main/scala/leon/purescala/ScalaPrinter.scala b/src/main/scala/leon/purescala/ScalaPrinter.scala
index 2d1202d73b6248efebf4ad13061f3933f3b9a35d..755e48af7f9034dfe12814256bc25326c7130c41 100644
--- a/src/main/scala/leon/purescala/ScalaPrinter.scala
+++ b/src/main/scala/leon/purescala/ScalaPrinter.scala
@@ -8,375 +8,31 @@ import Trees._
 import TypeTrees._
 import Definitions._
 
+import PrinterHelpers._
+
 /** This pretty-printer only print valid scala syntax */
 class ScalaPrinter(opts: PrinterOptions, sb: StringBuffer = new StringBuffer) extends PrettyPrinter(opts, sb) {
-  import Common._
-  import Trees._
-  import TypeTrees._
-  import Definitions._
-
-  import java.lang.StringBuffer
-
-  override def ppBinary(left: Tree, right: Tree, op: String)(implicit  parent: Option[Tree], lvl: Int) {
-    pp(left, parent)
-    sb.append(op)
-    pp(right, parent)
-  }
-
-  // EXPRESSIONS
-  // all expressions are printed in-line
-  override def pp(tree: Tree, parent: Option[Tree])(implicit lvl: Int): Unit = {
-    implicit val p = Some(tree)
-
-    def optParentheses(body: => Unit) {
-      val rp = requiresParentheses(tree, parent)
-      if (rp) sb.append("(")
-      body
-      if (rp) sb.append(")")
-    }
-
-    def optBraces(body: Int => Unit) {
-      val rp = requiresBraces(tree, parent)
-      if (rp) {
-        sb.append("{\n")
-        ind(lvl+1)
-
-        body(lvl+1)
-
-        sb.append("\n")
-        ind(lvl)
-        sb.append("}\n")
-      } else {
-        body(lvl)
-      }
-    }
-
-    var printPos = opts.printPositions
 
+  override def pp(tree: Tree)(implicit ctx: PrinterContext): Unit = {
     tree match {
-      case LetTuple(ids,d,e) =>
-        optBraces { implicit lvl =>
-          sb.append("val (" )
-          for (((id, tpe), i) <- ids.map(id => (id, id.getType)).zipWithIndex) {
-              pp(id, p)
-              sb.append(": ")
-              pp(tpe, p)
-              if (i != ids.size-1) {
-                  sb.append(", ")
-              }
-          }
-          sb.append(") = ")
-          pp(d, p)
-          sb.append("\n")
-          ind
-          pp(e, p)
-          sb.append("\n")
-        }
-
-      case Let(b,d,e) =>
-        optBraces { implicit lvl =>
-          sb.append("val " + b + " = ")
-          pp(d, p)
-          sb.append("\n")
-          ind
-          pp(e, p)
-          sb.append("\n")
-        }
-
-      case LetDef(fd, body) =>
-        optBraces { implicit lvl =>
-          pp(fd, p)
-          sb.append("\n")
-          sb.append("\n")
-          ind
-          pp(body, p)
-        }
-
-      case And(exprs)           => optParentheses { ppNary(exprs, "", " && ", "") }
-      case Or(exprs)            => optParentheses { ppNary(exprs, "", " || ", "") }
-      case Not(Equals(l, r))    => optParentheses { ppBinary(l, r, " != ") }
-      case UMinus(expr)         => ppUnary(expr, "-(", ")")
-      case Equals(l,r)          => optParentheses { ppBinary(l, r, " == ") }
-
-      case IntLiteral(v)        => sb.append(v)
-      case BooleanLiteral(v)    => sb.append(v)
-      case StringLiteral(s)     => sb.append("\"" + s + "\"")
-      case UnitLiteral()        => sb.append("()")
-
-      /* These two aren't really supported in Scala, but we know how to encode them. */
-      case Implies(l,r)         => pp(Or(Not(l), r), p)
-      case Iff(l,r)             => optParentheses { ppBinary(l, r, " == ") }
-
-      case Tuple(exprs)         => ppNary(exprs, "(", ", ", ")")
-      case TupleSelect(t, i)    =>
-        pp(t, p)
-        sb.append("._" + i)
-
-      case Plus(l,r)            => optParentheses { ppBinary(l, r, " + ") }
-      case Minus(l,r)           => optParentheses { ppBinary(l, r, " - ") }
-      case Times(l,r)           => optParentheses { ppBinary(l, r, " * ") }
-      case Division(l,r)        => optParentheses { ppBinary(l, r, " / ") }
-      case Modulo(l,r)          => optParentheses { ppBinary(l, r, " % ") }
-      case LessThan(l,r)        => optParentheses { ppBinary(l, r, " < ") }
-      case GreaterThan(l,r)     => optParentheses { ppBinary(l, r, " > ") }
-      case LessEquals(l,r)      => optParentheses { ppBinary(l, r, " <= ") }
-      case GreaterEquals(l,r)   => optParentheses { ppBinary(l, r, " >= ") }
-      case fs @ FiniteSet(rs)        =>
-        if (rs.isEmpty) {
-          fs.getType match {
-            case SetType(b) =>
-              sb.append("Set[")
-              pp(b, p)
-              sb.append("]()")
-            case _ =>
-              sb.append("Set()")
-          }
-        } else {
-          ppNary(rs, "Set(", ", ", ")")
-        }
-      case FiniteMultiset(rs)   => ppNary(rs, "{|", ", ", "|}")
-      case EmptyMultiset(_)     => sys.error("Not Valid Scala")
-      case ElementOfSet(e, s)   => optParentheses { ppBinary(s, e, " contains ") }
-      case SetUnion(l,r)        => optParentheses { ppBinary(l, r, " ++ ") }
-      case SetDifference(l,r)   => optParentheses { ppBinary(l, r, " -- ") }
-      case SetIntersection(l,r) => optParentheses { ppBinary(l, r, " & ") }
-      case SetMin(s) =>
-        pp(s, p)
-        sb.append(".min")
-      case SetMax(s) =>
-        pp(s, p)
-        sb.append(".max")
-      case SetCardinality(t) => ppUnary(t, "", ".size")
-      case FiniteMap(rs) =>
-        sb.append("{")
-        val sz = rs.size
-        var c = 0
-        rs.foreach{case (k, v) => {
-          pp(k, p); sb.append(" -> "); pp(v, p); c += 1 ; if(c < sz) sb.append(", ")
-        }}
-        sb.append("}")
-
-      case MapGet(m,k) =>
-        pp(m, p)
-        ppNary(Seq(k), "(", ",", ")")
-
-      case MapIsDefinedAt(m,k) => {
-        pp(m, p)
-        sb.append(".isDefinedAt")
-        ppNary(Seq(k), "(", ",", ")")
-      }
-      case ArrayLength(a) =>
-        pp(a, p)
-        sb.append(".length")
-
-      case ArrayClone(a) =>
-        pp(a, p)
-        sb.append(".clone")
-
-      case ArrayFill(size, v) =>
-        sb.append("Array.fill(")
-        pp(size, p)
-        sb.append(")(")
-        pp(v, p)
-        sb.append(")")
-
-      case ArraySelect(ar, i) =>
-        pp(ar, p)
-        sb.append("(")
-        pp(i, p)
-        sb.append(")")
-
-      case ArrayUpdated(ar, i, v) =>
-        pp(ar, p)
-        sb.append(".updated(")
-        pp(i, p)
-        sb.append(", ")
-        pp(v, p)
-        sb.append(")")
-
-      case FiniteArray(exprs) =>
-        ppNary(exprs, "Array(", ", ", ")")
-
-      case Distinct(exprs) =>
-        sb.append("distinct")
-        ppNary(exprs, "(", ", ", ")")
-      
-      case IfExpr(c, t, e) =>
-        optParentheses {
-          sb.append("if (")
-          pp(c, p)
-          sb.append(") {\n")
-          ind(lvl+1)
-          pp(t, p)(lvl+1)
-          sb.append("\n")
-          ind(lvl)
-          sb.append("} else {\n")
-          ind(lvl+1)
-          pp(e, p)(lvl+1)
-          sb.append("\n")
-          ind(lvl)
-          sb.append("}")
-        }
-
-      case Choose(ids, pred) =>
-        optParentheses {
-          sb.append("choose { (")
-          for (((id, tpe), i) <- ids.map(id => (id, id.getType)).zipWithIndex) {
-              pp(id, p)
-              sb.append(": ")
-              pp(tpe, p)
-              if (i != ids.size-1) {
-                  sb.append(", ")
-              }
-          }
-          sb.append(") =>\n")
-          ind(lvl+1)
-          pp(pred, p)(lvl+1)
-          sb.append("\n")
-          ind(lvl)
-          sb.append("}")
-        }
-
-      case mex @ MatchExpr(s, csc) => {
-        optParentheses {
-          pp(s, p)
-          sb.append(" match {\n")
-
-          csc.foreach { cs =>
-            ind(lvl+1)
-            pp(cs, p)(lvl+1)
-            sb.append("\n")
-          }
-
-          ind(lvl)
-          sb.append("}")
-        }
-      }
-
-      case Not(expr) => sb.append("!"); optParentheses { pp(expr, p) }
-
-      case e @ Error(desc) => {
-        sb.append("leon.lang.error[")
-        pp(e.getType, p)
-        sb.append("](\"" + desc + "\")")
-      }
-
-      case (expr: PrettyPrintable) => expr.printWith(this)
-
-      // Definitions
-      case Program(id, modules) =>
-        ppNary(modules, "", "\n", "")
-
-      case ModuleDef(id, defs) =>
-        sb.append("object ")
-        pp(id, p)
-        sb.append(" {\n")
-
-        var c = 0
-        val sz = defs.size
-
-        defs.foreach(df => {
-          ind(lvl+1)
-          pp(df, p)(lvl+1)
-          if(c < sz - 1) {
-            sb.append("\n\n")
-          }
-          c = c + 1
-        })
-
-        sb.append("\n")
-        ind(lvl)
-        sb.append("}\n")
-
-      case vd: ValDef =>
-        pp(vd.id, p)
-        sb.append(": ")
-        pp(vd.tpe, p)
-
-      case fd: FunDef =>
-        sb.append("def ")
-        pp(fd.id, p)
-
-        if (fd.tparams.nonEmpty) {
-          ppNary(fd.tparams, "[", ", ", "]")
-        }
-
-        ppNary(fd.params, "(", ", ", ")")
-
-        sb.append(": ")
-        pp(fd.returnType, p)
-        sb.append(" = {\n")
-        ind(lvl+1)
-
-        fd.precondition match {
-          case None =>
-          case Some(prec) =>
-            sb.append("require(")
-            pp(prec, p)(lvl+1)
-            sb.append(");\n")
-            ind(lvl+1)
-        }
-
-        fd.body match {
-          case Some(body) =>
-            pp(body, p)(lvl+1)
-          case None =>
-            sb.append("???")
-        }
-
-        sb.append("\n")
-        ind
-
-        fd.postcondition match {
-          case None =>
-            sb.append("}")
-
-          case Some((id, postc)) =>
-            sb.append("} ensuring { ")
-            pp(Variable(id), p)
-            sb.append(" => ")
-            pp(postc, p)
-            sb.append(" }")
-        }
+      case Not(Equals(l, r))    => p"$l != $r"
+      case Iff(l,r)             => p"$l == $r"
+      case Implies(l,r)         => pp(Or(Not(l), r))
+      case Choose(vars, pred)   => p"choose(${typed(vars)} => $pred)" // TODO
+      case ElementOfSet(e,s)    => p"$s.contains(e)"
+      case SetUnion(l,r)        => p"$l ++ $r"
+      case MapUnion(l,r)        => p"$l ++ $r"
+      case SetDifference(l,r)   => p"$l -- $r"
+      case SetIntersection(l,r) => p"$l & $r"
+      case SetCardinality(s)    => p"$s.size"
+      case FiniteArray(exprs)   => p"Array($exprs)"
+      case Distinct(exprs)      => p"distinct($exprs)"
+      case Not(expr)            => p"!$expr"
 
       case _ =>
-        super.pp(tree, parent)(lvl)
-        // Parent will already print
-        printPos = false
-    }
-
-    if (printPos) {
-      ppos(tree.getPos)
+        super.pp(tree)
     }
   }
-
-  private def requiresBraces(ex: Tree, within: Option[Tree]): Boolean = (ex, within) match {
-    case (_, None) => false
-    case (_, Some(_: Definition)) => false
-    case (_, Some(_: MatchExpr | _: MatchCase | _: Let | _: LetTuple | _: LetDef)) => false
-    case (_, _) => true
-  }
-
-  private def precedence(ex: Expr): Int = ex match {
-    case (_: ElementOfSet) => 0
-    case (_: Or) => 1
-    case (_: And) => 3
-    case (_: GreaterThan | _: GreaterEquals  | _: LessEquals | _: LessThan) => 4
-    case (_: Equals | _: Iff | _: Not) => 5
-    case (_: Plus | _: Minus | _: SetUnion| _: SetDifference) => 6
-    case (_: Times | _: Division | _: Modulo) => 7
-    case _ => 7
-  }
-
-  private def requiresParentheses(ex: Tree, within: Option[Tree]): Boolean = (ex, within) match {
-    case (_, None) => false
-    case (_, Some(_: Definition)) => false
-    case (_, Some(_: MatchExpr | _: MatchCase | _: Let | _: LetTuple | _: LetDef | _: IfExpr)) => false
-    case (_, Some(_: FunctionInvocation)) => false
-    case (ie: IfExpr, _) => true
-    case (e1: Expr, Some(e2: Expr)) if precedence(e1) > precedence(e2) => false
-    case (_, _) => true
-  }
 }
 
 object ScalaPrinter extends PrettyPrinterFactory {
diff --git a/src/main/scala/leon/synthesis/FileInterface.scala b/src/main/scala/leon/synthesis/FileInterface.scala
index 899ce89edddad73f17c035f08fe65ade78485b49..4936b748c5fae3315bedc3288b7da440def0d43f 100644
--- a/src/main/scala/leon/synthesis/FileInterface.scala
+++ b/src/main/scala/leon/synthesis/FileInterface.scala
@@ -8,6 +8,7 @@ import purescala.Common.Tree
 import purescala.Definitions.FunDef
 import purescala.ScalaPrinter
 import purescala.PrinterOptions
+import purescala.PrinterContext
 
 import leon.utils.RangePosition
 
@@ -61,7 +62,7 @@ class FileInterface(reporter: Reporter) {
         val indent = lineChars.takeWhile(_ == ' ').size
 
         val p = new ScalaPrinter(PrinterOptions())
-        p.pp(toTree, Some(fromTree))(indent/2)
+        p.pp(toTree)(PrinterContext(toTree, Some(fromTree), indent/2, p))
 
         before + p.toString + after
 
diff --git a/src/main/scala/leon/xlang/Trees.scala b/src/main/scala/leon/xlang/Trees.scala
index 3532030ae7ab8860157b3d040cfa6187c75225a3..6da2d30f7d87b032de6e8ef1ed16849302baca3d 100644
--- a/src/main/scala/leon/xlang/Trees.scala
+++ b/src/main/scala/leon/xlang/Trees.scala
@@ -8,10 +8,11 @@ import purescala.TypeTrees._
 import purescala.Trees._
 import purescala.Definitions._
 import purescala.Extractors._
-import purescala.{PrettyPrinter, PrettyPrintable, ScalaPrinter}
+import purescala.{PrettyPrinter, PrettyPrintable, ScalaPrinter, PrinterContext}
 import utils._
 
 object Trees {
+  import purescala.PrinterHelpers._
 
   private def ind(sb: StringBuffer, lvl: Int) : StringBuffer = {
     sb.append("  " * lvl)
@@ -24,15 +25,10 @@ object Trees {
       Some((args :+ rest, exprs => Block(exprs.init, exprs.last)))
     }
 
-    def printWith(printer: PrettyPrinter)(implicit lvl: Int) {
-      printer.append("{\n")
-      (exprs :+ last).foreach(e => {
-        printer.ind(lvl+1)
-        printer.pp(e, Some(this))(lvl+1)
-        printer.append("\n")
-      })
-      printer.ind(lvl)
-      printer.append("}\n")
+    def printWith(implicit pctx: PrinterContext) {
+      p"""|{
+          |  ${nary(exprs :+ last, "\n")}
+          |}"""
     }
 
     val fixedType = last.getType
@@ -45,12 +41,8 @@ object Trees {
       Some((expr, Assignment(varId, _)))
     }
 
-    def printWith(printer: PrettyPrinter)(implicit lvl: Int) {
-      printer.append("(")
-      printer.append(varId.name)
-      printer.append(" = ")
-      printer.pp(expr, Some(this))
-      printer.append(")")
+    def printWith(implicit pctx: PrinterContext) {
+      p"$varId = $expr;"
     }
   }
 
@@ -66,24 +58,17 @@ object Trees {
       Some((cond, body, (t1, t2) => While(t1, t2).setInvariant(this.invariant).setPos(this)))
     }
 
-    def printWith(printer: PrettyPrinter)(implicit lvl: Int) {
+    def printWith(implicit pctx: PrinterContext) {
       invariant match {
-        case Some(inv) => {
-          printer.append("\n")
-          printer.ind
-          printer.append("@invariant: ")
-          printer.pp(inv, Some(this))
-          printer.append("\n")
-          printer.ind
-        }
+        case Some(inv) =>
+          p"""|@invariant($inv)
+              |"""
         case None =>
       }
-      printer.append("while(")
-      printer.pp(cond, Some(this))
-      printer.append(")\n")
-      printer.ind(lvl+1)
-      printer.pp(body, Some(this))(lvl+1)
-      printer.append("\n")
+
+      p"""|while($cond) {
+          |  $body
+          |}"""
     }
   }
 
@@ -92,20 +77,15 @@ object Trees {
       Some((pred, (expr: Expr) => Epsilon(expr).setType(this.getType).setPos(this)))
     }
 
-    def printWith(printer: PrettyPrinter)(implicit lvl: Int) {
-      printer match {
-        case _ =>
-          printer.append("epsilon(x" + this.getPos.line + "_" + this.getPos.col + ". ")
-          printer.pp(pred, Some(this))
-          printer.append(")")
-      }
+    def printWith(implicit pctx: PrinterContext) {
+      p"epsilon(x${getPos.line}_${getPos.col}. $pred)"
     }
   }
 
   case class EpsilonVariable(pos: Position) extends Expr with Terminal with PrettyPrintable{
 
-    def printWith(printer: PrettyPrinter)(implicit lvl: Int) {
-      printer.append("x" + pos.line + "_" + pos.col)
+    def printWith(implicit pctx: PrinterContext) {
+      p"x${pos.line}_${pos.col}"
     }
   }
 
@@ -120,30 +100,11 @@ object Trees {
       Some((expr, body, (e: Expr, b: Expr) => LetVar(binders, e, b)))
     }
 
-    def printWith(printer: PrettyPrinter)(implicit lvl: Int) {
-      printer match {
-        case _: ScalaPrinter =>
-          val LetVar(b,d,e) = this
-          printer.append("locally {\n")
-          printer.ind(lvl+1)
-          printer.append("var " + b + " = ")
-          printer.pp(d, Some(this))(lvl+1)
-          printer.append("\n")
-          printer.ind(lvl+1)
-          printer.pp(e, Some(this))(lvl+1)
-          printer.nl
-          printer.append("}")
-          printer.nl
-
-        case _ =>
-          val LetVar(b,d,e) = this
-          printer.append("(letvar (" + b + " := ");
-          printer.pp(d, Some(this))
-          printer.append(") in\n")
-          printer.ind(lvl+1)
-          printer.pp(e, Some(this))(lvl+1)
-          printer.append(")")
-      }
+    def printWith(implicit pctx: PrinterContext) {
+      p"""|locally {
+          |  var $binder = $value
+          |  $body
+          |}"""
     }
   }
 
@@ -152,18 +113,11 @@ object Trees {
       Some((expr, (e: Expr) => Waypoint(i, e)))
     }
 
-    def printWith(printer: PrettyPrinter)(implicit lvl: Int) {
-      printer match {
-        case _ =>
-          printer.append("waypoint_" + i + "(")
-          printer.pp(expr, Some(this))
-          printer.append(")")
-      }
+    def printWith(implicit pctx: PrinterContext) {
+      p"waypoint_$i($expr)"
     }
   }
 
-  //the difference between ArrayUpdate and ArrayUpdated is that the former has a side effect while the latter is the functional version
-  //ArrayUpdate should be eliminated soon in the analysis while ArrayUpdated is kept all the way to the backend
   case class ArrayUpdate(array: Expr, index: Expr, newValue: Expr) extends Expr with FixedType with NAryExtractable with PrettyPrintable {
     val fixedType = UnitType
 
@@ -172,12 +126,8 @@ object Trees {
       Some((Seq(t1,t2,t3), (as: Seq[Expr]) => ArrayUpdate(as(0), as(1), as(2))))
     }
 
-    def printWith(printer: PrettyPrinter)(implicit lvl: Int) {
-      printer.pp(array, Some(this))
-      printer.append("(")
-      printer.pp(index, Some(this))
-      printer.append(") = ")
-      printer.pp(newValue, Some(this))
+    def printWith(implicit pctx: PrinterContext) {
+      p"$array($index) = $newValue"
     }
   }