Coverage Summary for Class: Arg2p (it.unibo.tuprolog.argumentation)

Class Method, % Branch, % Line, % Instruction, %
Arg2p 69.2% (18/26) 72.2% (13/18) 78.9% (30/38) 89% (284/319)
Arg2p$Builder 100% (6/6) 75% (3/4) 100% (11/11) 100% (92/92)
Arg2p$Companion 100% (6/6) 100% (7/7) 100% (41/41)
Total 78.9% (30/38) 72.7% (16/22) 85.7% (48/56) 92.3% (417/452)


 @file:Suppress("NON_EXPORTABLE_TYPE")
 
 package it.unibo.tuprolog.argumentation
 
 import it.unibo.tuprolog.argumentation.causality.modules.CausalityModule
 import it.unibo.tuprolog.argumentation.core.Arg2pSolver
 import it.unibo.tuprolog.argumentation.core.Arg2pSolverFactory
 import it.unibo.tuprolog.argumentation.core.model.Graph
 import it.unibo.tuprolog.argumentation.core.model.mining.activeBranch
 import it.unibo.tuprolog.argumentation.core.model.mining.graph
 import it.unibo.tuprolog.argumentation.core.model.terms.TermValues
 import it.unibo.tuprolog.argumentation.core.runtime.theory.RuleAnnotations
 import it.unibo.tuprolog.argumentation.parallel.LABELLINGS
 import it.unibo.tuprolog.argumentation.parallel.checked
 import it.unibo.tuprolog.argumentation.parallel.graphsOf
 import it.unibo.tuprolog.core.Atom
 import it.unibo.tuprolog.core.Integer
 import it.unibo.tuprolog.core.Struct
 import it.unibo.tuprolog.core.Term
 import it.unibo.tuprolog.core.Var
 import it.unibo.tuprolog.solve.MutableSolver
 import it.unibo.tuprolog.solve.channel.OutputChannel
 import it.unibo.tuprolog.theory.Theory
 import kotlin.js.JsExport
 import kotlin.js.JsName
 import kotlin.js.JsStatic
 import kotlin.jvm.JvmOverloads
 import kotlin.jvm.JvmStatic
 import it.unibo.tuprolog.solve.Solution as LogicSolution
 
 /**
  * A solver of arg2p for a theory, as Prolog text, with its [Flags]: it answers queries ([answer]), gives the graph of
  * each labelling ([graphs]), solves Prolog goals ([solve]) and tests causality ([causality]).
  *
  * ```kotlin
  * val arg2p = Arg2p.of("r1 : [] => a.\nr2 : [] => -a.\nsup(r1, r2).")
  * arg2p.answer("a").inStatements      // [a]
  * ```
  *
  * The context of the solver keeps a branch for every stage of a resolution (each module call made with `:::`),
  * numbered from 0: [activeBranch] is the last one, [graphAt] the graph of any of them, and [contextFacts] what a
  * branch stores.
  */
 @JsExport
 class Arg2p private constructor(
     private val solver: MutableSolver,
     // the libraries of the solver, which declare its flags
     private val arg2p: Arg2pSolver,
 ) {
     /** The causality tests on the theory: NESS and but-for (see [Causality]) */
     val causality: Causality = Causality(this)
 
     /** The theory, as Prolog text */
     val theory: String get() = solver.staticKb.toString(asPrologText = true)
 
     /**
      * The statements for [query], Prolog text, labelled in, out and und (see `answerQuery/4`)
      *
      * @throws Arg2pException if the evaluation halts (a theory its profile refuses, a query that cannot be parsed)
      */
     fun answer(query: String): Answer = AnswerQuery(term(query)).let { it.answer(first(it.goal)) }
 
     /**
      * The graph of each labelling of the theory: one, or several for a semantics with several labellings (preferred,
      * stable, ...)
      *
      * @throws Arg2pException if the evaluation halts (a theory its profile refuses)
      */
     @JsExport.Ignore
     @JsName("graphsList")
     fun graphs(): List<Graph> =
         // read while the solver is at each solution, the graph of its labelling in the active branch
         graphsOf(solver.solve(Struct.of(LABELLINGS)).iterator()) { solver.graph() }
 
     /** The graph of each labelling of the theory (an array in JavaScript) */
     @JsName("graphs")
     fun graphArray(): Array<Graph> = graphs().toTypedArray()
 
     /** The graph of the last resolution: the one of its last stage */
     fun graph(): Graph = graphAt(activeBranch)
 
     /** The graph of the context branch [branch] */
     fun graphAt(branch: Int): Graph = solver.graph(branch)
 
     /** The context branch of the last stage of the last resolution */
     val activeBranch: Int get() = solver.activeBranch()
 
     /** The flags the solver knows, by name: graphBuildMode, queryMode, ... (see [setting]) */
     @JsExport.Ignore
     @JsName("knownFlagsList")
     val knownFlags: List<String> get() = declaredFlags.keys.toList()
 
     /** The flags the solver knows, by name (an array in JavaScript) */
     @JsName("knownFlags")
     val knownFlagArray: Array<String> get() = knownFlags.toTypedArray()
 
     /**
      * The values of [flag] in effect for the theory, as the solver checks them: those the theory states, or else those
      * of the [Flags] of the solver that differ from their default, or else those the profile of the theory presets (see
      * Profiles). A switch on has the value `true`, off none.
      *
      * @throws Arg2pException for a flag the solver does not know (see [knownFlags])
      */
     @JsExport.Ignore
     @JsName("settingList")
     fun setting(flag: String): List<String> {
         val declared = declaredFlags[flag] ?: throw Arg2pException("Not a flag of arg2p: $flag (known: ${knownFlags.joinToString()})")
         val value = Var.of("Value")
         return if (declared.isSwitch) {
             listOfNotNull(first(Struct.of("setting", Atom.of(flag)))?.let { "true" })
         } else {
             solver
                 .solve(Struct.of("setting", Atom.of(flag), value))
                 .map(::checked)
                 .filter { it.isYes }
                 .map { solution -> solution.substitution[value]!!.let { (it as? Atom)?.value ?: TermValues.format(it) } }
                 .toList()
         }
     }
 
     /** The values of [flag] in effect for the theory (an array in JavaScript) */
     @JsName("setting")
     fun settingArray(flag: String): Array<String> = setting(flag).toTypedArray()
 
     private val declaredFlags get() = arg2p.settings.flags
 
     /** The solutions of [goal], Prolog text, computed one at a time as they are asked for */
     fun solve(goal: String): Solutions = solutionsOf(solver.solve(struct(goal)).iterator())
 
     /**
      * What the context branch [branch] stores matching [goal], Prolog text (facts, or clause(Head, Body) for rules):
      * the solutions of context_check(Branch, Goal)
      */
     fun contextFacts(
         branch: Int,
         goal: String,
     ): Solutions = solutionsOf(solver.solve(Struct.of("context_check", Integer.of(branch), struct(goal))).iterator())
 
     /** Adds the clauses of [theory], Prolog text, to the theory (an [Arg2pException] telling where it does not parse) */
     fun add(theory: String) = solver.appendStaticKb(parsed(theory))
 
     /** Replaces the theory with [theory], Prolog text (an [Arg2pException] telling where it does not parse) */
     fun load(theory: String) = solver.loadStaticKb(parsed(theory))
 
     // The clauses of [theory], read with the operators of the solver
     private fun parsed(theory: String): Theory = SyntaxProblem.parsing("Theory") { RuleAnnotations.parse(theory, solver.operators) }
 
     // The first solution of [goal]: null if it fails, an Arg2pException if it halts
     internal fun first(goal: Struct): LogicSolution? = checked(solver.solve(goal).first()).takeIf { it.isYes }
 
     internal fun term(text: String): Term = SyntaxProblem.parsing("Query") { TermValues.term(text, solver.operators) }
 
     private fun struct(goal: String): Struct = term(goal).let { it as? Struct ?: throw Arg2pException("Not a goal: $goal") }
 
     /** A solver of arg2p, as it is described: its theory, its flags, and where what it prints goes */
     class Builder {
         private var theory = ""
         private var flags = Flags.defaults()
         private var output: ((String) -> Unit)? = null
 
         /** The theory, as Prolog text */
         fun theory(theory: String): Builder = apply { this.theory = theory }
 
         /** The flags ([Flags.defaults] otherwise) */
         fun flags(flags: Flags): Builder = apply { this.flags = flags }
 
         /** The function receiving what the solver prints (the standard output otherwise) */
         fun output(consumer: (String) -> Unit): Builder = apply { output = consumer }
 
         /** The solver: an [Arg2pException] telling where the theory does not parse otherwise */
         fun build(): Arg2p {
             // the causality module of its own for each solver: it keeps the graphs it computes
             val arg2p = Arg2pSolver.default(listOf(flags.library()), listOf(CausalityModule()))
             return Arg2p(
                 SyntaxProblem.parsing("Theory") {
                     Arg2pSolverFactory.solverOf(arg2p, theory, output = output?.let { OutputChannel.of(it) } ?: OutputChannel.stdOut())
                 },
                 arg2p,
             )
         }
     }
 
     companion object {
         /** A solver of arg2p for [theory], Prolog text, with [flags] */
         @JvmStatic
         @JvmOverloads
         @JsStatic
         fun of(
             theory: String = "",
             flags: Flags = Flags.defaults(),
         ): Arg2p = Builder().theory(theory).flags(flags).build()
 
         /** A solver of arg2p, as it is described */
         @JvmStatic
         @JsStatic
         fun builder(): Builder = Builder()
 
         /** Where [theory], Prolog text (a theory, or flags), does not parse; null if it does. Nothing is solved. */
         @JvmStatic
         @JsStatic
         fun checkTheory(theory: String): SyntaxProblem? = SyntaxProblem.check { RuleAnnotations.parse(theory, TermValues.operators) }
 
         /** Where [query], a goal in Prolog text, does not parse; null if it does. Nothing is solved. */
         @JvmStatic
         @JsStatic
         fun checkQuery(query: String): SyntaxProblem? = SyntaxProblem.check { TermValues.term(query) }
 
         /** The version of arg2p, the released one for a release */
         @JvmStatic
         @JsStatic
         fun version(): String = Arg2pLibrary.version()
     }
 }