From 462b61579f0f4b2e71b042320d65cd2a78d77c4b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ali=20Sinan=20K=C3=B6ksal?= <alisinan@gmail.com>
Date: Mon, 4 Jul 2011 21:20:58 +0000
Subject: [PATCH] Generation of code for lazySolve yielding LTuples

---
 cp-demo/LazyVars.scala |  12 +++-
 src/cp/LTrees.scala    | 137 ++++++++++++++++++++++++++++++++++-------
 src/cp/Terms.scala     | 133 ++++++++++++++++++++++++++++-----------
 src/cp/Utils.scala     |  42 ++++++++++++-
 4 files changed, 263 insertions(+), 61 deletions(-)

diff --git a/cp-demo/LazyVars.scala b/cp-demo/LazyVars.scala
index 6e01ef2c9..1f3e6785f 100644
--- a/cp-demo/LazyVars.scala
+++ b/cp-demo/LazyVars.scala
@@ -7,7 +7,7 @@ object LazyVars {
   def chooseInt(lower: Int, upper: Int) = ((x: Int) => x >= lower && x <= upper).lazyFindAll
 
   def main(args: Array[String]): Unit = {
-    f3()
+    f4()
   }
 
   def f1() {
@@ -44,4 +44,14 @@ object LazyVars {
       println("i, (a, b):" + i.value + ", " + pair.value)
     }
   }
+
+  def f4() {
+    val p = ((x: Int, y: Int) => x > 0 && y == 2 * x && x <= 5).lazySolve
+    val other = ((z: Int) => z == p._1 + p._2).lazySolve
+    println("x + y: " + other.value)
+    val x = p._1
+    val y = p._2
+    println("x: " + x.value)
+    println("y: " + y.value)
+  }
 }
diff --git a/src/cp/LTrees.scala b/src/cp/LTrees.scala
index ea5b6f5c6..40f781675 100644
--- a/src/cp/LTrees.scala
+++ b/src/cp/LTrees.scala
@@ -13,11 +13,126 @@ import scala.collection.generic.CanBuildFrom
 import scala.collection.GenTraversableOnce
 
 object LTrees {
+  /** Handler for converting values from Expr to Scala and reporting forced
+   * values */
   trait LHandler[T] {
     def convert(s: Seq[Expr]): T
     def enqueueAsForced(ids: Seq[Identifier], values: Seq[Expr]): Unit
   }
 
+  /* Symbolic variables */
+  object L {
+    def unapply(l: L[_]): Option[Seq[Identifier]] = {
+      if (l == null) None else Some(l.ids)
+    }
+  }
+
+  class L[T](handler: LHandler[T], val ids: Seq[Identifier]) extends {
+    import ConstraintSolving.GlobalContext
+
+    var cache: Option[T] = None
+
+    def value: T = cache match {
+      case Some(value) => value
+      case None =>
+        val model = GlobalContext.findValues(ids)
+        val toRet = handler.convert(model)
+        handler.enqueueAsForced(ids, model)
+        cache = Some(toRet)
+        toRet
+    }
+  }
+
+  trait LTuple[T] {
+    /* Forces this tuple to have a value */
+    def value: T
+  }
+
+  /** GENERATED CODE ***/
+
+  class LTuple1[T1](l1: L[T1]) extends LTuple[(T1)] {
+    def _1: L[T1] = l1
+    def value: (T1) = (_1.value)
+  }
+  
+  class LTuple2[T1,T2](l1: L[T1],l2: L[T2]) extends LTuple[(T1,T2)] {
+    def _1: L[T1] = l1
+    def _2: L[T2] = l2
+    def value: (T1,T2) = (_1.value,_2.value)
+  }
+  
+  class LTuple3[T1,T2,T3](l1: L[T1],l2: L[T2],l3: L[T3]) extends LTuple[(T1,T2,T3)] {
+    def _1: L[T1] = l1
+    def _2: L[T2] = l2
+    def _3: L[T3] = l3
+    def value: (T1,T2,T3) = (_1.value,_2.value,_3.value)
+  }
+  
+  class LTuple4[T1,T2,T3,T4](l1: L[T1],l2: L[T2],l3: L[T3],l4: L[T4]) extends LTuple[(T1,T2,T3,T4)] {
+    def _1: L[T1] = l1
+    def _2: L[T2] = l2
+    def _3: L[T3] = l3
+    def _4: L[T4] = l4
+    def value: (T1,T2,T3,T4) = (_1.value,_2.value,_3.value,_4.value)
+  }
+  
+  class LTuple5[T1,T2,T3,T4,T5](l1: L[T1],l2: L[T2],l3: L[T3],l4: L[T4],l5: L[T5]) extends LTuple[(T1,T2,T3,T4,T5)] {
+    def _1: L[T1] = l1
+    def _2: L[T2] = l2
+    def _3: L[T3] = l3
+    def _4: L[T4] = l4
+    def _5: L[T5] = l5
+    def value: (T1,T2,T3,T4,T5) = (_1.value,_2.value,_3.value,_4.value,_5.value)
+  }
+  
+  class LTuple6[T1,T2,T3,T4,T5,T6](l1: L[T1],l2: L[T2],l3: L[T3],l4: L[T4],l5: L[T5],l6: L[T6]) extends LTuple[(T1,T2,T3,T4,T5,T6)] {
+    def _1: L[T1] = l1
+    def _2: L[T2] = l2
+    def _3: L[T3] = l3
+    def _4: L[T4] = l4
+    def _5: L[T5] = l5
+    def _6: L[T6] = l6
+    def value: (T1,T2,T3,T4,T5,T6) = (_1.value,_2.value,_3.value,_4.value,_5.value,_6.value)
+  }
+  
+  class LTuple7[T1,T2,T3,T4,T5,T6,T7](l1: L[T1],l2: L[T2],l3: L[T3],l4: L[T4],l5: L[T5],l6: L[T6],l7: L[T7]) extends LTuple[(T1,T2,T3,T4,T5,T6,T7)] {
+    def _1: L[T1] = l1
+    def _2: L[T2] = l2
+    def _3: L[T3] = l3
+    def _4: L[T4] = l4
+    def _5: L[T5] = l5
+    def _6: L[T6] = l6
+    def _7: L[T7] = l7
+    def value: (T1,T2,T3,T4,T5,T6,T7) = (_1.value,_2.value,_3.value,_4.value,_5.value,_6.value,_7.value)
+  }
+  
+  class LTuple8[T1,T2,T3,T4,T5,T6,T7,T8](l1: L[T1],l2: L[T2],l3: L[T3],l4: L[T4],l5: L[T5],l6: L[T6],l7: L[T7],l8: L[T8]) extends LTuple[(T1,T2,T3,T4,T5,T6,T7,T8)] {
+    def _1: L[T1] = l1
+    def _2: L[T2] = l2
+    def _3: L[T3] = l3
+    def _4: L[T4] = l4
+    def _5: L[T5] = l5
+    def _6: L[T6] = l6
+    def _7: L[T7] = l7
+    def _8: L[T8] = l8
+    def value: (T1,T2,T3,T4,T5,T6,T7,T8) = (_1.value,_2.value,_3.value,_4.value,_5.value,_6.value,_7.value,_8.value)
+  }
+  
+  class LTuple9[T1,T2,T3,T4,T5,T6,T7,T8,T9](l1: L[T1],l2: L[T2],l3: L[T3],l4: L[T4],l5: L[T5],l6: L[T6],l7: L[T7],l8: L[T8],l9: L[T9]) extends LTuple[(T1,T2,T3,T4,T5,T6,T7,T8,T9)] {
+    def _1: L[T1] = l1
+    def _2: L[T2] = l2
+    def _3: L[T3] = l3
+    def _4: L[T4] = l4
+    def _5: L[T5] = l5
+    def _6: L[T6] = l6
+    def _7: L[T7] = l7
+    def _8: L[T8] = l8
+    def _9: L[T9] = l9
+    def value: (T1,T2,T3,T4,T5,T6,T7,T8,T9) = (_1.value,_2.value,_3.value,_4.value,_5.value,_6.value,_7.value,_8.value,_9.value)
+  }
+  /** END OF GENERATED CODE ***/
+
+  /** A stream for symbolic variables */
   class LStream[T](val constraint: (L[T]) => Constraint[T]) extends scala.collection.generic.FilterMonadic[L[T], Traversable[L[T]]] {
 
     import ConstraintSolving.GlobalContext
@@ -135,26 +250,4 @@ object LTrees {
     }
   }
 
-  /* L for Lazy, L for Logic */
-  object L {
-    def unapply(l: L[_]): Option[Seq[Identifier]] = {
-      if (l == null) None else Some(l.ids)
-    }
-  }
-
-  class L[T](handler: LHandler[T], val ids: Seq[Identifier]) extends {
-    import ConstraintSolving.GlobalContext
-
-    var cache: Option[T] = None
-
-    def value: T = cache match {
-      case Some(value) => value
-      case None =>
-        val model = GlobalContext.findValues(ids)
-        val toRet = handler.convert(model)
-        handler.enqueueAsForced(ids, model)
-        cache = Some(toRet)
-        toRet
-    }
-  }
 }
diff --git a/src/cp/Terms.scala b/src/cp/Terms.scala
index f41007ca8..19c8221eb 100644
--- a/src/cp/Terms.scala
+++ b/src/cp/Terms.scala
@@ -35,44 +35,11 @@ object Terms {
       findAllExprSeq(asConstraint(this)).map(convertingFunction(_))
     }
 
-    def lazySolve(implicit asConstraint: (Term[T,R]) => Constraint[T]): L[T] = {
-      def removeGuard(g: Identifier): Unit = {
-        GlobalContext.kill(g)
-        val noMoreLive = Not(Variable(g))
-        GlobalContext.enqueueAssert(noMoreLive)
-      }
-
-      val constraint = asConstraint(this)
-      val (newConsts, newExpr) = combineConstraint(constraint)
-
-      GlobalContext.initializeIfNeeded(constraint.program)
-
-      val newGuard = FreshIdentifier("live", true).setType(BooleanType)
-      GlobalContext.addLive(newGuard)
-
-      val toAssert = Implies(Variable(newGuard), newExpr)
-      if (GlobalContext.checkAssumptions(toAssert)) {
-        // we can return an L value
-        val handler = new LHandler[T] {
-          def convert(s: Seq[Expr]): T = convertingFunction(s)
-          def enqueueAsForced(ids: Seq[Identifier], values: Seq[Expr]): Unit = {
-            val haveValues = And((ids zip values) map {
-              case (i, v) => Equals(Variable(i), v)
-            })
-            GlobalContext.enqueueAssert(haveValues)
-            removeGuard(newGuard)
-          }
-        }
-
-        new L[T](handler, newConsts)
-      } else {
-        // constraint is not satisfiable, remove guard from context
-        removeGuard(newGuard)
-        throw new UnsatisfiableConstraintException
-      }
+    def lazySolve(implicit asConstraint: (Term[T,R]) => Constraint[T]): LTuple[T] = {
+      throw new Exception()
     }
 
-    def lazyFind(implicit asConstraint: (Term[T,R]) => Term[T,Boolean]): Option[L[T]] = {
+    def lazyFind(implicit asConstraint: (Term[T,R]) => Term[T,Boolean]): Option[LTuple[T]] = {
       try {
         Some(this.lazySolve)
       } catch {
@@ -86,6 +53,46 @@ object Terms {
     }
   }
 
+  def removeGuard(g: Identifier): Unit = {
+    GlobalContext.kill(g)
+    val noMoreLive = Not(Variable(g))
+    GlobalContext.enqueueAssert(noMoreLive)
+  }
+
+  def createL[T](constraint: Constraint[_], constant: Identifier, guard: Identifier): L[T] = {
+    val handler = new LHandler[T] {
+      def convert(s: Seq[Expr]): T = constraint.converter.exprSeq2scala1[T](s)
+      def enqueueAsForced(ids: Seq[Identifier], values: Seq[Expr]): Unit = {
+        val haveValues = And((ids zip values) map {
+          case (i, v) => Equals(Variable(i), v)
+        })
+        GlobalContext.enqueueAssert(haveValues)
+        removeGuard(guard)
+      }
+    }
+    new L[T](handler, Seq(constant))
+  }
+
+  def constantsAndGuards[T](constraint: Constraint[T]): (Seq[Identifier], Seq[Identifier]) = {
+    val (newConsts, newExpr) = combineConstraint(constraint)
+
+    GlobalContext.initializeIfNeeded(constraint.program)
+
+    val newGuards = newConsts map (nc => FreshIdentifier("live", true).setType(BooleanType))
+    newGuards foreach GlobalContext.addLive
+
+    val toAssert = Implies(Or(newGuards map (ng => Variable(ng))), newExpr)
+    if (GlobalContext.checkAssumptions(toAssert)) {
+      // we can return variables and their guards
+      (newConsts, newGuards)
+    } else {
+      // constraint is not satisfiable, remove guard from context
+      newGuards foreach removeGuard
+      throw new UnsatisfiableConstraintException
+    }
+  }
+
+
   /** This construct represents a constraint with an expression to minimize */
   abstract class MinConstraint[T](cons : Constraint[_], minFunc : IntTerm[_]) {
     val convertingFunction : (Seq[Expr] => T)
@@ -186,6 +193,12 @@ object Terms {
       MinConstraint1[T1](asConstraint(this), minFunc)
     }
   
+    def lazySolve(implicit asConstraint: (Term1[T1,R]) => Constraint1[T1]): LTuple1[T1] = {
+      val constraint = asConstraint(this)
+      val (constants, guards) = constantsAndGuards(constraint)
+      new LTuple1[T1](createL[T1](constraint, constants(0), guards(0)))
+    }
+  
     def compose0[A1](other : Term1[A1, T1]) : Term1[A1, R] = {
       val (newExpr, newTypes) = Terms.compose(other, this, 0, 1, 1)
       Term1(this.program, newExpr, if (this.scalaFunction == null || other.scalaFunction == null) null else (x_0 : A1) => this.scalaFunction(other.scalaFunction(x_0)), newTypes, this.converter)
@@ -253,6 +266,12 @@ object Terms {
       MinConstraint2[T1,T2](asConstraint(this), minFunc)
     }
   
+    def lazySolve(implicit asConstraint: (Term2[T1,T2,R]) => Constraint2[T1,T2]): LTuple2[T1,T2] = {
+      val constraint = asConstraint(this)
+      val (constants, guards) = constantsAndGuards(constraint)
+      new LTuple2[T1,T2](createL[T1](constraint, constants(0), guards(0)),createL[T2](constraint, constants(1), guards(1)))
+    }
+  
     def compose0[A1](other : Term1[A1, T1]) : Term2[A1, T2, R] = {
       val (newExpr, newTypes) = Terms.compose(other, this, 0, 1, 2)
       Term2(this.program, newExpr, if (this.scalaFunction == null || other.scalaFunction == null) null else (x_0 : A1, x_1 : T2) => this.scalaFunction(other.scalaFunction(x_0), x_1), newTypes, this.converter)
@@ -355,6 +374,12 @@ object Terms {
       MinConstraint3[T1,T2,T3](asConstraint(this), minFunc)
     }
   
+    def lazySolve(implicit asConstraint: (Term3[T1,T2,T3,R]) => Constraint3[T1,T2,T3]): LTuple3[T1,T2,T3] = {
+      val constraint = asConstraint(this)
+      val (constants, guards) = constantsAndGuards(constraint)
+      new LTuple3[T1,T2,T3](createL[T1](constraint, constants(0), guards(0)),createL[T2](constraint, constants(1), guards(1)),createL[T3](constraint, constants(2), guards(2)))
+    }
+  
     def compose0[A1](other : Term1[A1, T1]) : Term3[A1, T2, T3, R] = {
       val (newExpr, newTypes) = Terms.compose(other, this, 0, 1, 3)
       Term3(this.program, newExpr, if (this.scalaFunction == null || other.scalaFunction == null) null else (x_0 : A1, x_1 : T2, x_2 : T3) => this.scalaFunction(other.scalaFunction(x_0), x_1, x_2), newTypes, this.converter)
@@ -482,6 +507,12 @@ object Terms {
       MinConstraint4[T1,T2,T3,T4](asConstraint(this), minFunc)
     }
   
+    def lazySolve(implicit asConstraint: (Term4[T1,T2,T3,T4,R]) => Constraint4[T1,T2,T3,T4]): LTuple4[T1,T2,T3,T4] = {
+      val constraint = asConstraint(this)
+      val (constants, guards) = constantsAndGuards(constraint)
+      new LTuple4[T1,T2,T3,T4](createL[T1](constraint, constants(0), guards(0)),createL[T2](constraint, constants(1), guards(1)),createL[T3](constraint, constants(2), guards(2)),createL[T4](constraint, constants(3), guards(3)))
+    }
+  
     def compose0[A1](other : Term1[A1, T1]) : Term4[A1, T2, T3, T4, R] = {
       val (newExpr, newTypes) = Terms.compose(other, this, 0, 1, 4)
       Term4(this.program, newExpr, if (this.scalaFunction == null || other.scalaFunction == null) null else (x_0 : A1, x_1 : T2, x_2 : T3, x_3 : T4) => this.scalaFunction(other.scalaFunction(x_0), x_1, x_2, x_3), newTypes, this.converter)
@@ -624,6 +655,12 @@ object Terms {
       MinConstraint5[T1,T2,T3,T4,T5](asConstraint(this), minFunc)
     }
   
+    def lazySolve(implicit asConstraint: (Term5[T1,T2,T3,T4,T5,R]) => Constraint5[T1,T2,T3,T4,T5]): LTuple5[T1,T2,T3,T4,T5] = {
+      val constraint = asConstraint(this)
+      val (constants, guards) = constantsAndGuards(constraint)
+      new LTuple5[T1,T2,T3,T4,T5](createL[T1](constraint, constants(0), guards(0)),createL[T2](constraint, constants(1), guards(1)),createL[T3](constraint, constants(2), guards(2)),createL[T4](constraint, constants(3), guards(3)),createL[T5](constraint, constants(4), guards(4)))
+    }
+  
     def compose0[A1](other : Term1[A1, T1]) : Term5[A1, T2, T3, T4, T5, R] = {
       val (newExpr, newTypes) = Terms.compose(other, this, 0, 1, 5)
       Term5(this.program, newExpr, if (this.scalaFunction == null || other.scalaFunction == null) null else (x_0 : A1, x_1 : T2, x_2 : T3, x_3 : T4, x_4 : T5) => this.scalaFunction(other.scalaFunction(x_0), x_1, x_2, x_3, x_4), newTypes, this.converter)
@@ -771,6 +808,12 @@ object Terms {
       MinConstraint6[T1,T2,T3,T4,T5,T6](asConstraint(this), minFunc)
     }
   
+    def lazySolve(implicit asConstraint: (Term6[T1,T2,T3,T4,T5,T6,R]) => Constraint6[T1,T2,T3,T4,T5,T6]): LTuple6[T1,T2,T3,T4,T5,T6] = {
+      val constraint = asConstraint(this)
+      val (constants, guards) = constantsAndGuards(constraint)
+      new LTuple6[T1,T2,T3,T4,T5,T6](createL[T1](constraint, constants(0), guards(0)),createL[T2](constraint, constants(1), guards(1)),createL[T3](constraint, constants(2), guards(2)),createL[T4](constraint, constants(3), guards(3)),createL[T5](constraint, constants(4), guards(4)),createL[T6](constraint, constants(5), guards(5)))
+    }
+  
     def compose0[A1](other : Term1[A1, T1]) : Term6[A1, T2, T3, T4, T5, T6, R] = {
       val (newExpr, newTypes) = Terms.compose(other, this, 0, 1, 6)
       Term6(this.program, newExpr, if (this.scalaFunction == null || other.scalaFunction == null) null else (x_0 : A1, x_1 : T2, x_2 : T3, x_3 : T4, x_4 : T5, x_5 : T6) => this.scalaFunction(other.scalaFunction(x_0), x_1, x_2, x_3, x_4, x_5), newTypes, this.converter)
@@ -913,6 +956,12 @@ object Terms {
       MinConstraint7[T1,T2,T3,T4,T5,T6,T7](asConstraint(this), minFunc)
     }
   
+    def lazySolve(implicit asConstraint: (Term7[T1,T2,T3,T4,T5,T6,T7,R]) => Constraint7[T1,T2,T3,T4,T5,T6,T7]): LTuple7[T1,T2,T3,T4,T5,T6,T7] = {
+      val constraint = asConstraint(this)
+      val (constants, guards) = constantsAndGuards(constraint)
+      new LTuple7[T1,T2,T3,T4,T5,T6,T7](createL[T1](constraint, constants(0), guards(0)),createL[T2](constraint, constants(1), guards(1)),createL[T3](constraint, constants(2), guards(2)),createL[T4](constraint, constants(3), guards(3)),createL[T5](constraint, constants(4), guards(4)),createL[T6](constraint, constants(5), guards(5)),createL[T7](constraint, constants(6), guards(6)))
+    }
+  
     def compose0[A1](other : Term1[A1, T1]) : Term7[A1, T2, T3, T4, T5, T6, T7, R] = {
       val (newExpr, newTypes) = Terms.compose(other, this, 0, 1, 7)
       Term7(this.program, newExpr, if (this.scalaFunction == null || other.scalaFunction == null) null else (x_0 : A1, x_1 : T2, x_2 : T3, x_3 : T4, x_4 : T5, x_5 : T6, x_6 : T7) => this.scalaFunction(other.scalaFunction(x_0), x_1, x_2, x_3, x_4, x_5, x_6), newTypes, this.converter)
@@ -1040,6 +1089,12 @@ object Terms {
       MinConstraint8[T1,T2,T3,T4,T5,T6,T7,T8](asConstraint(this), minFunc)
     }
   
+    def lazySolve(implicit asConstraint: (Term8[T1,T2,T3,T4,T5,T6,T7,T8,R]) => Constraint8[T1,T2,T3,T4,T5,T6,T7,T8]): LTuple8[T1,T2,T3,T4,T5,T6,T7,T8] = {
+      val constraint = asConstraint(this)
+      val (constants, guards) = constantsAndGuards(constraint)
+      new LTuple8[T1,T2,T3,T4,T5,T6,T7,T8](createL[T1](constraint, constants(0), guards(0)),createL[T2](constraint, constants(1), guards(1)),createL[T3](constraint, constants(2), guards(2)),createL[T4](constraint, constants(3), guards(3)),createL[T5](constraint, constants(4), guards(4)),createL[T6](constraint, constants(5), guards(5)),createL[T7](constraint, constants(6), guards(6)),createL[T8](constraint, constants(7), guards(7)))
+    }
+  
     def compose0[A1](other : Term1[A1, T1]) : Term8[A1, T2, T3, T4, T5, T6, T7, T8, R] = {
       val (newExpr, newTypes) = Terms.compose(other, this, 0, 1, 8)
       Term8(this.program, newExpr, if (this.scalaFunction == null || other.scalaFunction == null) null else (x_0 : A1, x_1 : T2, x_2 : T3, x_3 : T4, x_4 : T5, x_5 : T6, x_6 : T7, x_7 : T8) => this.scalaFunction(other.scalaFunction(x_0), x_1, x_2, x_3, x_4, x_5, x_6, x_7), newTypes, this.converter)
@@ -1142,6 +1197,12 @@ object Terms {
       MinConstraint9[T1,T2,T3,T4,T5,T6,T7,T8,T9](asConstraint(this), minFunc)
     }
   
+    def lazySolve(implicit asConstraint: (Term9[T1,T2,T3,T4,T5,T6,T7,T8,T9,R]) => Constraint9[T1,T2,T3,T4,T5,T6,T7,T8,T9]): LTuple9[T1,T2,T3,T4,T5,T6,T7,T8,T9] = {
+      val constraint = asConstraint(this)
+      val (constants, guards) = constantsAndGuards(constraint)
+      new LTuple9[T1,T2,T3,T4,T5,T6,T7,T8,T9](createL[T1](constraint, constants(0), guards(0)),createL[T2](constraint, constants(1), guards(1)),createL[T3](constraint, constants(2), guards(2)),createL[T4](constraint, constants(3), guards(3)),createL[T5](constraint, constants(4), guards(4)),createL[T6](constraint, constants(5), guards(5)),createL[T7](constraint, constants(6), guards(6)),createL[T8](constraint, constants(7), guards(7)),createL[T9](constraint, constants(8), guards(8)))
+    }
+  
     def compose0[A1](other : Term1[A1, T1]) : Term9[A1, T2, T3, T4, T5, T6, T7, T8, T9, R] = {
       val (newExpr, newTypes) = Terms.compose(other, this, 0, 1, 9)
       Term9(this.program, newExpr, if (this.scalaFunction == null || other.scalaFunction == null) null else (x_0 : A1, x_1 : T2, x_2 : T3, x_3 : T4, x_4 : T5, x_5 : T6, x_6 : T7, x_7 : T8, x_8 : T9) => this.scalaFunction(other.scalaFunction(x_0), x_1, x_2, x_3, x_4, x_5, x_6, x_7, x_8), newTypes, this.converter)
diff --git a/src/cp/Utils.scala b/src/cp/Utils.scala
index cf306265c..085c38dbd 100644
--- a/src/cp/Utils.scala
+++ b/src/cp/Utils.scala
@@ -33,6 +33,15 @@ object Utils {
   MinConstraint%d[%s](asConstraint(this), minFunc)
 }""" format (intTraitName, curriedImplicit2Constraint, arity, traitArgParamsString, arity, traitArgParamsString)
 
+        val createLCalls = (1 to arity) map (i => """createL[T%d](constraint, constants(%d), guards(%d))""" format (i, i - 1, i - 1))
+        val createLCallsString = createLCalls.mkString(",")
+        val lazySolveMethod =
+"""def lazySolve(implicit asConstraint: (%s) => Constraint%d[%s]): LTuple%d[%s] = {
+  val constraint = asConstraint(this)
+  val (constants, guards) = constantsAndGuards(constraint)
+  new LTuple%d[%s](%s)
+}""" format (traitName, arity, traitArgParamsString, arity, traitArgParamsString, arity, traitArgParamsString, createLCallsString)
+
         val composeMethods = (for (arityF <- 1 to (maxArity - arity + 1)) yield {
           for (index <- 0 until arity) yield {
             val fParams = (1 to arityF).map("A" + _)
@@ -77,6 +86,8 @@ object Utils {
 
 %s
 
+%s
+
 %s
 }""" format (traitName, termClassParamTuple, "R", arity, traitParams.mkString(","), 
   arity, traitArgParamsString, 
@@ -84,7 +95,7 @@ object Utils {
   termClassParamTuple, "R", 
   evaluatorArgs.mkString(","),
   applyParams.mkString(", "), applyArgs.mkString(", "), 
-  indent(orMethod), indent(andMethod), indent(notMethod), indent(minimizingMethod), indent(composeMethods))
+  indent(orMethod), indent(andMethod), indent(notMethod), indent(minimizingMethod), indent(lazySolveMethod), indent(composeMethods))
         
         termTraitString
       }
@@ -191,7 +202,31 @@ object Utils {
     }
   }
 
+  object GenerateLTuples {
+    def apply(maxArity: Int): String = {
+      val classes = for (arity <- 1 to maxArity) yield {
+        val typeParams = (1 to arity) map ("T" + _)
+        val typeParamString = typeParams.mkString(",")
+        val arguments = (1 to arity) map (i => "l%d: L[T%d]" format (i, i))
+        val argumentString = arguments.mkString(",")
+        val componentMethods = (1 to arity) map (i => "def _%d: L[T%d] = l%d" format (i, i, i))
+        val componentMethodString = componentMethods.mkString("\n")
+        val valueMethodParams = (1 to arity) map (i => "_%d.value" format (i))
+        val valueMethodParamString = valueMethodParams.mkString(",")
+        val valueMethod = "def value: (%s) = (%s)" format (typeParamString, valueMethodParamString)
+"""class LTuple%d[%s](%s) extends LTuple[(%s)] {
+%s
+%s
+}""" format (arity, typeParamString, argumentString, typeParamString, indent(componentMethodString), indent(valueMethod))
+      }
+
+      classes.mkString("\n\n")
+    }
+  }
+
   def main(args: Array[String]) : Unit = {
+    if (args.size != 1)
+      throw new Exception("Enter an arity for code generation")
     val termTraits = GenerateTerms(args(0).toInt)
     val termObjects = GenerateTermObjects(args(0).toInt)
     val minConstraintsClasses = GenerateMinConstraintClasses(args(0).toInt)
@@ -199,7 +234,10 @@ object Utils {
 
     val converterMethods = GenerateConverterMethods(args(0).toInt)
 
+    val ltupleClasses = GenerateLTuples(args(0).toInt)
+
     val everything = Seq(typeAliases, termTraits, termObjects, minConstraintsClasses).mkString("\n\n")
-    println(indent(everything))
+    // println(indent(everything))
+    println(indent(ltupleClasses))
   }
 }
-- 
GitLab