use crate::error::Error;
use crate::framework::ValueBasedFramework;
use crate::types::{Audience, Value, ValueAssignment};
use argumentation::ArgumentationFramework;
pub fn from_apx(input: &str) -> Result<(ValueBasedFramework<String>, Audience), Error> {
let mut base = ArgumentationFramework::new();
let mut values = ValueAssignment::new();
let mut prefs: Vec<(String, String)> = Vec::new();
for (line_idx, raw_line) in input.lines().enumerate() {
let line_no = line_idx + 1;
let line = raw_line.split('%').next().unwrap_or("").trim();
if line.is_empty() {
continue;
}
let fact = line.strip_suffix('.').ok_or_else(|| Error::ApxParse {
line: line_no,
reason: format!("expected fact ending with '.', got: {line}"),
})?;
let (pred, args) = parse_pred(fact, line_no)?;
match pred {
"arg" => {
let [name] = expect_n_args::<1>(&args, line_no, "arg")?;
base.add_argument(name.to_string());
}
"att" => {
let [attacker, target] = expect_n_args::<2>(&args, line_no, "att")?;
let attacker_s = attacker.to_string();
let target_s = target.to_string();
base.add_attack(&attacker_s, &target_s)
.map_err(Error::from)?;
}
"val" => {
let [arg, value] = expect_n_args::<2>(&args, line_no, "val")?;
values.promote(arg.to_string(), Value::new(value.to_string()));
}
"valpref" => {
let [a, b] = expect_n_args::<2>(&args, line_no, "valpref")?;
prefs.push((a.to_string(), b.to_string()));
}
other => {
return Err(Error::ApxParse {
line: line_no,
reason: format!("unknown predicate: {other}"),
});
}
}
}
let audience = audience_from_prefs(&prefs);
Ok((ValueBasedFramework::new(base, values), audience))
}
pub fn to_apx(vaf: &ValueBasedFramework<String>, audience: &Audience) -> String {
let mut out = String::new();
let mut args: Vec<&String> = vaf.base().arguments().collect();
args.sort();
for arg in &args {
out.push_str(&format!("arg({arg}).\n"));
}
for target in &args {
let mut attackers: Vec<&String> = vaf.base().attackers(*target).into_iter().collect();
attackers.sort();
for atk in attackers {
out.push_str(&format!("att({atk}, {target}).\n"));
}
}
let mut entries: Vec<(&String, &[Value])> = vaf
.value_assignment()
.entries()
.collect();
entries.sort_by(|a, b| a.0.cmp(b.0));
for (arg, vals) in entries {
for v in vals {
out.push_str(&format!("val({arg}, {v}).\n"));
}
}
let all_values: Vec<&Value> = audience.values().collect();
let mut emitted = std::collections::BTreeSet::new();
for a in &all_values {
for b in &all_values {
if audience.prefers(a, b) && emitted.insert((a.as_str(), b.as_str())) {
out.push_str(&format!("valpref({a}, {b}).\n"));
}
}
}
out
}
fn parse_pred(fact: &str, line: usize) -> Result<(&str, Vec<&str>), Error> {
let open = fact.find('(').ok_or_else(|| Error::ApxParse {
line,
reason: format!("expected '(' in fact: {fact}"),
})?;
let close = fact.rfind(')').ok_or_else(|| Error::ApxParse {
line,
reason: format!("expected ')' in fact: {fact}"),
})?;
if close <= open {
return Err(Error::ApxParse {
line,
reason: format!("malformed fact: {fact}"),
});
}
let pred = &fact[..open];
let args_str = &fact[open + 1..close];
let args: Vec<&str> = args_str.split(',').map(|s| s.trim()).collect();
Ok((pred, args))
}
fn expect_n_args<const N: usize>(
args: &[&str],
line: usize,
pred: &str,
) -> Result<[String; N], Error> {
if args.len() != N {
return Err(Error::ApxParse {
line,
reason: format!(
"{pred} expects {N} arg(s), got {got}",
got = args.len()
),
});
}
let mut out: [String; N] = std::array::from_fn(|_| String::new());
for (i, a) in args.iter().enumerate() {
out[i] = (*a).to_string();
}
Ok(out)
}
fn audience_from_prefs(prefs: &[(String, String)]) -> Audience {
use std::collections::{BTreeSet, HashMap, HashSet};
if prefs.is_empty() {
return Audience::new();
}
let mut all: BTreeSet<String> = BTreeSet::new();
for (a, b) in prefs {
all.insert(a.clone());
all.insert(b.clone());
}
let mut outranks: HashMap<String, HashSet<String>> = HashMap::new();
let mut indegree: HashMap<String, usize> = HashMap::new();
for v in &all {
outranks.entry(v.clone()).or_default();
indegree.entry(v.clone()).or_insert(0);
}
for (a, b) in prefs {
if outranks.get_mut(a).unwrap().insert(b.clone()) {
*indegree.get_mut(b).unwrap() += 1;
}
}
let mut tiers: Vec<Vec<Value>> = Vec::new();
let mut remaining: HashSet<String> = all.into_iter().collect();
while !remaining.is_empty() {
let mut current_tier: Vec<String> = remaining
.iter()
.filter(|v| *indegree.get(*v).unwrap() == 0)
.cloned()
.collect();
if current_tier.is_empty() {
current_tier = remaining.iter().cloned().collect();
}
current_tier.sort();
for v in ¤t_tier {
remaining.remove(v);
if let Some(succs) = outranks.get(v) {
for s in succs {
if let Some(d) = indegree.get_mut(s)
&& *d > 0
{
*d -= 1;
}
}
}
}
tiers.push(current_tier.into_iter().map(Value::new).collect());
}
Audience::from_tiers(tiers)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_small_vaf() {
let input = r#"
% Hal & Carla in APX
arg(h1).
arg(c1).
att(h1, c1).
att(c1, h1).
val(h1, life).
val(c1, property).
valpref(life, property).
"#;
let (vaf, audience) = from_apx(input).unwrap();
assert_eq!(vaf.base().len(), 2);
assert!(audience.prefers(&Value::new("life"), &Value::new("property")));
let serialised = to_apx(&vaf, &audience);
let (vaf2, audience2) = from_apx(&serialised).unwrap();
assert_eq!(vaf2.base().len(), 2);
assert!(audience2.prefers(&Value::new("life"), &Value::new("property")));
}
#[test]
fn parse_error_reports_line_and_predicate() {
let input = "arg(a).\nbogus(stuff).\n";
let err = from_apx(input).unwrap_err();
match err {
Error::ApxParse { line, reason } => {
assert_eq!(line, 2);
assert!(reason.contains("unknown predicate"));
}
other => panic!("wrong error variant: {other:?}"),
}
}
#[test]
fn comments_and_whitespace_ignored() {
let input = r#"
% header comment
arg(a). % trailing comment
arg(b).
att(a, b).
"#;
let (vaf, _) = from_apx(input).unwrap();
assert_eq!(vaf.base().len(), 2);
}
#[test]
fn empty_input_yields_empty_vaf() {
let (vaf, audience) = from_apx("").unwrap();
assert_eq!(vaf.base().len(), 0);
assert_eq!(audience.value_count(), 0);
}
#[test]
fn multi_tier_audience_emerges_from_chained_prefs() {
let input = r#"
arg(a).
arg(b).
arg(c).
val(a, life).
val(b, fairness).
val(c, property).
valpref(life, fairness).
valpref(fairness, property).
"#;
let (_vaf, audience) = from_apx(input).unwrap();
assert_eq!(audience.tier_count(), 3);
assert!(audience.prefers(&Value::new("life"), &Value::new("fairness")));
assert!(audience.prefers(&Value::new("fairness"), &Value::new("property")));
assert!(audience.prefers(&Value::new("life"), &Value::new("property")));
}
}