Coverage Summary for Class: CallModule (it.unibo.tuprolog.argumentation.core.primitives.extra)

Class Class, % Method, % Branch, % Line, % Instruction, %
CallModule 100% (1/1) 100% (9/9) 66.7% (20/30) 89.7% (35/39) 83.6% (255/305)


 package it.unibo.tuprolog.argumentation.core.primitives.extra
 
 import it.unibo.tuprolog.argumentation.SyntaxProblem
 import it.unibo.tuprolog.argumentation.core.flags.ModulesPath
 import it.unibo.tuprolog.argumentation.core.model.terms.TermValues
 import it.unibo.tuprolog.argumentation.core.runtime.modules.solveForCaller
 import it.unibo.tuprolog.argumentation.core.runtime.settings.Settings
 import it.unibo.tuprolog.argumentation.core.runtime.theory.RuleAnnotations
 import it.unibo.tuprolog.core.Atom
 import it.unibo.tuprolog.core.List
 import it.unibo.tuprolog.core.Struct
 import it.unibo.tuprolog.core.Term
 import it.unibo.tuprolog.core.exception.TuPrologException
 import it.unibo.tuprolog.core.parsing.ParseException
 import it.unibo.tuprolog.solve.ExecutionContext
 import it.unibo.tuprolog.solve.Signature
 import it.unibo.tuprolog.solve.Solution
 import it.unibo.tuprolog.solve.SolveOptions
 import it.unibo.tuprolog.solve.Solver
 import it.unibo.tuprolog.solve.exception.error.ExistenceError
 import it.unibo.tuprolog.solve.exception.error.SyntaxError
 import it.unibo.tuprolog.solve.exception.error.TypeError
 import it.unibo.tuprolog.solve.libs.io.IOLib
 import it.unibo.tuprolog.solve.libs.io.Url
 import it.unibo.tuprolog.solve.primitive.Primitive
 import it.unibo.tuprolog.solve.primitive.Solve
 import it.unibo.tuprolog.theory.Theory
 
 /**
  * call_module(+Modules, +Goal): Goal solved on the theories of Modules alone, loaded in their order. Each module is an
  * atom: a location, a URL or the path of a file ending in `.pl` (relative to the working directory), or else the name
  * of a file in the modules path (flag `modulesPath`, the working directory if there is none), `.pl` left out. A module
  * that cannot be read raises an existence error, one that does not parse a syntax error; an error raised within the
  * modules is raised by the call.
  */
 internal object CallModule : Primitive {
     val signature = Signature("call_module", 2)
 
     override fun solve(request: Solve.Request<ExecutionContext>): Sequence<Solve.Response> {
         val modules: Term = request.arguments[0]
         val goal: Term = request.arguments[1]
 
         if (modules !is List) {
             throw TypeError.forArgument(request.context, request.signature, TypeError.Expected.LIST, modules, 0)
         }
         if (goal !is Struct) {
             throw TypeError.forArgument(request.context, request.signature, TypeError.Expected.CALLABLE, goal, 1)
         }
         val names =
             modules.toList().map {
                 it as? Atom ?: throw TypeError.forArgument(request.context, request.signature, TypeError.Expected.ATOM, it, 0)
             }
 
         val base = modulesPath(request.context)
         val solver = solverOf(request.context, names.map { locationOf(it.value, base) })
         return solver.solveForCaller(goal, SolveOptions.allLazily()).map {
             when (it) {
                 is Solution.Yes -> request.replySuccess(it.substitution)
                 // an error inside the modules surfaces: a failure would turn a broken call into an empty result
                 is Solution.Halt -> throw it.exception
                 else -> request.replyFail()
             }
         }
     }
 
     // The modules path of the settings, if there is one
     private fun modulesPath(context: ExecutionContext): String? =
         Settings
             .of(context)
             ?.valuesOf(ModulesPath, context)
             ?.firstOrNull()
             ?.let { (it as? Atom)?.value ?: it.toString() }
 
     // The location of the module [name]: a URL, or a path ending in .pl, as it is; else the file of the module in [base]
     private fun locationOf(
         name: String,
         base: String?,
     ): String =
         when {
             isUrl(name) || name.endsWith(".pl") -> name
             base == null -> "$name.pl"
             else -> "${base.trimEnd('/', '\\')}/$name.pl"
         }
 
     // A location carrying a scheme (http://, file://): the others are paths of files
     private fun isUrl(location: String) = location.contains("://")
 
     // The text of the module at [location]: an existence error if it cannot be read
     private fun read(
         context: ExecutionContext,
         location: String,
     ): String =
         try {
             (if (isUrl(location)) Url.of(location) else Url.file(location)).readAsText()
         } catch (e: TuPrologException) {
             throw ExistenceError.of(
                 context,
                 ExistenceError.ObjectType.SOURCE_SINK,
                 Atom.of(location),
                 "No module can be read at `$location`: ${e.message}",
             )
         }
 
     // The theory of the module at [location], read with the operators of every theory of arg2p (those of the module
     // calls, ::, among them): a syntax error if it does not parse
     private fun theoryOf(
         context: ExecutionContext,
         location: String,
     ): Theory {
         val text = read(context, location)
         return try {
             RuleAnnotations.parse(text, TermValues.operators)
         } catch (e: ParseException) {
             throw SyntaxError.of(context, "$location: ${SyntaxProblem.of(e)}")
         }
     }
 
     // A solver of the modules at [locations], each adding its clauses to those before it, with the libraries and the
     // flags of the calling context (the theories of arg2p rely on unknown predicates failing, which the default flags
     // do not give), and IOLib, for the modules using its predicates: the theory of the caller is left out
     private fun solverOf(
         context: ExecutionContext,
         locations: kotlin.collections.List<String>,
     ): Solver =
         context
             .createMutableSolver(libraries = context.libraries + IOLib, staticKb = Theory.empty())
             .also { solver -> locations.forEach { solver.appendStaticKb(theoryOf(context, it)) } }
 }