use std::collections::HashMap;
pub type AtomId = u32;
pub type AgentId = u32;
pub type AgentMask = u32;
pub type FormulaId = u32;
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum Node {
True,
Atom(AtomId),
Not(FormulaId),
And(FormulaId, FormulaId),
Knows(AgentId, FormulaId),
Believes(AgentId, FormulaId),
Safe(AgentId, FormulaId),
CondBel(AgentId, FormulaId, FormulaId),
Common(AgentMask, FormulaId),
}
#[derive(Default, Debug, Clone)]
pub struct Store {
nodes: Vec<Node>,
map: HashMap<Node, FormulaId>,
}
impl Store {
pub fn mk(&mut self, n: Node) -> FormulaId {
if let Some(&i) = self.map.get(&n) {
return i;
}
let i = self.nodes.len() as FormulaId;
self.nodes.push(n.clone());
self.map.insert(n, i);
i
}
pub fn node(&self, f: FormulaId) -> &Node {
debug_assert!((f as usize) < self.nodes.len(), "id not produced by this store");
&self.nodes[f as usize]
}
pub fn len(&self) -> usize {
self.nodes.len()
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn tru(&mut self) -> FormulaId {
self.mk(Node::True)
}
pub fn fls(&mut self) -> FormulaId {
let t = self.tru();
self.mk(Node::Not(t))
}
pub fn atom(&mut self, a: AtomId) -> FormulaId {
self.mk(Node::Atom(a))
}
pub fn not(&mut self, f: FormulaId) -> FormulaId {
self.mk(Node::Not(f))
}
pub fn and(&mut self, a: FormulaId, b: FormulaId) -> FormulaId {
self.mk(Node::And(a, b))
}
pub fn or(&mut self, a: FormulaId, b: FormulaId) -> FormulaId {
let na = self.not(a);
let nb = self.not(b);
let c = self.and(na, nb);
self.not(c)
}
pub fn implies(&mut self, a: FormulaId, b: FormulaId) -> FormulaId {
let na = self.not(a);
self.or(na, b)
}
pub fn all(&mut self, fs: &[FormulaId]) -> FormulaId {
match fs.split_first() {
None => self.tru(),
Some((&h, rest)) => rest.iter().fold(h, |acc, &f| self.and(acc, f)),
}
}
pub fn any(&mut self, fs: &[FormulaId]) -> FormulaId {
match fs.split_first() {
None => self.fls(),
Some((&h, rest)) => rest.iter().fold(h, |acc, &f| self.or(acc, f)),
}
}
pub fn knows(&mut self, i: AgentId, f: FormulaId) -> FormulaId {
self.mk(Node::Knows(i, f))
}
pub fn believes(&mut self, i: AgentId, f: FormulaId) -> FormulaId {
self.mk(Node::Believes(i, f))
}
pub fn safe(&mut self, i: AgentId, f: FormulaId) -> FormulaId {
self.mk(Node::Safe(i, f))
}
pub fn cond_bel(&mut self, i: AgentId, psi: FormulaId, phi: FormulaId) -> FormulaId {
self.mk(Node::CondBel(i, psi, phi))
}
pub fn common(&mut self, g: AgentMask, f: FormulaId) -> FormulaId {
self.mk(Node::Common(g, f))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identical_subterms_share_one_id() {
let mut s = Store::default();
let p = s.atom(0);
let a = s.believes(0, p);
let b = s.believes(0, p);
assert_eq!(a, b, "hash-consing must return the same id");
let before = s.len();
let _ = s.believes(0, p);
assert_eq!(s.len(), before, "re-making a node must not grow the arena");
}
#[test]
fn or_is_built_from_not_and_and() {
let mut s = Store::default();
let p = s.atom(0);
let q = s.atom(1);
let disj = s.or(p, q);
match s.node(disj) {
Node::Not(inner) => match s.node(*inner) {
Node::And(x, y) => {
assert!(matches!(s.node(*x), Node::Not(f) if *f == p));
assert!(matches!(s.node(*y), Node::Not(f) if *f == q));
}
other => panic!("expected And, got {other:?}"),
},
other => panic!("expected Not, got {other:?}"),
}
}
#[test]
fn empty_conjunction_is_true_and_empty_disjunction_is_false() {
let mut s = Store::default();
let t = s.tru();
let f = s.fls();
assert_eq!(s.all(&[]), t, "empty conjunction must be verum");
assert_eq!(s.any(&[]), f, "empty disjunction must be falsum");
}
}