Skip to main content

sim_lib_pattern/
matching.rs

1use std::sync::Arc;
2
3use sim_kernel::{
4    Cx, Diagnostic, Error, Expr, MatchScore, Result, Shape, ShapeBindings, ShapeMatch, Symbol,
5    Value,
6};
7
8use crate::{AlgebraicDataType, VariantConstructor};
9
10/// One arm of a match: a labelled pattern [`Shape`] plus optional coverage info.
11///
12/// The kernel defines the [`Shape`] match/binding contract; an arm wraps a
13/// shape with the label reported on a hit and, optionally, the ADT variant it
14/// covers so [`exhaustiveness_diagnostics`] can verify completeness.
15#[derive(Clone)]
16pub struct MatchArm {
17    label: Symbol,
18    shape: Arc<dyn Shape>,
19    covered_variant: Option<Symbol>,
20}
21
22impl MatchArm {
23    /// Builds an arm from a label and a checking [`Shape`], covering no variant.
24    pub fn new(label: Symbol, shape: Arc<dyn Shape>) -> Self {
25        Self {
26            label,
27            shape,
28            covered_variant: None,
29        }
30    }
31
32    /// Builds an arm matching `constructor`'s variant, recording its coverage.
33    pub fn for_constructor(constructor: &VariantConstructor) -> Self {
34        Self {
35            label: constructor.variant().clone(),
36            shape: constructor.shape(),
37            covered_variant: Some(constructor.variant().clone()),
38        }
39    }
40
41    /// Records the ADT variant this arm covers, for exhaustiveness checking.
42    pub fn with_covered_variant(mut self, variant: Symbol) -> Self {
43        self.covered_variant = Some(variant);
44        self
45    }
46
47    /// Returns the label reported when this arm matches.
48    pub fn label(&self) -> &Symbol {
49        &self.label
50    }
51
52    /// Returns the kernel [`Shape`] this arm checks against.
53    pub fn shape(&self) -> &Arc<dyn Shape> {
54        &self.shape
55    }
56
57    /// Returns the ADT variant this arm covers, if any.
58    pub fn covered_variant(&self) -> Option<&Symbol> {
59        self.covered_variant.as_ref()
60    }
61}
62
63/// The outcome of a successful [`match_value`]: which arm fired, its bindings,
64/// and the match score.
65#[derive(Clone, Debug)]
66pub struct PatternMatch {
67    arm_index: usize,
68    label: Symbol,
69    captures: ShapeBindings,
70    score: MatchScore,
71}
72
73impl PatternMatch {
74    /// Returns the index of the arm that matched.
75    pub fn arm_index(&self) -> usize {
76        self.arm_index
77    }
78
79    /// Returns the label of the arm that matched.
80    pub fn label(&self) -> &Symbol {
81        &self.label
82    }
83
84    /// Returns the kernel [`ShapeBindings`] captured by the matching arm.
85    pub fn captures(&self) -> &ShapeBindings {
86        &self.captures
87    }
88
89    /// Returns the kernel [`MatchScore`] the matching arm earned.
90    pub fn score(&self) -> MatchScore {
91        self.score
92    }
93}
94
95/// Matches `value` against `arms` in order, returning the first that accepts.
96///
97/// The kernel defines the [`Shape`] match/binding contract; this runs each
98/// arm's shape over the value and reports the first hit with its captured
99/// [`ShapeBindings`].
100///
101/// # Errors
102///
103/// Returns an error if no arm accepts the value.
104///
105/// # Examples
106///
107/// ```
108/// use std::sync::Arc;
109/// use sim_kernel::{Cx, DefaultFactory, NoopEvalPolicy, Symbol};
110/// use sim_lib_pattern::{
111///     AlgebraicDataType, MatchArm, VariantDeclaration, match_value,
112/// };
113///
114/// let mut cx = Cx::new(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));
115/// let maybe = AlgebraicDataType::new(
116///     Symbol::qualified("adt", "Maybe"),
117///     vec![
118///         VariantDeclaration::nullary(Symbol::qualified("maybe", "Nothing")),
119///         VariantDeclaration::nullary(Symbol::qualified("maybe", "Just")),
120///     ],
121/// )
122/// .unwrap();
123/// let nothing = maybe.constructor(&Symbol::qualified("maybe", "Nothing")).unwrap();
124/// let just = maybe.constructor(&Symbol::qualified("maybe", "Just")).unwrap();
125/// let value = just.construct(&mut cx, vec![]).unwrap();
126///
127/// let matched = match_value(
128///     &mut cx,
129///     value,
130///     &[
131///         MatchArm::for_constructor(&nothing),
132///         MatchArm::for_constructor(&just),
133///     ],
134/// )
135/// .unwrap();
136/// assert_eq!(matched.arm_index(), 1);
137/// assert_eq!(matched.label(), &Symbol::qualified("maybe", "Just"));
138/// ```
139pub fn match_value(cx: &mut Cx, value: Value, arms: &[MatchArm]) -> Result<PatternMatch> {
140    let mut diagnostics = Vec::new();
141    for (index, arm) in arms.iter().enumerate() {
142        let matched = arm.shape().check_value(cx, value.clone())?;
143        if matched.accepted {
144            return Ok(PatternMatch {
145                arm_index: index,
146                label: arm.label().clone(),
147                captures: matched.captures,
148                score: matched.score,
149            });
150        }
151        diagnostics.extend(matched.diagnostics);
152    }
153    Err(Error::Eval(format!(
154        "no pattern arm matched: {}",
155        diagnostic_summary(&diagnostics)
156    )))
157}
158
159/// Checks `value` against a single `shape`, returning the kernel [`ShapeMatch`].
160///
161/// Thin pass-through to the kernel match contract, exposed as the pattern
162/// organ's value-destructuring entry point.
163///
164/// # Errors
165///
166/// Propagates any error from the shape's value check.
167pub fn destructure_value(cx: &mut Cx, value: Value, shape: &dyn Shape) -> Result<ShapeMatch> {
168    shape.check_value(cx, value)
169}
170
171/// Checks `expr` against a single `shape`, returning the kernel [`ShapeMatch`].
172///
173/// The expression-side counterpart to [`destructure_value`], used to match and
174/// bind unevaluated [`Expr`] forms.
175///
176/// # Errors
177///
178/// Propagates any error from the shape's expression check.
179pub fn destructure_expr(cx: &mut Cx, expr: &Expr, shape: &dyn Shape) -> Result<ShapeMatch> {
180    shape.check_expr(cx, expr)
181}
182
183/// Returns diagnostics for any `adt` variants not covered by `arms`.
184///
185/// Returns an empty vector when the arms cover every variant; otherwise a
186/// single error diagnostic listing the missing variants.
187pub fn exhaustiveness_diagnostics(adt: &AlgebraicDataType, arms: &[MatchArm]) -> Vec<Diagnostic> {
188    let covered = arms
189        .iter()
190        .filter_map(MatchArm::covered_variant)
191        .collect::<std::collections::BTreeSet<_>>();
192    let missing = adt
193        .variants()
194        .filter(|variant| !covered.contains(variant.symbol()))
195        .map(|variant| variant.symbol().to_string())
196        .collect::<Vec<_>>();
197    if missing.is_empty() {
198        return Vec::new();
199    }
200    let mut diagnostic = Diagnostic::error(format!(
201        "non-exhaustive match for {}: missing {}",
202        adt.symbol(),
203        missing.join(", ")
204    ));
205    diagnostic.code = Some(Symbol::qualified("pattern", "non-exhaustive"));
206    vec![diagnostic]
207}
208
209fn diagnostic_summary(diagnostics: &[Diagnostic]) -> String {
210    diagnostics
211        .first()
212        .map(|diagnostic| diagnostic.message.clone())
213        .unwrap_or_else(|| "all pattern shapes rejected the value".to_owned())
214}