Coverage Summary for Class: RuleToClauseNatural (it.unibo.tuprolog.argumentation.core.primitives.graph.language)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| RuleToClauseNatural |
75%
(3/4)
|
50%
(2/4)
|
90.5%
(19/21)
|
74.1%
(86/116)
|
| RuleToClauseNatural$TemplateExtractor |
100%
(7/7)
|
75%
(9/12)
|
94.8%
(55/58)
|
91.3%
(335/367)
|
| RuleToClauseNatural$TemplateExtractor$Result |
100%
(1/1)
|
|
100%
(3/3)
|
100%
(12/12)
|
| Total |
91.7%
(11/12)
|
68.8%
(11/16)
|
93.9%
(77/82)
|
87.5%
(433/495)
|
package it.unibo.tuprolog.argumentation.core.primitives.graph.language
import it.unibo.tuprolog.argumentation.core.model.terms.RuleTerm
import it.unibo.tuprolog.core.List
import it.unibo.tuprolog.core.Substitution
import it.unibo.tuprolog.core.Term
import it.unibo.tuprolog.core.parsing.parse
import it.unibo.tuprolog.solve.ExecutionContext
import it.unibo.tuprolog.solve.primitive.BinaryRelation
import it.unibo.tuprolog.solve.primitive.Solve
import kotlin.collections.List as KList
/**
* ruleToClauseNatural(+Rules, -Clauses): as ruleToClause/2, with naturalTerms: the statements written as text
* are turned into terms, the words in upper case and in brackets becoming their arguments
*/
internal object RuleToClauseNatural : BinaryRelation.WithoutSideEffects<ExecutionContext>("ruleToClauseNatural") {
class TemplateExtractor {
private val upperRegex = Regex("""\b[A-Z][a-zA-Z]*\b""")
private val bracketRegex = Regex("""\[([^\]]+)\]""")
// template -> number of extracted slots
private val knownTemplates = mutableMapOf<String, Int>()
data class Result(
val extracted: KList<String>,
val normalized: String,
)
fun processLiteralRestricted(
input: String,
parser: (String) -> String,
): String {
val trimmed =
input
.replace("'-'", "-")
.replace("'~'", "~")
.trim()
val regex = Regex("""^(-)?(~)?\(?(o|p)?\(?(-)?\(?([^()]+)\)?\)?\)?$""")
val match = regex.matchEntire(trimmed) ?: return parser(trimmed)
val atomText = match.groups[5]!!.value
return trimmed.replace(atomText, parser(atomText))
}
fun process(text: String): String =
processLiteralRestricted(text) {
processInternal(it).normalized
}
fun processInternal(text: String): Result {
val text =
text
.trim()
.removeSurrounding("'")
.removeSurrounding("\"")
if (text.contains("(")) {
return Result(emptyList(), text)
}
// 1. Uppercase extraction
val upperWords = upperRegex.findAll(text).toList()
if (upperWords.isNotEmpty()) {
val upperMatches = upperWords.map { it.value }
val predicate = buildPredicate(text, upperMatches)
val template = buildTemplate(text, upperWords)
knownTemplates[template] = upperMatches.size
return Result(upperMatches, predicate)
}
// 2. Bracket extraction
val bracketMatches = bracketRegex.findAll(text).map { it.groupValues[1] }.toList()
if (bracketMatches.isNotEmpty()) {
val predicate = buildPredicate(text.replace("[", "").replace("]", ""), bracketMatches)
return Result(bracketMatches, predicate)
}
// 3. Template matching
for ((template, _) in knownTemplates) {
val regex = Regex(template)
val match = regex.matchEntire(text)
if (match != null) {
val extracted = match.groupValues.drop(1)
val res = buildPredicate(text, extracted)
return Result(extracted, res)
}
}
// 4. Fallback
return Result(emptyList(), text.replace(" ", "_"))
}
private fun buildPredicate(
text: String,
extracted: KList<String>,
): String {
var tmp = text
// Remove extracted elements
extracted.forEach {
tmp = tmp.replaceFirst(Regex("""\b${Regex.escape(it)}\b"""), "")
}
// Normalize predicate
val predicate =
tmp
.trim()
.replace(Regex("""\s+"""), "_")
// Build arguments
val args = extracted.joinToString(", ")
return "$predicate($args)"
}
// The pattern of the texts written as [text], with other words in place of [words]: the rest of the text, the
// user's, matched as it is (escaped)
private fun buildTemplate(
text: String,
words: KList<MatchResult>,
): String {
var last = 0
val template = StringBuilder("^")
words.forEach { word ->
template.append(Regex.escape(text.substring(last, word.range.first))).append("(.+?)")
last = word.range.last + 1
}
return template.append(Regex.escape(text.substring(last))).append("$").toString()
}
}
fun parseTerm(
extractor: TemplateExtractor,
target: KList<Term>,
): KList<Term> {
val mapped =
target.map { term ->
if (term.isTuple) {
term.asTuple()!!.toList().joinToString(",", prefix = "(", postfix = ")") {
extractor.process(it.toString())
}
} else {
extractor.process(term.toString())
}
}
return Term.parse("[${mapped.joinToString(",")}]").asList()!!.toList()
}
override fun Solve.Request<ExecutionContext>.computeAllSubstitutions(
first: Term,
second: Term,
): Sequence<Substitution> {
val extractor = TemplateExtractor()
return sequenceOf(
Substitution.of(
second.asVar()!!,
List.of(
first
.asList()!!
.toList()
.map { original ->
original.asList()!!.toList().let {
RuleTerm.of(List.of(parseTerm(extractor, it))) ?: throw IllegalArgumentException()
}
},
),
),
)
}
}