diff --git a/src/main/scala/leon/Main.scala b/src/main/scala/leon/Main.scala index 26fcab817fe63ef8d1e6e634cfa74e84ccedab19..e05adbf6c181c96ad69d4096cd866115b0edb6f8 100644 --- a/src/main/scala/leon/Main.scala +++ b/src/main/scala/leon/Main.scala @@ -21,7 +21,8 @@ object Main { xlang.XlangAnalysisPhase, synthesis.SynthesisPhase, termination.TerminationPhase, - verification.AnalysisPhase + verification.AnalysisPhase, + refactor.RefactorPhase ) } @@ -32,6 +33,7 @@ object Main { lazy val topLevelOptions : Set[LeonOptionDef] = Set( LeonFlagOptionDef ("termination", "--termination", "Check program termination"), + LeonFlagOptionDef ("refactor", "--refactor", "Refactoring/Repair"), LeonFlagOptionDef ("synthesis", "--synthesis", "Partial synthesis of choose() constructs"), LeonFlagOptionDef ("xlang", "--xlang", "Support for extra program constructs (imperative,...)"), LeonValueOptionDef("solvers", "--solvers=s1,s2", "Use solvers s1 and s2 (fairz3,enum,smt)"), @@ -140,6 +142,8 @@ object Main { for(opt <- leonOptions) opt match { case LeonFlagOption("termination", value) => settings = settings.copy(termination = value) + case LeonFlagOption("refactor", value) => + settings = settings.copy(refactor = value) case LeonFlagOption("synthesis", value) => settings = settings.copy(synthesis = value) case LeonFlagOption("xlang", value) => @@ -205,6 +209,7 @@ object Main { import termination.TerminationPhase import xlang.XlangAnalysisPhase import verification.AnalysisPhase + import refactor.RefactorPhase val pipeBegin : Pipeline[List[String],Program] = ExtractionPhase andThen @@ -213,6 +218,8 @@ object Main { val pipeProcess: Pipeline[Program, Any] = { if (settings.synthesis) { SynthesisPhase + } else if (settings.refactor) { + RefactorPhase } else if (settings.termination) { TerminationPhase } else if (settings.xlang) { diff --git a/src/main/scala/leon/Settings.scala b/src/main/scala/leon/Settings.scala index 4ad409af285d8aaaebf72fd9d82254dcb8e94d93..31cf87ca4ecb8b93ee4a0da74377f27c8360ed3b 100644 --- a/src/main/scala/leon/Settings.scala +++ b/src/main/scala/leon/Settings.scala @@ -8,6 +8,7 @@ case class Settings( val strictCompilation: Boolean = true, // Terminates Leon in case an error occured during extraction val debugSections: Set[DebugSection] = Set(), // Enables debug message for the following sections val termination: Boolean = false, + val refactor: Boolean = false, val synthesis: Boolean = false, val xlang: Boolean = false, val verify: Boolean = true, diff --git a/src/main/scala/leon/purescala/Constructors.scala b/src/main/scala/leon/purescala/Constructors.scala index 6aeda471b2cde381144e476b0313cf70254f3ff5..8dd01c3a9eaaa7c054be18ff2fb8853b98138ab0 100644 --- a/src/main/scala/leon/purescala/Constructors.scala +++ b/src/main/scala/leon/purescala/Constructors.scala @@ -32,4 +32,10 @@ object Constructors { Tuple(Seq(ch)) } } + + def tupleWrap(es: Seq[Expr]): Expr = if (es.size > 1) { + Tuple(es) + } else { + es.head + } } diff --git a/src/main/scala/leon/purescala/DefOps.scala b/src/main/scala/leon/purescala/DefOps.scala index c374d664a60e7cb644ec668d0d2dc90c40c6321a..1a3876714548db58912a028c489e70774137913f 100644 --- a/src/main/scala/leon/purescala/DefOps.scala +++ b/src/main/scala/leon/purescala/DefOps.scala @@ -78,6 +78,12 @@ object DefOps { } } + def funDefsFromMain(p: Program): Set[FunDef] = { + p.units.filter(_.isMainUnit).toSet.flatMap{ (u: UnitDef) => + u.definedFunctions + } + } + def visibleFunDefsFromMain(p: Program): Set[FunDef] = { p.units.filter(_.isMainUnit).toSet.flatMap{ (u: UnitDef) => visibleFunDefsFrom(u) ++ u.definedFunctions @@ -188,13 +194,6 @@ object DefOps { df <- descendDefs(startingPoint,path) ) yield df ) orElse { - // Now starts the package nonsense - def isPrefixOf[A](pre : List[A],l : List[A]) : Boolean = (pre,l) match { - case (Nil, _) => true - case (hp :: tp, hl :: tl) if hp == hl => isPrefixOf(tp,tl) - case _ => false - } - val program = inProgram(base) val currentPack = inPackage(base) val knownPacks = program.units map { _.pack } @@ -209,11 +208,11 @@ object DefOps { import scala.util.Try val (packagePart, objectPart) = Try { // Find the longest match, then re-attach the current package. - val pack = subPacks filter { isPrefixOf(_,fullNameList) } maxBy(_.length) + val pack = subPacks filter { fullNameList.startsWith(_) } maxBy(_.length) ( currentPack ++ pack, fullNameList drop pack.length ) } orElse { Try { // In this case, try to find a package that fits beginning from the root package - val pack = knownPacks filter { isPrefixOf(_,fullNameList) } maxBy(_.length) + val pack = knownPacks filter { fullNameList.startsWith(_) } maxBy(_.length) (pack, fullNameList drop pack.length) }} getOrElse { // No package matches diff --git a/src/main/scala/leon/purescala/TypeTreeOps.scala b/src/main/scala/leon/purescala/TypeTreeOps.scala index fd35346f36b2a3e16758bcf85509396feb09a400..a9e761bb0c717500d47f7243376ff7e77048541d 100644 --- a/src/main/scala/leon/purescala/TypeTreeOps.scala +++ b/src/main/scala/leon/purescala/TypeTreeOps.scala @@ -35,6 +35,15 @@ object TypeTreeOps { } } else { (tpe, stpe) match { + case (t, tp1: TypeParameter) => + if (freeParams contains tp1) { + Some(Map(tp1 -> t)) + } else if (tp1 == t) { + Some(Map()) + } else { + None + } + case (tp1: TypeParameter, t) => if (freeParams contains tp1) { Some(Map(tp1 -> t)) diff --git a/src/main/scala/leon/refactor/RefactorPhase.scala b/src/main/scala/leon/refactor/RefactorPhase.scala new file mode 100644 index 0000000000000000000000000000000000000000..77e12c2d41f94a5a2bf63c263e3f98cb3694b0bc --- /dev/null +++ b/src/main/scala/leon/refactor/RefactorPhase.scala @@ -0,0 +1,50 @@ +/* Copyright 2009-2014 EPFL, Lausanne */ + +package leon +package refactor + +import purescala.Common._ +import purescala.Definitions._ +import purescala.Trees._ +import purescala.TreeOps._ +import purescala.TypeTrees._ +import purescala.Constructors._ +import purescala.DefOps._ + +object RefactorPhase extends LeonPhase[Program, Program] { + val name = "Refactor" + val description = "Refactoring/Repairing" + + implicit val debugSection = utils.DebugSectionRefactor + + override val definedOptions : Set[LeonOptionDef] = Set( + LeonValueOptionDef("functions", "--functions=f1:f2", "Refactor functions f1,f2,...") + ) + + def run(ctx: LeonContext)(program: Program): Program = { + var refactorFuns: Option[Seq[String]] = None + + val reporter = ctx.reporter + + for(opt <- ctx.options) opt match { + case LeonValueOption("functions", ListValue(fs)) => + refactorFuns = Some(fs) + case _ => + } + + + val fdFilter = { + import OptionsHelpers._ + + filterInclusive(refactorFuns.map(fdMatcher), None) + } + + val toRefactor = funDefsFromMain(program).toList.sortWith((fd1, fd2) => fd1.getPos < fd2.getPos).filter(fdFilter) + + for (fd <- toRefactor) { + new Repairman(ctx, program, fd).repair() + } + + program + } +} diff --git a/src/main/scala/leon/refactor/Repairman.scala b/src/main/scala/leon/refactor/Repairman.scala new file mode 100644 index 0000000000000000000000000000000000000000..287c01db46daedc4e7859c6a975b8a4891aeacc7 --- /dev/null +++ b/src/main/scala/leon/refactor/Repairman.scala @@ -0,0 +1,205 @@ +/* Copyright 2009-2014 EPFL, Lausanne */ + +package leon +package refactor + +import purescala.Common._ +import purescala.Definitions._ +import purescala.Trees._ +import purescala.TreeOps._ +import purescala.TypeTrees._ +import purescala.Constructors._ +import purescala.ScalaPrinter +import evaluators._ +import datagen._ +import solvers._ +import utils._ +import solvers.z3._ +import codegen._ +import verification._ +import synthesis._ + +class Repairman(ctx: LeonContext, program: Program, fd: FunDef) { + val reporter = ctx.reporter + + var testBank = List[Example]() + + var solutions: List[(Solution, Expr, List[FunDef])] = Nil + + def repair(): Unit = { + // Gather in/out tests + val pre = fd.precondition.getOrElse(BooleanLiteral(true)) + val args = fd.params.map(_.id) + val argsWrapped = tupleWrap(args.map(_.toVariable)) + + val out = fd.postcondition.map(_._1).getOrElse(FreshIdentifier("res", true).setType(fd.returnType)) + + val tfd = program.library.passes.get.typed(Seq(argsWrapped.getType, out.getType)) + + val inouts = testBank; + + val testsExpr = FiniteMap(inouts.collect { + case InOutExample(ins, outs) => + tupleWrap(ins) -> tupleWrap(outs) + }.toList).setType(MapType(argsWrapped.getType, out.getType)) + + val passes = FunctionInvocation(tfd, Seq(argsWrapped, out.toVariable, testsExpr)) + + val spec = And(fd.postcondition.map(_._2).getOrElse(BooleanLiteral(true)), passes) + + // Synthesis from the ground up + val p = Problem(fd.params.map(_.id).toList, pre, spec, List(out)) + + val soptions = SynthesisPhase.processOptions(ctx); + + val synthesizer = new Synthesizer(ctx, fd, program, p, soptions) + + synthesizer.synthesize() match { + case (search, sols) => + for (sol <- sols) { + val expr = sol.toSimplifiedExpr(ctx, program) + + val (npr, fds) = synthesizer.solutionToProgram(sol) + solutions ::= (sol, expr, fds) + + if (!sol.isTrusted) { + + val timeoutMs = 3000l + val solverf = SolverFactory(() => (new FairZ3Solver(ctx, npr) with TimeoutSolver).setTimeout(timeoutMs)) + val vctx = VerificationContext(ctx, npr, solverf, reporter) + val nfd = fds.head + val vcs = AnalysisPhase.generateVerificationConditions(vctx, Some(List(nfd.id.name))) + + AnalysisPhase.checkVerificationConditions(vctx, vcs) + + var unknown = false; + var ces = List[Seq[Expr]]() + + for (vc <- vcs.getOrElse(nfd, List())) { + if (vc.value == Some(false)) { + vc.counterExample match { + case Some(m) => + ces = nfd.params.map(vd => m(vd.id)) :: ces; + + case _ => + } + } else if (vc.value == None) { + unknown = true; + } + } + + + if (ces.isEmpty) { + if (!unknown) { + reporter.info("ZZUCCESS!") + } else { + reporter.info("ZZUCCESS (maybe)!") + } + } else { + reporter.info("Failed :(, but I learned: "+ces.map(_.mkString(",")).mkString(" | ")) + testBank ++= ces.map(InExample(_)) + } + } else { + reporter.info("ZZUCCESS!") + } + } + + + if (solutions.isEmpty) { + reporter.info("Trey aagggain") + repair() + } else { + + reporter.info(ASCIIHelpers.title("Solutions")) + for (((sol, expr, fds), i) <- solutions.zipWithIndex) { + reporter.info(ASCIIHelpers.subTitle("Solution "+(i+1)+":")) + reporter.info(ScalaPrinter(expr)); + } + + if (solutions.size > 1) { + reporter.info(ASCIIHelpers.title("Disambiguating...")) + disambiguate(p, solutions(0)._1, solutions(1)._1) match { + case Some((o1, o2)) => + reporter.info("Solutions differ on "+o1.ins.mkString(", ")) + reporter.info("solution A produces: "+o1.outs.mkString(", ")) + reporter.info("solution B produces: "+o2.outs.mkString(", ")) + + case None => + reporter.info("Solutions appear equivalent..") + } + } + } + + case _ => + reporter.error("I failed you :(") + } + + } + + def disambiguate(p: Problem, sol1: Solution, sol2: Solution): Option[(InOutExample, InOutExample)] = { + val s1 = sol1.toSimplifiedExpr(ctx, program) + val s2 = sol2.toSimplifiedExpr(ctx, program) + + val e = new DefaultEvaluator(ctx, program); + + def unwrap(e: Expr) = if (p.xs.size > 1) { + val Tuple(es) = e + es + } else { + Seq(e) + } + + if (s1 == s2) { + None + } else { + val diff = And(p.pc, Not(Equals(s1, s2))) + val solver = (new FairZ3Solver(ctx, program) with TimeoutSolver).setTimeout(1000) + + solver.assertCnstr(diff) + solver.check match { + case Some(true) => + val m = solver.getModel + val inputs = p.as.map(id => m.getOrElse(id, simplestValue(id.getType))) + val inputsMap = (p.as zip inputs).toMap + + (e.eval(s1, inputsMap), e.eval(s2, inputsMap)) match { + case (EvaluationResults.Successful(r1), EvaluationResults.Successful(r2)) => + Some((InOutExample(inputs, unwrap(r1)), InOutExample(inputs, unwrap(r2)))) + case _ => + None + } + case Some(false) => + None + case _ => + // considered as equivalent + None + } + } + } + + def classifier(ok: Seq[Example], notok: Seq[Example]): Option[Expr] = { + // Obtain expr that returns partitions all examples into ok/notok + + None + } + + def discoverTests() = { + val evaluator = new CodeGenEvaluator(ctx, program, CodeGenParams(checkContracts = true)) + val tests = new NaiveDataGen(ctx, program, evaluator) + + val pre = fd.precondition.getOrElse(BooleanLiteral(true)) + val inputs = tests.generateFor(fd.params.map(_.id), pre, 30, 10000).toList + + testBank ++= inputs.map { i => + evaluator.eval(FunctionInvocation(fd.typed(fd.tparams.map(_.tp)), i)) match { + case EvaluationResults.Successful(res) => + new InOutExample(i, List(res)) + + case _ => + new InExample(i) + } + } + } + + discoverTests() +} diff --git a/src/main/scala/leon/synthesis/InOutExample.scala b/src/main/scala/leon/synthesis/InOutExample.scala index 8978e38ae002faf74ffc55f5528f499340c7cc0e..0e2e4faa84d51d59885b59814909428bcc30c57b 100644 --- a/src/main/scala/leon/synthesis/InOutExample.scala +++ b/src/main/scala/leon/synthesis/InOutExample.scala @@ -5,5 +5,6 @@ package synthesis import purescala.Trees.Expr -class InExample(val ins: Seq[Expr]) -class InOutExample(is: Seq[Expr], val outs: Seq[Expr]) extends InExample(is) +class Example(val ins: Seq[Expr]) +case class InOutExample(is: Seq[Expr], val outs: Seq[Expr]) extends Example(is) +case class InExample(is: Seq[Expr]) extends Example(is) diff --git a/src/main/scala/leon/synthesis/Problem.scala b/src/main/scala/leon/synthesis/Problem.scala index 82b02486f71a5ec772327b3caf909ef8f544fb6c..cc21e87a9798a32d6a997b5efc10832317a66e55 100644 --- a/src/main/scala/leon/synthesis/Problem.scala +++ b/src/main/scala/leon/synthesis/Problem.scala @@ -12,7 +12,7 @@ import leon.purescala.Common._ case class Problem(as: List[Identifier], pc: Expr, phi: Expr, xs: List[Identifier]) { override def toString = "⟦ "+as.mkString(";")+", "+(if (pc != BooleanLiteral(true)) pc+" ≺ " else "")+" ⟨ "+phi+" ⟩ "+xs.mkString(";")+" ⟧ " - def getTests(sctx: SynthesisContext): Seq[InExample] = { + def getTests(sctx: SynthesisContext): Seq[Example] = { import purescala.Extractors._ import evaluators._ @@ -20,7 +20,7 @@ case class Problem(as: List[Identifier], pc: Expr, phi: Expr, xs: List[Identifie val ev = new DefaultEvaluator(sctx.context, sctx.program) - def isValidExample(ex: InExample): Boolean = { + def isValidExample(ex: Example): Boolean = { val (mapping, cond) = ex match { case io: InOutExample => (Map((as zip io.ins) ++ (xs zip io.outs): _*), And(pc, phi)) @@ -142,9 +142,9 @@ case class Problem(as: List[Identifier], pc: Expr, phi: Expr, xs: List[Identifie val examples = consolidated.toSeq.flatMap { t => val ids = t.keySet if ((ids & allIds) == allIds) { - Some(new InOutExample(as.map(t), xs.map(t))) + Some(InOutExample(as.map(t), xs.map(t))) } else if ((ids & insIds) == insIds) { - Some(new InExample(as.map(t))) + Some(InExample(as.map(t))) } else { None } diff --git a/src/main/scala/leon/synthesis/Synthesizer.scala b/src/main/scala/leon/synthesis/Synthesizer.scala index 5598f59b83f3e30436c75771c4785e01b824f339..0e2bff86f85aa2d69274233d79973c51b888f436 100644 --- a/src/main/scala/leon/synthesis/Synthesizer.scala +++ b/src/main/scala/leon/synthesis/Synthesizer.scala @@ -96,7 +96,7 @@ class Synthesizer(val context : LeonContext, } // Returns the new program and the new functions generated for this - def solutionToProgram(sol: Solution): (Program, Set[FunDef]) = { + def solutionToProgram(sol: Solution): (Program, List[FunDef]) = { import purescala.TypeTrees.TupleType import purescala.Definitions.ValDef @@ -109,12 +109,12 @@ class Synthesizer(val context : LeonContext, Variable(id) -> TupleSelect(res, i+1) }.toMap - val fd = new FunDef(FreshIdentifier("finalTerm", true), Nil, ret, problem.as.map(id => ValDef(id, id.getType)), DefType.MethodDef) + val fd = new FunDef(FreshIdentifier(functionContext.id.name+"_final", true), Nil, ret, problem.as.map(id => ValDef(id, id.getType)), DefType.MethodDef) fd.precondition = Some(And(problem.pc, sol.pre)) fd.postcondition = Some((res.id, replace(mapPost, problem.phi))) fd.body = Some(sol.term) - val newDefs = sol.defs + fd + val newDefs = fd +: sol.defs.toList val npr = program.copy(units = program.units map { u => u.copy(modules = ModuleDef(FreshIdentifier("synthesis"), newDefs.toSeq, false) +: u.modules ) diff --git a/src/main/scala/leon/synthesis/rules/Cegis.scala b/src/main/scala/leon/synthesis/rules/Cegis.scala index 51e671cf57ef7a38634ca348fcb54086fdda5003..ad57f6edfdd6771a396392ff3394a3e61f563f5a 100644 --- a/src/main/scala/leon/synthesis/rules/Cegis.scala +++ b/src/main/scala/leon/synthesis/rules/Cegis.scala @@ -97,6 +97,10 @@ case object CEGIS extends Rule("CEGIS") { alts.toList } + case _: TypeParameter => + { () => + Nil + } case _ => sctx.reporter.error("Can't construct generator. Unsupported type: "+t+"["+t.getClass+"]"); { () => Nil } diff --git a/src/main/scala/leon/synthesis/rules/Tegis.scala b/src/main/scala/leon/synthesis/rules/Tegis.scala index 15ac89ea9911845b21018d2895708664d2fe9f7c..0b16539aa77b1221a1a73cf31ca3d96dd71e94b7 100644 --- a/src/main/scala/leon/synthesis/rules/Tegis.scala +++ b/src/main/scala/leon/synthesis/rules/Tegis.scala @@ -12,6 +12,7 @@ import purescala.Common._ import purescala.Definitions._ import purescala.TypeTrees._ import purescala.TreeOps._ +import purescala.DefOps._ import purescala.TypeTreeOps._ import purescala.Extractors._ import purescala.ScalaPrinter @@ -87,7 +88,7 @@ case object TEGIS extends Rule("TEGIS") { } def getFcallGenerators(t: TypeTree): Seq[Generator[TypeTree, Expr]] = { - def isCandidate(fd: FunDef): Option[TypedFunDef] = { + def getCandidates(fd: FunDef): Seq[TypedFunDef] = { // Prevents recursive calls val cfd = sctx.functionContext @@ -107,16 +108,50 @@ case object TEGIS extends Rule("TEGIS") { val free = fd.tparams.map(_.tp) canBeSubtypeOf(fd.returnType, free, t) match { case Some(tpsMap) => - Some(fd.typed(free.map(tp => tpsMap.getOrElse(tp, tp)))) + val tfd = fd.typed(free.map(tp => tpsMap.getOrElse(tp, tp))) + + if (tpsMap.size < free.size) { + /* Some type params remain free, we want to assign them: + * + * List[T] => Int, for instance, will be found when + * requesting Int, but we need to assign T to viable + * types. For that we use problem inputs as heuristic, + * and look for instantiations of T such that input <?: + * List[T]. + */ + p.as.map(_.getType).distinct.flatMap { (atpe: TypeTree) => + var finalFree = free.toSet -- tpsMap.keySet + var finalMap = tpsMap + + for (ptpe <- tfd.params.map(_.tpe).distinct) { + canBeSubtypeOf(atpe, finalFree.toSeq, ptpe) match { + case Some(ntpsMap) => + finalFree --= ntpsMap.keySet + finalMap ++= ntpsMap + case _ => + } + } + + if (finalFree.isEmpty) { + List(fd.typed(free.map(tp => finalMap.getOrElse(tp, tp)))) + } else { + Nil + } + } + } else { + /* All type parameters that used to be free are assigned + */ + List(tfd) + } case None => - None + Nil } } else { - None + Nil } } - val funcs = sctx.program.definedFunctions.flatMap(isCandidate) + val funcs = visibleFunDefsFromMain(sctx.program).toSeq.flatMap(getCandidates) funcs.map{ tfd => Generator[TypeTree, Expr](tfd.params.map(_.tpe), { sub => FunctionInvocation(tfd, sub) }) @@ -124,7 +159,12 @@ case object TEGIS extends Rule("TEGIS") { } def getGenerators(t: TypeTree): Seq[Generator[TypeTree, Expr]] = { - getBaseGenerators(t) ++ getInputGenerators(t) ++ getFcallGenerators(t) + val res = getBaseGenerators(t) ++ getInputGenerators(t) ++ getFcallGenerators(t) + //for (g <- res) { + // val inputs = g.subTrees.zipWithIndex.map{ case (t, i) => Variable(FreshIdentifier(t.toString).setType(t)) } + // println(f"$t%20s := ${g.builder(inputs)}") + //} + res } val enum = new MemoizedEnumerator[TypeTree, Expr](getGenerators) diff --git a/src/main/scala/leon/utils/DebugSections.scala b/src/main/scala/leon/utils/DebugSections.scala index adcfecf25140edeb2cae1bda2db8fae2d259f4b3..0e4d30a4e538e431cb8dc4315defd8c9f459e11e 100644 --- a/src/main/scala/leon/utils/DebugSections.scala +++ b/src/main/scala/leon/utils/DebugSections.scala @@ -18,6 +18,7 @@ case object DebugSectionTrees extends DebugSection("trees", 1 << 6 case object DebugSectionPositions extends DebugSection("positions", 1 << 7) case object DebugSectionDataGen extends DebugSection("datagen", 1 << 8) case object DebugSectionEvaluation extends DebugSection("eval", 1 << 9) +case object DebugSectionRefactor extends DebugSection("refactor", 1 << 10) object DebugSections { val all = Set[DebugSection]( @@ -30,6 +31,7 @@ object DebugSections { DebugSectionTrees, DebugSectionPositions, DebugSectionDataGen, - DebugSectionEvaluation + DebugSectionEvaluation, + DebugSectionRefactor ) } diff --git a/src/main/scala/leon/utils/Library.scala b/src/main/scala/leon/utils/Library.scala index 7c590125952fba53e8cc6a867d0d4298bf53a066..4899368aa217eb0e7f8c34372d9a8ef471cc6838 100644 --- a/src/main/scala/leon/utils/Library.scala +++ b/src/main/scala/leon/utils/Library.scala @@ -17,6 +17,10 @@ case class Library(pgm: Program) { case fd: FunDef => fd } + lazy val passes = lookup("leon.lang.passes").collect { + case fd: FunDef => fd + } + def lookup(name: String): Option[Definition] = { searchByFullName(name, pgm) }