1use crate::error::Error;
21use crate::framework::ValueBasedFramework;
22use crate::types::{Audience, Value, ValueAssignment};
23use argumentation::ArgumentationFramework;
24
25pub fn from_apx(input: &str) -> Result<(ValueBasedFramework<String>, Audience), Error> {
32 let mut base = ArgumentationFramework::new();
33 let mut values = ValueAssignment::new();
34 let mut prefs: Vec<(String, String)> = Vec::new();
35
36 for (line_idx, raw_line) in input.lines().enumerate() {
37 let line_no = line_idx + 1;
38 let line = raw_line.split('%').next().unwrap_or("").trim();
40 if line.is_empty() {
41 continue;
42 }
43 let fact = line.strip_suffix('.').ok_or_else(|| Error::ApxParse {
45 line: line_no,
46 reason: format!("expected fact ending with '.', got: {line}"),
47 })?;
48 let (pred, args) = parse_pred(fact, line_no)?;
50 match pred {
51 "arg" => {
52 let [name] = expect_n_args::<1>(&args, line_no, "arg")?;
53 base.add_argument(name.to_string());
54 }
55 "att" => {
56 let [attacker, target] = expect_n_args::<2>(&args, line_no, "att")?;
57 let attacker_s = attacker.to_string();
58 let target_s = target.to_string();
59 base.add_attack(&attacker_s, &target_s)
60 .map_err(Error::from)?;
61 }
62 "val" => {
63 let [arg, value] = expect_n_args::<2>(&args, line_no, "val")?;
64 values.promote(arg.to_string(), Value::new(value.to_string()));
65 }
66 "valpref" => {
67 let [a, b] = expect_n_args::<2>(&args, line_no, "valpref")?;
68 prefs.push((a.to_string(), b.to_string()));
69 }
70 other => {
71 return Err(Error::ApxParse {
72 line: line_no,
73 reason: format!("unknown predicate: {other}"),
74 });
75 }
76 }
77 }
78
79 let audience = audience_from_prefs(&prefs);
80 Ok((ValueBasedFramework::new(base, values), audience))
81}
82
83pub fn to_apx(vaf: &ValueBasedFramework<String>, audience: &Audience) -> String {
85 let mut out = String::new();
86 let mut args: Vec<&String> = vaf.base().arguments().collect();
87 args.sort();
88 for arg in &args {
89 out.push_str(&format!("arg({arg}).\n"));
90 }
91 for target in &args {
92 let mut attackers: Vec<&String> = vaf.base().attackers(*target).into_iter().collect();
93 attackers.sort();
94 for atk in attackers {
95 out.push_str(&format!("att({atk}, {target}).\n"));
96 }
97 }
98 let mut entries: Vec<(&String, &[Value])> = vaf
99 .value_assignment()
100 .entries()
101 .collect();
102 entries.sort_by(|a, b| a.0.cmp(b.0));
103 for (arg, vals) in entries {
104 for v in vals {
105 out.push_str(&format!("val({arg}, {v}).\n"));
106 }
107 }
108 let all_values: Vec<&Value> = audience.values().collect();
114 let mut emitted = std::collections::BTreeSet::new();
115 for a in &all_values {
116 for b in &all_values {
117 if audience.prefers(a, b) && emitted.insert((a.as_str(), b.as_str())) {
118 out.push_str(&format!("valpref({a}, {b}).\n"));
119 }
120 }
121 }
122 out
123}
124
125fn parse_pred(fact: &str, line: usize) -> Result<(&str, Vec<&str>), Error> {
126 let open = fact.find('(').ok_or_else(|| Error::ApxParse {
127 line,
128 reason: format!("expected '(' in fact: {fact}"),
129 })?;
130 let close = fact.rfind(')').ok_or_else(|| Error::ApxParse {
131 line,
132 reason: format!("expected ')' in fact: {fact}"),
133 })?;
134 if close <= open {
135 return Err(Error::ApxParse {
136 line,
137 reason: format!("malformed fact: {fact}"),
138 });
139 }
140 let pred = &fact[..open];
141 let args_str = &fact[open + 1..close];
142 let args: Vec<&str> = args_str.split(',').map(|s| s.trim()).collect();
143 Ok((pred, args))
144}
145
146fn expect_n_args<const N: usize>(
147 args: &[&str],
148 line: usize,
149 pred: &str,
150) -> Result<[String; N], Error> {
151 if args.len() != N {
152 return Err(Error::ApxParse {
153 line,
154 reason: format!(
155 "{pred} expects {N} arg(s), got {got}",
156 got = args.len()
157 ),
158 });
159 }
160 let mut out: [String; N] = std::array::from_fn(|_| String::new());
161 for (i, a) in args.iter().enumerate() {
162 out[i] = (*a).to_string();
163 }
164 Ok(out)
165}
166
167fn audience_from_prefs(prefs: &[(String, String)]) -> Audience {
172 use std::collections::{BTreeSet, HashMap, HashSet};
173
174 if prefs.is_empty() {
175 return Audience::new();
176 }
177
178 let mut all: BTreeSet<String> = BTreeSet::new();
180 for (a, b) in prefs {
181 all.insert(a.clone());
182 all.insert(b.clone());
183 }
184
185 let mut outranks: HashMap<String, HashSet<String>> = HashMap::new();
187 let mut indegree: HashMap<String, usize> = HashMap::new();
188 for v in &all {
189 outranks.entry(v.clone()).or_default();
190 indegree.entry(v.clone()).or_insert(0);
191 }
192 for (a, b) in prefs {
193 if outranks.get_mut(a).unwrap().insert(b.clone()) {
194 *indegree.get_mut(b).unwrap() += 1;
195 }
196 }
197
198 let mut tiers: Vec<Vec<Value>> = Vec::new();
199 let mut remaining: HashSet<String> = all.into_iter().collect();
200 while !remaining.is_empty() {
201 let mut current_tier: Vec<String> = remaining
202 .iter()
203 .filter(|v| *indegree.get(*v).unwrap() == 0)
204 .cloned()
205 .collect();
206 if current_tier.is_empty() {
207 current_tier = remaining.iter().cloned().collect();
209 }
210 current_tier.sort();
211 for v in ¤t_tier {
212 remaining.remove(v);
213 if let Some(succs) = outranks.get(v) {
215 for s in succs {
216 if let Some(d) = indegree.get_mut(s)
217 && *d > 0
218 {
219 *d -= 1;
220 }
221 }
222 }
223 }
224 tiers.push(current_tier.into_iter().map(Value::new).collect());
225 }
226
227 Audience::from_tiers(tiers)
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn round_trip_small_vaf() {
236 let input = r#"
237% Hal & Carla in APX
238arg(h1).
239arg(c1).
240att(h1, c1).
241att(c1, h1).
242val(h1, life).
243val(c1, property).
244valpref(life, property).
245"#;
246 let (vaf, audience) = from_apx(input).unwrap();
247 assert_eq!(vaf.base().len(), 2);
248 assert!(audience.prefers(&Value::new("life"), &Value::new("property")));
249 let serialised = to_apx(&vaf, &audience);
251 let (vaf2, audience2) = from_apx(&serialised).unwrap();
252 assert_eq!(vaf2.base().len(), 2);
253 assert!(audience2.prefers(&Value::new("life"), &Value::new("property")));
254 }
255
256 #[test]
257 fn parse_error_reports_line_and_predicate() {
258 let input = "arg(a).\nbogus(stuff).\n";
259 let err = from_apx(input).unwrap_err();
260 match err {
261 Error::ApxParse { line, reason } => {
262 assert_eq!(line, 2);
263 assert!(reason.contains("unknown predicate"));
264 }
265 other => panic!("wrong error variant: {other:?}"),
266 }
267 }
268
269 #[test]
270 fn comments_and_whitespace_ignored() {
271 let input = r#"
272% header comment
273arg(a). % trailing comment
274arg(b).
275
276att(a, b).
277"#;
278 let (vaf, _) = from_apx(input).unwrap();
279 assert_eq!(vaf.base().len(), 2);
280 }
281
282 #[test]
283 fn empty_input_yields_empty_vaf() {
284 let (vaf, audience) = from_apx("").unwrap();
285 assert_eq!(vaf.base().len(), 0);
286 assert_eq!(audience.value_count(), 0);
287 }
288
289 #[test]
290 fn multi_tier_audience_emerges_from_chained_prefs() {
291 let input = r#"
292arg(a).
293arg(b).
294arg(c).
295val(a, life).
296val(b, fairness).
297val(c, property).
298valpref(life, fairness).
299valpref(fairness, property).
300"#;
301 let (_vaf, audience) = from_apx(input).unwrap();
302 assert_eq!(audience.tier_count(), 3);
303 assert!(audience.prefers(&Value::new("life"), &Value::new("fairness")));
304 assert!(audience.prefers(&Value::new("fairness"), &Value::new("property")));
305 assert!(audience.prefers(&Value::new("life"), &Value::new("property")));
306 }
307}