Skip to main content

sim_lib_pattern/
adt.rs

1use std::{
2    collections::BTreeMap,
3    sync::{Arc, OnceLock},
4};
5
6use sim_kernel::{
7    Args, Callable, Cx, Error, Expr, Object, ObjectCompat, Result, Shape, Symbol, Value,
8};
9
10use crate::{AdtShape, VariantShape};
11
12/// A named field of an ADT variant, carrying the kernel [`Shape`] that checks
13/// and binds its value.
14///
15/// The kernel defines the [`Shape`] match/binding contract; a `PatternField`
16/// pairs that contract with a field name so variant construction and matching
17/// run the field's shape over the supplied value.
18#[derive(Clone)]
19pub struct PatternField {
20    name: Symbol,
21    shape: Arc<dyn Shape>,
22}
23
24impl PatternField {
25    /// Builds a field with the given name and checking [`Shape`].
26    pub fn new(name: Symbol, shape: Arc<dyn Shape>) -> Self {
27        Self { name, shape }
28    }
29
30    /// Returns the field name.
31    pub fn name(&self) -> &Symbol {
32        &self.name
33    }
34
35    /// Returns the kernel [`Shape`] that checks and binds the field value.
36    pub fn shape(&self) -> &Arc<dyn Shape> {
37        &self.shape
38    }
39}
40
41/// One named variant of an [`AlgebraicDataType`], with its ordered fields.
42#[derive(Clone)]
43pub struct VariantDeclaration {
44    symbol: Symbol,
45    fields: Vec<PatternField>,
46}
47
48impl VariantDeclaration {
49    /// Builds a variant with the given tag symbol and ordered fields.
50    pub fn new(symbol: Symbol, fields: Vec<PatternField>) -> Self {
51        Self { symbol, fields }
52    }
53
54    /// Builds a field-less (nullary) variant such as an enum tag.
55    pub fn nullary(symbol: Symbol) -> Self {
56        Self {
57            symbol,
58            fields: Vec::new(),
59        }
60    }
61
62    /// Returns the variant tag symbol.
63    pub fn symbol(&self) -> &Symbol {
64        &self.symbol
65    }
66
67    /// Returns the variant fields in declaration order.
68    pub fn fields(&self) -> &[PatternField] {
69        &self.fields
70    }
71}
72
73/// A tagged-union type: a named set of variants, each with its own fields.
74///
75/// The kernel defines the [`Shape`] match/binding protocol; this type is the
76/// pattern organ's concrete declaration of an algebraic data type. It produces
77/// [`VariantConstructor`]s for building tagged values and a kernel [`Shape`]
78/// ([`AdtShape`]) for matching them. See the [crate README] for the
79/// protocol-versus-behavior boundary.
80///
81/// [crate README]: https://docs.rs/sim-runtime
82///
83/// # Examples
84///
85/// ```
86/// use std::sync::Arc;
87/// use sim_kernel::Symbol;
88/// use sim_lib_pattern::{AlgebraicDataType, VariantDeclaration};
89///
90/// let maybe = AlgebraicDataType::new(
91///     Symbol::qualified("adt", "Maybe"),
92///     vec![
93///         VariantDeclaration::nullary(Symbol::qualified("maybe", "Nothing")),
94///         VariantDeclaration::nullary(Symbol::qualified("maybe", "Just")),
95///     ],
96/// )
97/// .unwrap();
98/// assert_eq!(maybe.constructors().len(), 2);
99/// assert!(maybe.constructor(&Symbol::qualified("maybe", "Just")).is_some());
100/// ```
101#[derive(Clone)]
102pub struct AlgebraicDataType {
103    symbol: Symbol,
104    variants: BTreeMap<Symbol, VariantDeclaration>,
105}
106
107impl AlgebraicDataType {
108    /// Builds an ADT from its name and variants.
109    ///
110    /// # Errors
111    ///
112    /// Returns an error if two variants share a tag symbol.
113    pub fn new(symbol: Symbol, variants: Vec<VariantDeclaration>) -> Result<Self> {
114        let mut by_symbol = BTreeMap::new();
115        for variant in variants {
116            if by_symbol
117                .insert(variant.symbol().clone(), variant.clone())
118                .is_some()
119            {
120                return Err(Error::Eval(format!(
121                    "duplicate ADT variant {}",
122                    variant.symbol()
123                )));
124            }
125        }
126        Ok(Self {
127            symbol,
128            variants: by_symbol,
129        })
130    }
131
132    /// Returns the ADT name symbol.
133    pub fn symbol(&self) -> &Symbol {
134        &self.symbol
135    }
136
137    /// Iterates the declared variants in tag order.
138    pub fn variants(&self) -> impl Iterator<Item = &VariantDeclaration> {
139        self.variants.values()
140    }
141
142    /// Returns the constructor for `variant`, or `None` if it is not declared.
143    pub fn constructor(&self, variant: &Symbol) -> Option<VariantConstructor> {
144        self.variants
145            .get(variant)
146            .cloned()
147            .map(|variant| VariantConstructor::new(self.symbol.clone(), variant))
148    }
149
150    /// Returns a constructor for every declared variant.
151    pub fn constructors(&self) -> Vec<VariantConstructor> {
152        self.variants
153            .values()
154            .cloned()
155            .map(|variant| VariantConstructor::new(self.symbol.clone(), variant))
156            .collect()
157    }
158
159    /// Returns the kernel [`Shape`] that matches any value of this ADT.
160    pub fn shape(&self) -> Arc<dyn Shape> {
161        Arc::new(AdtShape::new(
162            self.symbol.clone(),
163            self.constructors()
164                .into_iter()
165                .map(|constructor| constructor.variant_shape())
166                .collect(),
167        ))
168    }
169}
170
171/// A callable builder for one ADT variant.
172///
173/// Calling a constructor checks each argument against the variant's field
174/// [`Shape`]s and produces a [`TaggedValue`]. It is also a runtime [`Object`]
175/// (callable, table-reflectable) so it can be installed as a value.
176#[derive(Clone)]
177pub struct VariantConstructor {
178    adt: Symbol,
179    variant: VariantDeclaration,
180}
181
182impl VariantConstructor {
183    /// Builds a constructor for `variant` within ADT `adt`.
184    pub fn new(adt: Symbol, variant: VariantDeclaration) -> Self {
185        Self { adt, variant }
186    }
187
188    /// Returns the owning ADT name symbol.
189    pub fn adt(&self) -> &Symbol {
190        &self.adt
191    }
192
193    /// Returns the variant tag symbol.
194    pub fn variant(&self) -> &Symbol {
195        self.variant.symbol()
196    }
197
198    /// Returns the variant fields in declaration order.
199    pub fn fields(&self) -> &[PatternField] {
200        self.variant.fields()
201    }
202
203    /// Returns the kernel [`VariantShape`] that matches this variant.
204    pub fn variant_shape(&self) -> VariantShape {
205        VariantShape::new(
206            self.adt.clone(),
207            self.variant.symbol().clone(),
208            self.variant.fields().to_vec(),
209        )
210    }
211
212    /// Returns the variant matcher as a boxed kernel [`Shape`].
213    pub fn shape(&self) -> Arc<dyn Shape> {
214        Arc::new(self.variant_shape())
215    }
216
217    /// Builds a [`TaggedValue`] from the supplied field values.
218    ///
219    /// # Errors
220    ///
221    /// Returns an error if the arity is wrong or a field [`Shape`] rejects its
222    /// value.
223    pub fn construct(&self, cx: &mut Cx, fields: Vec<Value>) -> Result<Value> {
224        if fields.len() != self.variant.fields().len() {
225            return Err(Error::Eval(format!(
226                "constructor {} expected {} fields, got {}",
227                self.variant.symbol(),
228                self.variant.fields().len(),
229                fields.len()
230            )));
231        }
232        for (field, value) in self.variant.fields().iter().zip(fields.iter()) {
233            let matched = field.shape().check_value(cx, value.clone())?;
234            if !matched.accepted {
235                return Err(Error::Eval(format!(
236                    "constructor {} rejected field {}: {}",
237                    self.variant.symbol(),
238                    field.name(),
239                    diagnostic_summary(&matched.diagnostics)
240                )));
241            }
242        }
243        let fields = self
244            .variant
245            .fields()
246            .iter()
247            .map(|field| field.name().clone())
248            .zip(fields)
249            .collect();
250        cx.factory().opaque(Arc::new(TaggedValue::new(
251            self.adt.clone(),
252            self.variant.symbol().clone(),
253            fields,
254        )))
255    }
256
257    /// Wraps the constructor itself as a callable runtime [`Value`].
258    pub fn as_value(&self, cx: &mut Cx) -> Result<Value> {
259        cx.factory().opaque(Arc::new(self.clone()))
260    }
261}
262
263impl Object for VariantConstructor {
264    fn display(&self, _cx: &mut Cx) -> Result<String> {
265        Ok(format!("#<constructor {}>", self.variant.symbol()))
266    }
267
268    fn as_any(&self) -> &dyn std::any::Any {
269        self
270    }
271}
272
273impl ObjectCompat for VariantConstructor {
274    fn as_callable(&self) -> Option<&dyn Callable> {
275        Some(self)
276    }
277
278    fn as_table(&self, cx: &mut Cx) -> Result<Value> {
279        cx.factory().table(vec![
280            (Symbol::new("adt"), cx.factory().symbol(self.adt().clone())?),
281            (
282                Symbol::new("variant"),
283                cx.factory().symbol(self.variant().clone())?,
284            ),
285            (
286                Symbol::new("arity"),
287                cx.factory().number_literal(
288                    Symbol::qualified("numbers", "f64"),
289                    self.fields().len().to_string(),
290                )?,
291            ),
292        ])
293    }
294}
295
296impl Callable for VariantConstructor {
297    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
298        self.construct(cx, args.into_vec())
299    }
300}
301
302#[sim_citizen_derive::non_citizen(
303    reason = "dynamic ADT variant value; canonical data is the variant symbol and field table",
304    kind = "marker"
305)]
306/// A constructed ADT value: an ADT name, a variant tag, and named field values.
307///
308/// This is the runtime [`Object`] that [`VariantConstructor::construct`]
309/// produces and that the [`AdtShape`]/[`VariantShape`] matchers recognize. Its
310/// canonical data is the variant symbol plus the field table.
311#[derive(Clone)]
312pub struct TaggedValue {
313    adt: Symbol,
314    variant: Symbol,
315    fields: Vec<(Symbol, Value)>,
316    header: OnceLock<sim_kernel::ObjectHeader>,
317}
318
319impl TaggedValue {
320    /// Builds a tagged value from its ADT name, variant tag, and named fields.
321    pub fn new(adt: Symbol, variant: Symbol, fields: Vec<(Symbol, Value)>) -> Self {
322        Self {
323            adt,
324            variant,
325            fields,
326            header: OnceLock::new(),
327        }
328    }
329
330    /// Returns the owning ADT name symbol.
331    pub fn adt(&self) -> &Symbol {
332        &self.adt
333    }
334
335    /// Returns the variant tag symbol.
336    pub fn variant(&self) -> &Symbol {
337        &self.variant
338    }
339
340    /// Returns the named field values in construction order.
341    pub fn fields(&self) -> &[(Symbol, Value)] {
342        &self.fields
343    }
344
345    /// Returns the value of field `name`, or `None` if absent.
346    pub fn field(&self, name: &Symbol) -> Option<&Value> {
347        self.fields
348            .iter()
349            .find_map(|(field, value)| (field == name).then_some(value))
350    }
351}
352
353impl Object for TaggedValue {
354    fn header(&self) -> &sim_kernel::ObjectHeader {
355        self.header.get_or_init(|| sim_kernel::ObjectHeader {
356            id: sim_kernel::Ref::Symbol(self.variant.clone()),
357            kind: Symbol::qualified("pattern", "tagged-value"),
358            trust: sim_kernel::TrustLevel::HostInternal,
359        })
360    }
361
362    fn display(&self, _cx: &mut Cx) -> Result<String> {
363        Ok(format!("#<{} {}>", self.adt, self.variant))
364    }
365
366    fn as_any(&self) -> &dyn std::any::Any {
367        self
368    }
369}
370
371impl ObjectCompat for TaggedValue {
372    fn as_table(&self, cx: &mut Cx) -> Result<Value> {
373        let fields = self
374            .fields
375            .iter()
376            .map(|(name, value)| (name.clone(), value.clone()))
377            .collect();
378        let fields = cx.factory().table(fields)?;
379        cx.factory().table(vec![
380            (Symbol::new("adt"), cx.factory().symbol(self.adt.clone())?),
381            (
382                Symbol::new("variant"),
383                cx.factory().symbol(self.variant.clone())?,
384            ),
385            (Symbol::new("fields"), fields),
386        ])
387    }
388
389    fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
390        let args = self
391            .fields
392            .iter()
393            .map(|(_, value)| value.object().as_expr(cx))
394            .collect::<Result<Vec<_>>>()?;
395        Ok(Expr::Call {
396            operator: Box::new(Expr::Symbol(self.variant.clone())),
397            args,
398        })
399    }
400}
401
402/// Downcasts a runtime [`Value`] to a [`TaggedValue`], if it is one.
403pub fn tagged_value(value: &Value) -> Option<&TaggedValue> {
404    value.object().downcast_ref::<TaggedValue>()
405}
406
407fn diagnostic_summary(diagnostics: &[sim_kernel::Diagnostic]) -> String {
408    diagnostics
409        .first()
410        .map(|diagnostic| diagnostic.message.clone())
411        .unwrap_or_else(|| "field shape rejected value".to_owned())
412}