Skip to main content

sim_shape/compare/
venn.rs

1//! `VennShapeSet`: a shape object that reasons about set membership across a
2//! collection of shapes built from and/or/not combinators.
3
4use std::{collections::BTreeSet, sync::Arc};
5
6use sim_kernel::{ClassRef, Cx, Error, Expr, Object, ObjectEncode, ObjectEncoding, Result, Symbol};
7
8use crate::{AndShape, NotShape, OrShape, Shape};
9
10/// Named set of shapes used to build Venn-style regions.
11///
12/// `VennShapeSet` is a runtime object so Lisp helpers can create it once and
13/// then request union, intersection, selected-only, outside, or exactly regions.
14#[derive(Clone)]
15pub struct VennShapeSet {
16    members: Vec<(Symbol, Arc<dyn Shape>)>,
17}
18
19impl VennShapeSet {
20    /// Create a named Venn set from shape members.
21    pub fn new(members: Vec<(Symbol, Arc<dyn Shape>)>) -> Self {
22        Self { members }
23    }
24
25    /// Return the named members in registration order.
26    pub fn members(&self) -> &[(Symbol, Arc<dyn Shape>)] {
27        &self.members
28    }
29
30    /// Build a shape accepted by any member.
31    pub fn union(&self) -> Arc<dyn Shape> {
32        Arc::new(OrShape::new(self.member_shapes()))
33    }
34
35    /// Build a shape accepted by every member.
36    pub fn intersection(&self) -> Arc<dyn Shape> {
37        Arc::new(AndShape::new(self.member_shapes()))
38    }
39
40    /// Build a shape accepted by one named member and rejected by the others.
41    pub fn only(&self, name: &Symbol) -> Result<Arc<dyn Shape>> {
42        let target = self.member_shape(name)?.clone();
43        let others = self
44            .members
45            .iter()
46            .filter(|(candidate, _)| candidate != name)
47            .map(|(_, shape)| shape.clone())
48            .collect::<Vec<_>>();
49        if others.is_empty() {
50            return Ok(target);
51        }
52        Ok(Arc::new(AndShape::new(vec![
53            target,
54            Arc::new(NotShape::new(Arc::new(OrShape::new(others)))),
55        ])))
56    }
57
58    /// Build a shape rejected by the union of all members.
59    pub fn outside_all(&self) -> Arc<dyn Shape> {
60        Arc::new(NotShape::new(self.union()))
61    }
62
63    /// Build a shape accepted by exactly the selected member names.
64    pub fn exactly(&self, names: &[Symbol]) -> Result<Arc<dyn Shape>> {
65        let selected = names.iter().cloned().collect::<BTreeSet<_>>();
66        for name in &selected {
67            self.member_shape(name)?;
68        }
69
70        let mut parts = Vec::new();
71        let mut others = Vec::new();
72        for (name, shape) in &self.members {
73            if selected.contains(name) {
74                parts.push(shape.clone());
75            } else {
76                others.push(shape.clone());
77            }
78        }
79        if !others.is_empty() {
80            parts.push(Arc::new(NotShape::new(Arc::new(OrShape::new(others)))));
81        }
82        Ok(Arc::new(AndShape::new(parts)))
83    }
84
85    fn member_shape(&self, name: &Symbol) -> Result<&Arc<dyn Shape>> {
86        self.members
87            .iter()
88            .find_map(|(candidate, shape)| (candidate == name).then_some(shape))
89            .ok_or_else(|| Error::Eval(format!("shape-venn: unknown member {name}")))
90    }
91
92    fn member_shapes(&self) -> Vec<Arc<dyn Shape>> {
93        self.members
94            .iter()
95            .map(|(_, shape)| shape.clone())
96            .collect()
97    }
98}
99
100impl Object for VennShapeSet {
101    fn display(&self, _cx: &mut Cx) -> Result<String> {
102        Ok(format!("#<shape-venn {} members>", self.members.len()))
103    }
104
105    fn as_any(&self) -> &dyn std::any::Any {
106        self
107    }
108}
109
110impl sim_kernel::ObjectCompat for VennShapeSet {
111    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
112        let symbol = crate::citizen::venn_shape_set_class_symbol();
113        if let Some(value) = cx.registry().class_by_symbol(&symbol) {
114            return Ok(value.clone());
115        }
116        cx.factory().nil()
117    }
118
119    fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
120        match self.object_encoding(cx)? {
121            ObjectEncoding::Constructor { class, args } => Ok(Expr::Call {
122                operator: Box::new(Expr::Symbol(class)),
123                args,
124            }),
125            _ => Err(Error::Eval(
126                "venn shape set produced a non-constructor object encoding; only \
127                 constructor encodings can render as an expression"
128                    .to_owned(),
129            )),
130        }
131    }
132
133    fn as_object_encoder(&self) -> Option<&dyn ObjectEncode> {
134        Some(self)
135    }
136}