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(¬hing),
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}