Skip to content
Snippets Groups Projects
StringRender.scala 27.95 KiB
/* Copyright 2009-2016 EPFL, Lausanne */

package leon
package synthesis
package rules

import scala.annotation.tailrec
import scala.collection.mutable.ListBuffer
import evaluators.AbstractEvaluator
import purescala.Definitions._
import purescala.Common._
import purescala.Types._
import purescala.Constructors._
import purescala.Expressions._
import purescala.Extractors._
import purescala.TypeOps
import purescala.DefOps
import purescala.ExprOps
import purescala.SelfPrettyPrinter
import solvers.ModelBuilder
import solvers.string.StringSolver
import programsets.DirectProgramSet
import programsets.JoinProgramSet

/** A template generator for a given type tree. 
  * Extend this class using a concrete type tree,
  * Then use the apply method to get a hole which can be a placeholder for holes in the template.
  * Each call to the `.instantiate` method of the subsequent Template will provide different instances at each position of the hole.
  */
abstract class TypedTemplateGenerator(t: TypeTree) {
  import StringRender.WithIds
  /** Provides a hole which can be */
  def apply(f: Expr => Expr): TemplateGenerator = {
    val id = FreshIdentifier("ConstToInstantiate", t, true)
    new TemplateGenerator(f(Variable(id)), id, t)
  }
  def nested(f: Expr => WithIds[Expr]): TemplateGenerator = {
    val id = FreshIdentifier("ConstToInstantiate", t, true)
    val res = f(Variable(id))
    new TemplateGenerator(res._1, id, t, res._2)
  }
  class TemplateGenerator(template: Expr, varId: Identifier, t: TypeTree, initialHoles: List[Identifier] = Nil) {
    private val optimizationVars = ListBuffer[Identifier]() ++= initialHoles
    private def Const: Variable = {
      val res = FreshIdentifier("const", t, true)
      optimizationVars += res
      Variable(res)
    }
    private def instantiate: Expr = {
      ExprOps.postMap({
        case Variable(id) if id == varId => Some(Const)
        case _ => None
      })(template)
    }
    def instantiateWithVars: WithIds[Expr] = (instantiate, optimizationVars.toList)
  }
}

/**
 * @author Mikael
 */
case object StringRender extends Rule("StringRender") {
  type WithIds[T] = (T, List[Identifier])
  
  var EDIT_ME = "_edit_me_"
  
  var enforceDefaultStringMethodsIfAvailable = true
  var enforceSelfStringMethodsIfAvailable = false
  
  val booleanTemplate = (a: Expr) => StringTemplateGenerator(Hole => IfExpr(a, Hole, Hole))
  
  import StringSolver.{StringFormToken, StringForm, Problem => SProblem, Equation, Assignment}
  
  /** Augment the left-hand-side to have possible function calls, such as x + "const" + customToString(_) ...
   *  Function calls will be eliminated when converting to a valid problem.
   */
  sealed abstract class AugmentedStringFormToken
  case class RegularStringFormToken(e: StringFormToken) extends AugmentedStringFormToken
  case class OtherStringFormToken(e: Expr) extends AugmentedStringFormToken
  type AugmentedStringForm = List[AugmentedStringFormToken]
  
  /** Augments the right-hand-side to have possible function calls, such as "const" + customToString(_) ... 
   *  Function calls will be eliminated when converting to a valid problem.
   */
  sealed abstract class AugmentedStringChunkRHS
  case class RegularStringChunk(e: String) extends AugmentedStringChunkRHS
  case class OtherStringChunk(e: Expr) extends AugmentedStringChunkRHS
  type AugmentedStringLiteral = List[AugmentedStringChunkRHS]
  
  /** Converts an expression to a stringForm, suitable for StringSolver */
  def toStringForm(e: Expr, acc: List[AugmentedStringFormToken] = Nil)(implicit hctx: SearchContext): Option[AugmentedStringForm] = e match {
    case StringLiteral(s) => 
      Some(RegularStringFormToken(Left(s))::acc)
    case Variable(id) => Some(RegularStringFormToken(Right(id))::acc)
    case StringConcat(lhs, rhs) => 
      toStringForm(rhs, acc).flatMap(toStringForm(lhs, _))
    case e:Application => Some(OtherStringFormToken(e)::acc)
    case e:FunctionInvocation => Some(OtherStringFormToken(e)::acc)
    case _ => None
  }
  
  /** Returns the string associated to the expression if it is computable */
  def toStringLiteral(e: Expr): Option[AugmentedStringLiteral] = e match {
    case StringLiteral(s) => Some(List(RegularStringChunk(s)))
    case StringConcat(lhs, rhs) =>
      toStringLiteral(lhs).flatMap(k => toStringLiteral(rhs).map(l => (k.init, k.last, l) match {
        case (kinit, RegularStringChunk(s), RegularStringChunk(sp)::ltail) =>
          kinit ++ (RegularStringChunk(s + sp)::ltail)
        case _ => k ++ l
      }))
    case e: Application => Some(List(OtherStringChunk(e)))
    case e: FunctionInvocation => Some(List(OtherStringChunk(e)))
    case _ => None
  }
  
  /** Converts an equality AugmentedStringForm == AugmentedStringLiteral to a list of equations
   *  For that, splits both strings on function applications. If they yield the same value, we can split, else it fails. */
  def toEquations(lhs: AugmentedStringForm, rhs: AugmentedStringLiteral): Option[List[Equation]] = {
    def rec(lhs: AugmentedStringForm, rhs: AugmentedStringLiteral,
        accEqs: ListBuffer[Equation], accLeft: ListBuffer[StringFormToken], accRight: StringBuffer): Option[List[Equation]] = (lhs, rhs) match {
      case (Nil, Nil) =>
        (accLeft.toList, accRight.toString) match {
          case (Nil, "") => Some(accEqs.toList)
          case (lhs, rhs) => Some((accEqs += ((lhs, rhs))).toList)
        }
      case (OtherStringFormToken(e)::lhstail, OtherStringChunk(f)::rhstail) =>
        if(ExprOps.canBeHomomorphic(e, f).nonEmpty) {
          rec(lhstail, rhstail, accEqs += ((accLeft.toList, accRight.toString)), ListBuffer[StringFormToken](), new StringBuffer)
        } else None
      case (OtherStringFormToken(e)::lhstail, Nil) =>
        None
      case (Nil, OtherStringChunk(f)::rhstail) =>
        None
      case (lhs, RegularStringChunk(s)::rhstail) =>
        rec(lhs, rhstail, accEqs, accLeft, accRight append s)
      case (RegularStringFormToken(e)::lhstail, rhs) =>
        rec(lhstail, rhs, accEqs, accLeft += e, accRight)
    }
    rec(lhs, rhs, ListBuffer[Equation](), ListBuffer[StringFormToken](), new StringBuffer)
  }
  
  /** Returns a stream of assignments compatible with input/output examples for the given template */
  def findAssignments(p: Program, inputs: Seq[Identifier], examples: ExamplesBank, template: Expr)(implicit hctx: SearchContext): Stream[Map[Identifier, String]] = {

    val e = new AbstractEvaluator(hctx, p)
    
    @tailrec def gatherEquations(s: List[InOutExample], acc: ListBuffer[Equation] = ListBuffer()): Option[SProblem] = s match {
      case Nil => Some(acc.toList)
      case InOutExample(in, rhExpr)::q =>
        if(rhExpr.length == 1) {
          val model = new ModelBuilder
          model ++= inputs.zip(in)
          val modelResult = model.result()
          val evalResult =  e.eval(template, modelResult)
          evalResult.result match {
            case None =>
              hctx.reporter.info("Eval = None : ["+template+"] in ["+inputs.zip(in)+"]")
              hctx.reporter.info(evalResult)
              None
            case Some((sfExpr, abstractSfExpr)) =>
              //ctx.reporter.debug("Eval = ["+sfExpr+"] (from "+abstractSfExpr+")")
              val sf = toStringForm(sfExpr)
              val rhs = toStringLiteral(rhExpr.head)
              (sf, rhs) match {
                case (Some(sfget), Some(rhsget)) =>
                  toEquations(sfget, rhsget) match {
                    case Some(equations) =>
                      gatherEquations(q, acc ++= equations)
                    case None =>
                      hctx.reporter.info("Could not extract equations from ["+sfget+"] == ["+rhsget+"]\n coming from ... == " + rhExpr)
                    None
                  }
                case _ =>
                  hctx.reporter.info("sf empty or rhs empty ["+sfExpr+"] => ["+sf+"] in ["+rhs+"]")
                  None
              }
          }
        } else {
          hctx.reporter.info("RHS.length != 1 : ["+rhExpr+"]")
          None 
        }
    }
    gatherEquations((examples.valids ++ examples.invalids).collect{ case io:InOutExample => io }.toList) match {
      case Some(problem) => StringSolver.solve(problem)
      case None => Stream.empty
    }
  }
  
  /** With a given (template, fundefs, consts) will find consts so that (expr, funs) passes all the examples */
  def findSolutions(examples: ExamplesBank, template: Stream[WithIds[Expr]], funDefs: Seq[(FunDef, Stream[WithIds[Expr]])])(implicit hctx: SearchContext, p: Problem): RuleApplication = {
    // Fun is a stream of many function applications.
    val funs= JoinProgramSet.direct(funDefs.map(fbody => fbody._2.map((fbody._1, _))).map(d => DirectProgramSet(d)))
    
    val wholeTemplates = JoinProgramSet.direct(funs, DirectProgramSet(template))
    
    def computeSolutions(funDefsBodies: Seq[(FunDef, WithIds[Expr])], template: WithIds[Expr]): Stream[Assignment] = {
      val funDefs = for((funDef, body) <- funDefsBodies) yield  { funDef.body = Some(body._1); funDef }
      val newProgram = DefOps.addFunDefs(hctx.program, funDefs, hctx.functionContext)
      findAssignments(newProgram, p.as.filter{ x => !x.getType.isInstanceOf[FunctionType] }, examples, template._1)
    }
    
    val tagged_solutions =
      for{(funDefs, template) <- wholeTemplates.programs} yield computeSolutions(funDefs, template).map((funDefs, template, _))
    
    solutionStreamToRuleApplication(p, leon.utils.StreamUtils.interleave(tagged_solutions))(hctx.program)
  }
  
  /** Find ambiguities not containing _edit_me_ to ask to the user */
  def askQuestion(input: List[Identifier], r: RuleClosed)(implicit c: LeonContext, p: Program): List[disambiguation.Question[StringLiteral]] = {
    //if !s.contains(EDIT_ME)
    val qb = new disambiguation.QuestionBuilder(input, r.solutions, (seq: Seq[Expr], expr: Expr) => expr match {
      case s @ StringLiteral(slv) if !slv.contains(EDIT_ME) => Some(s)
      case _ => None
    })
    qb.result()
  }

  /** Converts the stream of solutions to a RuleApplication */
  def solutionStreamToRuleApplication(p: Problem, solutions: Stream[(Seq[(FunDef, WithIds[Expr])], WithIds[Expr], Assignment)])(implicit program: Program): RuleApplication = {
    if(solutions.isEmpty) RuleFailed() else {
      RuleClosed(
          for((funDefsBodies, (singleTemplate, ids), assignment) <- solutions) yield {
            val fds = for((fd, (body, ids)) <- funDefsBodies) yield {
              val initMap = ids.map(_ -> StringLiteral(EDIT_ME)).toMap
              fd.body = Some(ExprOps.simplifyString(ExprOps.replaceFromIDs(initMap ++ assignment.mapValues(StringLiteral), body)))
              fd
            }
            val initMap = ids.map(_ -> StringLiteral(EDIT_ME)).toMap
            val term = ExprOps.simplifyString(ExprOps.replaceFromIDs(initMap ++ assignment.mapValues(StringLiteral), singleTemplate))
            val (finalTerm, finalDefs) = makeFunctionsUnique(term, fds.toSet)
            
            Solution(BooleanLiteral(true), finalDefs, finalTerm)
          })
    }
  }
  
  /** Crystallizes a solution so that it will not me modified if the body of fds is modified. */
  def makeFunctionsUnique(term: Expr, fds: Set[FunDef])(implicit program: Program): (Expr, Set[FunDef]) = {
    var transformMap = Map[FunDef, FunDef]()
    def mapExpr(body: Expr): Expr = {
      ExprOps.preMap((e: Expr) => e match {
        case FunctionInvocation(TypedFunDef(fd, _), args) if fds(fd) => Some(functionInvocation(getMapping(fd), args))
        case e => None
      })(body)
    }
    
    def getMapping(fd: FunDef): FunDef = {
      transformMap.getOrElse(fd, {
        val newfunDef = new FunDef(fd.id.freshen, fd.tparams, fd.params, fd.returnType) // With empty body
        transformMap += fd -> newfunDef
        newfunDef.body = fd.body.map(mapExpr _)
        newfunDef
      })
    }
    
    (mapExpr(term), fds.map(getMapping _))
  }
  
  
  object StringTemplateGenerator extends TypedTemplateGenerator(StringType)
  
  case class DependentType(caseClassParent: Option[TypeTree], inputName: String, typeToConvert: TypeTree)
  
  type MapFunctions = Map[DependentType, (FunDef, Stream[WithIds[Expr]])]
  
  /** Result of the current synthesis process */
  class StringSynthesisResult(
      val adtToString: MapFunctions,
      val funNames: Set[String]
  ) {
    def add(d: DependentType, f: FunDef, s: Stream[WithIds[Expr]]): StringSynthesisResult = {
      new StringSynthesisResult(adtToString + (d -> ((f, s))), funNames + f.id.name)
    }
    def freshFunName(s: String): String = {
      if(!funNames(s)) return s
      var i = 1
      var s0 = s
      do {
        i += 1
        s0 = s + i
      } while(funNames(s+i))
      s0
    }
  }
  type StringConverters = Map[TypeTree, List[Expr => Expr]]
  
  /** Companion object to create a StringSynthesisContext */
  object StringSynthesisContext {
    def empty(
        abstractStringConverters: StringConverters,
        originalInputs: Set[Expr],
        provided_functions: Seq[Identifier])(implicit hctx: SearchContext) =
      new StringSynthesisContext(None, new StringSynthesisResult(Map(), Set()),
        abstractStringConverters,
        originalInputs,
        provided_functions)
  }
  
  /** Context for the current synthesis process */
  class StringSynthesisContext(
      val currentCaseClassParent: Option[TypeTree],
      val result: StringSynthesisResult,
      val abstractStringConverters: StringConverters,
      val originalInputs: Set[Expr],
      val provided_functions: Seq[Identifier]
  )(implicit hctx: SearchContext) {
    def add(d: DependentType, f: FunDef, s: Stream[WithIds[Expr]]): StringSynthesisContext = {
      new StringSynthesisContext(currentCaseClassParent, result.add(d, f, s),
          abstractStringConverters,
          originalInputs,
          provided_functions)
    }
    def copy(currentCaseClassParent: Option[TypeTree]=currentCaseClassParent, result: StringSynthesisResult = result): StringSynthesisContext = 
      new StringSynthesisContext(currentCaseClassParent, result,
          abstractStringConverters,
          originalInputs,
          provided_functions)
    def freshFunName(s: String) = result.freshFunName(s)
  }
  
  /** Creates an empty function definition for the dependent type */
  def createEmptyFunDef(ctx: StringSynthesisContext, tpe: DependentType)(implicit hctx: SearchContext): FunDef = {
    def defaultFunName(t: TypeTree) = t match {
      case AbstractClassType(c, d) => c.id.asString(hctx)
      case CaseClassType(c, d) => c.id.asString(hctx)
      case t => t.asString(hctx)
    }
    
    val funName2 = tpe.caseClassParent match {
      case None => defaultFunName(tpe.typeToConvert) + "_" + tpe.inputName + "_s" 
      case Some(t) => defaultFunName(tpe.typeToConvert) + "In"+defaultFunName(t) + "_" + tpe.inputName + "_s" 
    }
    val funName3 = funName2.replaceAll("[^a-zA-Z0-9_]","")
    val funName = funName3(0).toLower + funName3.substring(1) 
    val funId = FreshIdentifier(ctx.freshFunName(funName), alwaysShowUniqueID = true)
    val argId= FreshIdentifier(tpe.typeToConvert.asString(hctx).toLowerCase()(0).toString, tpe.typeToConvert)
    val tparams = hctx.functionContext.tparams
    new FunDef(funId, tparams, ValDef(argId) :: ctx.provided_functions.map(ValDef).toList, StringType) // Empty function.
  }
  
  /** Pre-updates of the function definition */
  def preUpdateFunDefBody(tpe: DependentType, fd: FunDef, ctx: StringSynthesisContext): StringSynthesisContext = {
    ctx.result.adtToString.get(tpe) match {
      case None => ctx.add(tpe, fd, Stream.Empty)
      case Some(_) => ctx
    }
  }

  /** Assembles multiple MatchCase to a singleMatchExpr using the function definition fd */
  private val mergeMatchCases = (fd: FunDef) => (cases: Seq[WithIds[MatchCase]]) => (MatchExpr(Variable(fd.params(0).id), cases.map(_._1)), cases.flatMap(_._2).toList)
  
  /** Returns a (possibly recursive) template which can render the inputs in their order.
    * Returns an expression and path-dependent pretty printers which can be used.
    * 
    * @param inputs The list of inputs. Make sure each identifier is typed.
    **/
  def createFunDefsTemplates(
      ctx: StringSynthesisContext,
      inputs: Seq[Expr])(implicit hctx: SearchContext): (Stream[WithIds[Expr]], StringSynthesisResult) = {
    
    def extractCaseVariants(cct: CaseClassType, ctx: StringSynthesisContext)
      : (Stream[WithIds[MatchCase]], StringSynthesisResult) = cct match {
      case CaseClassType(ccd: CaseClassDef, tparams2) =>
        val typeMap = ccd.tparams.zip(tparams2).toMap
        val fields = ccd.fields.map(vd => TypeOps.instantiateType(vd.id, typeMap) )
        val pattern = CaseClassPattern(None, ccd.typed(tparams2), fields.map(k => WildcardPattern(Some(k))))
        val (rhs, result) = createFunDefsTemplates(ctx.copy(currentCaseClassParent=Some(cct)), fields.map(Variable)) // Invoke functions for each of the fields.
        val newCases = rhs.map(e => (MatchCase(pattern, None, e._1), e._2))
        (newCases, result)
    }
    
    /* Returns a constant pattern matching in which all classes are rendered using their proper name
     * For example:
     * {{{
     * sealed abstract class Thread
     * case class T1() extends Thread()
     * case Class T2() extends Thread()
     * }}}
     * Will yield the following expression:
     * {{{t match {
     *   case T1() => "T1"
     *   case T2() => "T2"
     * }
     * }}}
     * 
     */
    def constantPatternMatching(fd: FunDef, act: AbstractClassType): WithIds[MatchExpr] = {
      val cases = (ListBuffer[WithIds[MatchCase]]() /: act.knownCCDescendants) {
        case (acc, cct @ CaseClassType(ccd, tparams2)) =>
          val typeMap = ccd.tparams.zip(tparams2).toMap
          val fields = ccd.fields.map(vd => TypeOps.instantiateType(vd.id, typeMap) )
          val pattern = CaseClassPattern(None, ccd.typed(tparams2), fields.map(k => WildcardPattern(Some(k))))
          val rhs = StringLiteral(ccd.id.asString)
          MatchCase(pattern, None, rhs)
          acc += ((MatchCase(pattern, None, rhs), Nil))
        case (acc, e) => hctx.reporter.fatalError("Could not handle this class definition for string rendering " + e)
      }
      mergeMatchCases(fd)(cases)
    }
    
    /* Returns a list of expressions converting the list of inputs to string.
     * Each expression is tagged with a list of identifiers, which is the list of variables which need to be found.
     * @return Along with the list, an updated function definitions to transform (parent-dependent) types to strings */
    @tailrec def gatherInputs(
        ctx: StringSynthesisContext,
        inputs: List[Expr],
        result: ListBuffer[Stream[WithIds[Expr]]] = ListBuffer()): (List[Stream[WithIds[Expr]]], StringSynthesisResult) = inputs match {
      case Nil => (result.toList, ctx.result)
      case input::q => 
        val dependentType = DependentType(ctx.currentCaseClassParent, input.asString(hctx.program)(hctx), input.getType)
        ctx.result.adtToString.get(dependentType) match {
        case Some(fd) =>
          gatherInputs(ctx, q, result += Stream((functionInvocation(fd._1, input::ctx.provided_functions.toList.map(Variable)), Nil)))
        case None => // No function can render the current type.
          // We should not rely on calling the original function on the first line of the body of the function itself.
          val exprs1s = (new SelfPrettyPrinter)
            .allowFunction(hctx.functionContext)
            .excludeFunction(hctx.functionContext)
            .withPossibleParameters.prettyPrintersForType(input.getType)(hctx, hctx.program)
            .map{ case (l, identifiers) => (application(l, Seq(input)), identifiers) } // Use already pre-defined pretty printers.
          val exprs1 = exprs1s.toList.sortBy{ case (Lambda(_, FunctionInvocation(tfd, _)), _) if tfd.fd == hctx.functionContext => 0 case _ => 1}
          val exprs2 = ctx.abstractStringConverters.getOrElse(input.getType, Nil).map(f => (f(input), List[Identifier]()))
          val defaultConverters: Stream[WithIds[Expr]] = exprs1.toStream #::: exprs2.toStream
          val recursiveConverters: Stream[WithIds[Expr]] =
            (new SelfPrettyPrinter)
            .prettyPrinterFromCandidate(hctx.functionContext, input.getType)(hctx, hctx.program)
            .map(l => (application(l, Seq(input)), List[Identifier]()))
            
          def mergeResults(templateConverters: =>Stream[WithIds[Expr]]): Stream[WithIds[Expr]] = {
            if(defaultConverters.isEmpty) templateConverters
            else if(enforceDefaultStringMethodsIfAvailable) {
              if(enforceSelfStringMethodsIfAvailable)
                recursiveConverters #::: defaultConverters 
              else {
                defaultConverters #::: recursiveConverters
              }
            }
            else  recursiveConverters #::: defaultConverters #::: templateConverters
          }
          
          input.getType match {
            case StringType =>
              gatherInputs(ctx, q, result +=
                mergeResults(Stream((input, Nil),
                        (functionInvocation(
                            hctx.program.library.escape.get, List(input)): Expr, Nil))))
            case BooleanType =>
              val (bTemplate, vs) = booleanTemplate(input).instantiateWithVars
              gatherInputs(ctx, q, result += mergeResults(Stream((BooleanToString(input), Nil), (bTemplate, vs))))
            case WithStringconverter(converter) => // Base case
              gatherInputs(ctx, q, result += mergeResults(Stream((converter(input), Nil))))
            case t: ClassType =>
              if(enforceDefaultStringMethodsIfAvailable && defaultConverters.nonEmpty) {
                gatherInputs(ctx, q, result += defaultConverters)
              } else {
                // Create the empty function body and updates the assignments parts.
                val fd = createEmptyFunDef(ctx, dependentType)
                val ctx2 = preUpdateFunDefBody(dependentType, fd, ctx) // Inserts the FunDef in the assignments so that it can already be used.
                t.root match {
                  case act @ AbstractClassType(acd, tps) =>
                    // Create a complete FunDef body with pattern matching
                    
                    val allKnownDescendantsAreCCAndHaveZeroArgs = act.knownCCDescendants.forall {
                      case CaseClassType(ccd, tparams2) => ccd.fields.isEmpty
                      case _ => false
                    }
                    
                    //TODO: Test other templates not only with Wilcard patterns, but more cases options for non-recursive classes (e.g. Option, Boolean, Finite parameterless case classes.)
                    val (ctx3, cases) = ((ctx2, ListBuffer[Stream[WithIds[MatchCase]]]()) /: act.knownCCDescendants) {
                      case ((ctx22, acc), cct @ CaseClassType(ccd, tparams2)) =>
                        val (newCases, result) = extractCaseVariants(cct, ctx22)
                        val ctx23 = ctx22.copy(result = result)
                        (ctx23, acc += newCases)
                      case ((adtToString, acc), e) => hctx.reporter.fatalError("Could not handle this class definition for string rendering " + e)
                    }
                    
                    val allMatchExprsEnd = JoinProgramSet(cases.map(DirectProgramSet(_)), mergeMatchCases(fd)).programs // General pattern match expressions
                    val allMatchExprs = if(allKnownDescendantsAreCCAndHaveZeroArgs) {
                      Stream(constantPatternMatching(fd, act)) ++ allMatchExprsEnd
                    } else allMatchExprsEnd
                    gatherInputs(ctx3.add(dependentType, fd, allMatchExprs), q,
                        result += Stream((functionInvocation(fd, input::ctx.provided_functions.toList.map(Variable)), Nil)))
                  case cct @ CaseClassType(ccd, tparams2) =>
                    val (newCases, result3) = extractCaseVariants(cct, ctx2)
                    val allMatchExprs = newCases.map(acase => mergeMatchCases(fd)(Seq(acase)))
                    gatherInputs(ctx2.copy(result = result3).add(dependentType, fd, allMatchExprs), q,
                        result += Stream((functionInvocation(fd, input::ctx.provided_functions.toList.map(Variable)), Nil)))
                }
              }
            case TypeParameter(t) =>
              if(defaultConverters.isEmpty) {
                hctx.reporter.fatalError("Could not handle type parameter for string rendering " + t)
              } else {
                gatherInputs(ctx, q, result += mergeResults(Stream.empty))
              }
              
            case t: SetType =>
              gatherInputs(ctx, q, result += defaultConverters)
            case t: BagType =>
              gatherInputs(ctx, q, result += defaultConverters)
            case t: MapType =>
              gatherInputs(ctx, q, result += defaultConverters)
            case tpe => 
              hctx.reporter.fatalError("Could not handle class type for string rendering " + tpe)
          }
      }
    }
    var ctx2 = ctx // We gather the functions only once.
    // Flatten tuple types
    val newInputs = inputs.flatMap{ case input =>
      input.getType match {
        case TupleType(bases) =>
          val blength = bases.length
          for(index <- 1 to blength) yield tupleSelect(input, index, blength)
        case _ => List(input)
      }
    }
    // Get all permutations
    val templates = for(inputPermutation <- newInputs.permutations.toStream) yield {
      val (exprs, result3) = gatherInputs(ctx2, inputPermutation.toList)
      ctx2 = ctx2.copy(result=result3)
      /* Add post, pre and in-between holes, and returns a single expr along with the new assignments. */
      val template: Stream[WithIds[Expr]] = exprs match {
        case Nil =>
          Stream(StringTemplateGenerator(Hole => Hole).instantiateWithVars)
        case exprList =>
          JoinProgramSet(exprList.map(DirectProgramSet(_)), (exprs: Seq[WithIds[Expr]]) =>
              StringTemplateGenerator.nested(Hole => {
                val res = ((StringConcat(Hole, exprs.head._1), exprs.head._2) /: exprs.tail) {
                  case ((finalExpr, finalIds), (expr, ids)) => (StringConcat(StringConcat(finalExpr, Hole), expr), finalIds ++ ids)
                }
                (StringConcat(res._1, Hole), res._2)
              }).instantiateWithVars
          ).programs
      }
      template
    }
    (templates.flatten, ctx2.result)
  }
  
  def instantiateOn(implicit hctx: SearchContext, p: Problem): Traversable[RuleInstantiation] = {
    //hctx.reporter.debug("StringRender:Output variables="+p.xs+", their types="+p.xs.map(_.getType))
    p.xs match {
      case List(IsTyped(v, StringType)) =>
        val examplesFinder = new ExamplesFinder(hctx, hctx.program)
          .setKeepAbstractExamples(true)
          .setEvaluationFailOnChoose(true)

        val examples = examplesFinder.extractFromProblem(p)
        
        val abstractStringConverters: StringConverters = p.as.flatMap { case x =>
          x.getType match {
            case FunctionType(Seq(aType), StringType) =>
              List((aType, (arg: Expr) => application(Variable(x), Seq(arg))))
            case _ => Nil
          }
        }.groupBy(_._1).mapValues(_.map(_._2))
       
        val (inputVariables, functionVariables) = p.as.partition ( x => x.getType match {
          case f: FunctionType => false
          case _ => true
        })
        
        val ruleInstantiations = ListBuffer[RuleInstantiation]()
        val originalInputs = inputVariables.map(Variable)
        ruleInstantiations += RuleInstantiation("String conversion") {
          val (expr, synthesisResult) = createFunDefsTemplates(
            StringSynthesisContext.empty(
              abstractStringConverters,
              originalInputs.toSet,
              functionVariables
            ), originalInputs
          )
          val funDefs = synthesisResult.adtToString
          
          /*val toDebug: String = (("\nInferred functions:" /: funDefs)( (t, s) =>
                t + "\n" + s._2._1.toString
              ))*/
          //hctx.reporter.debug("Inferred expression:\n" + expr + toDebug)
          
          findSolutions(examples, expr, funDefs.values.toSeq)
        }
        
        ruleInstantiations.toList
        
      case _ => Nil
    }
  }
}