Coverage Summary for Class: Settings (it.unibo.tuprolog.argumentation.core.runtime.settings)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| Settings |
93.3%
(14/15)
|
84.2%
(64/76)
|
98.3%
(58/59)
|
96.9%
(618/638)
|
| Settings$Companion |
100%
(1/1)
|
50%
(2/4)
|
100%
(1/1)
|
85%
(17/20)
|
| Total |
93.8%
(15/16)
|
82.5%
(66/80)
|
98.3%
(59/60)
|
96.5%
(635/658)
|
package it.unibo.tuprolog.argumentation.core.runtime.settings
import it.unibo.tuprolog.argumentation.core.Arg2pSolver
import it.unibo.tuprolog.argumentation.core.flags.ArgsFlag
import it.unibo.tuprolog.argumentation.core.flags.GraphBuildMode
import it.unibo.tuprolog.argumentation.core.modules.ArgLibrary
import it.unibo.tuprolog.argumentation.core.primitives.settings.SettingSwitch
import it.unibo.tuprolog.argumentation.core.primitives.settings.SettingValue
import it.unibo.tuprolog.core.Atom
import it.unibo.tuprolog.core.Clause
import it.unibo.tuprolog.core.Fact
import it.unibo.tuprolog.core.Struct
import it.unibo.tuprolog.core.Term
import it.unibo.tuprolog.core.Truth
import it.unibo.tuprolog.core.Var
import it.unibo.tuprolog.solve.ExecutionContext
import it.unibo.tuprolog.solve.exception.error.DomainError
import it.unibo.tuprolog.solve.exception.error.TypeError
import it.unibo.tuprolog.solve.library.Library
import it.unibo.tuprolog.solve.primitive.Solve
import it.unibo.tuprolog.theory.Theory
/**
* The flags of an arg2p solver: those its libraries declare ([ArgsFlag]), checked by the Prolog code through
* setting/1 (whether a switch is on) and setting/2 (the values of a flag); checking a flag no library declares, or a
* switch for its values (and the other way round), raises a domain error.
*
* Their values are a view over the facts of the libraries (the settings of the solver, as [FlagsBuilder] states them)
* and those of the theory, through the profile of the theory (graphBuildMode, see [Profiles]): for each flag, the
* theory states its values, or else the settings of the solver if they differ from the default of the flag, or else
* the profile, or else the settings of the solver. A flag is stated with no value by `Flag(none)` (no graph
* extension, say), a switch stated off by `Switch(off)`: so a theory clears what its profile presets. A value
* standing for others ([ArgsFlag.aliases], the builtin of conflictModel) states those, and the values tell them.
*/
class Settings(
private val solver: Arg2pSolver,
) : ArgLibrary {
/** The flags the libraries of the solver declare, by name */
val flags: Map<String, ArgsFlag<*, *>> by lazy {
(solver.baseLibraries() + solver.staticLibraries() + solver.dynamicLibraries())
.flatMap { it.flags() }
.associateBy { it.predicate() }
}
// The values of the flags, for the last theory met (the settings of the libraries are those of the solver)
private var viewOf: Theory? = null
private var view: Map<String, List<Term>> = emptyMap()
/** Whether the switch [flag] is on in [context] */
fun isOn(
flag: ArgsFlag<*, *>,
context: ExecutionContext,
): Boolean = valuesOf(flag, context).isNotEmpty()
/** The values of [flag] in [context] (for a switch, `true` if it is on) */
fun valuesOf(
flag: ArgsFlag<*, *>,
context: ExecutionContext,
): List<Term> = viewFor(context)[flag.predicate()].orEmpty()
/**
* The facts stating the flags of [context] with their values (see [valuesOf]), but for the flags named in
* [except]: for a theory solved elsewhere with the same flags
*/
fun facts(
context: ExecutionContext,
except: Set<String> = emptySet(),
): List<Fact> =
flags.filterKeys { it !in except }.flatMap { (name, flag) ->
valuesOf(flag, context).map { value -> Fact.of(if (flag.isSwitch) Atom.of(name) else Struct.of(name, value)) }
}
/** Whether [clause] is a fact stating a flag */
fun states(clause: Clause): Boolean {
val head = clause.head ?: return false
val flag = flags[head.functor] ?: return false
return clause.isFact && (head.arity == 1 || (flag.isSwitch && head.arity == 0))
}
private fun viewFor(context: ExecutionContext): Map<String, List<Term>> {
if (viewOf !== context.staticKb) {
val libraries = context.libraries.asTheory(context.unificator)
val theory = flags.mapValues { (_, flag) -> stated(flag, context.staticKb) }
val solver = flags.mapValues { (_, flag) -> stated(flag, libraries) }
val mode = theory[GraphBuildMode.predicate()].orEmpty().ifEmpty { solver[GraphBuildMode.predicate()].orEmpty() }
val profile = if (mode.isEmpty()) Profiles.default else Profiles.named((mode.first() as? Atom)?.value)
val presets = profile?.presets.orEmpty()
view =
flags.mapValues { (name, flag) ->
// a switch of the solver is off with no fact, another flag has no value stated
val stated = solver.getValue(name)
// the values of the default in any order (those of a set of parts, as conflictModel)
val explicit = (flag.isSwitch || stated.isNotEmpty()) && stated.map { it.toString() }.toSet() != defaultOf(flag).toSet()
val preset = presets[name]?.let { expanded(flag, it) }
val values =
when {
theory.getValue(name).isNotEmpty() -> theory.getValue(name)
explicit -> stated
flag.isSwitch -> preset ?: stated
else -> preset ?: stated.ifEmpty { defaultOf(flag).map { Atom.of(it) } }
}
values.filter { it != NONE && it != OFF }
}
viewOf = context.staticKb
}
return view
}
// The values of a flag stated by the facts of a theory: none or off for one stated with no value
private fun stated(
flag: ArgsFlag<*, *>,
theory: Theory,
): List<Term> {
val valued = theory[Struct.of(flag.predicate(), Var.of("Value"))].filter { it.isFact }.map { it.head[0] }
if (!flag.isSwitch) return expanded(flag, valued.toList())
return theory[Atom.of(flag.predicate())].filter { it.isFact }.map { Truth.TRUE }.toList() + valued.filter { it == OFF }
}
// The values of a flag, those standing for others replaced by them (see ArgsFlag.aliases), each once
private fun expanded(
flag: ArgsFlag<*, *>,
values: List<Term>,
): List<Term> =
if (flag.aliases.isEmpty()) {
values
} else {
values.flatMap { value -> flag.aliases[(value as? Atom)?.value]?.map { Atom.of(it) } ?: listOf(value) }.distinct()
}
// The default values of a flag, as text
private fun defaultOf(flag: ArgsFlag<*, *>): List<String> =
when (val default = flag.default()) {
is Boolean -> if (default) listOf(Truth.TRUE.toString()) else emptyList()
is Iterable<*> -> default.map { Atom.of(it.toString()).toString() }
else -> listOf(Atom.of(default.toString()).toString())
}
/** The flag named by the first argument of [request], declared of the kind its check expects (a switch or not) */
internal fun declared(
request: Solve.Request<ExecutionContext>,
switch: Boolean,
): ArgsFlag<*, *> {
val name = request.arguments[0]
if (name !is Atom) throw TypeError.forArgument(request.context, request.signature, TypeError.Expected.ATOM, name, 0)
return flags[name.value]?.takeIf { it.isSwitch == switch }
?: throw DomainError.forArgument(request.context, request.signature, DomainError.Expected.FLAG, name, 0)
}
override val alias = "prolog.argumentation.settings"
override val baseContent: Library
get() =
listOf(SettingSwitch(this), SettingValue(this)).let { primitives ->
Library.of(
alias = this.alias,
primitives = primitives.associateBy { it.signature },
)
}
override val baseFlags: Iterable<ArgsFlag<*, *>>
get() = emptyList()
companion object {
// The values stating a flag with no value, and a switch off
private val NONE = Atom.of("none")
private val OFF = Atom.of("off")
/** The settings of the solver of [context], if they are reachable */
fun of(context: ExecutionContext): Settings? = (context.libraries.primitives[SettingSwitch.SIGNATURE] as? SettingSwitch)?.owner
}
}