Skip to main content

sim_lib_logic/
env.rs

1//! Substitution environment: the variable bindings built during unification.
2//!
3//! A [`LogicEnv`] maps logic variables to their bound terms and drives the
4//! unifier. It bridges to the kernel `Shape` contracts by projecting its
5//! bindings into a `ShapeMatch`; see the [`README`](https://docs.rs/sim-runtime).
6use std::{
7    collections::{BTreeMap, BTreeSet},
8    sync::Arc,
9};
10
11use sim_kernel::{Cx, Expr, MatchScore, Result, ShapeBindings, ShapeMatch, Symbol};
12use sim_shape::{AnyShape, CaptureShape, ExactExprShape, ListShape, Shape, ShapeObject};
13
14use crate::model::OccursCheck;
15
16/// A unification substitution: bindings from logic variables to terms.
17///
18/// Carries a resolution `depth` so renamed clause variables stay distinct
19/// across recursive calls.
20#[derive(Clone, Debug, Default, PartialEq, Eq)]
21pub struct LogicEnv {
22    captures: BTreeMap<Symbol, Expr>,
23    depth: usize,
24}
25
26impl LogicEnv {
27    /// Creates an empty environment at depth zero.
28    pub fn new() -> Self {
29        Self::default()
30    }
31
32    /// Creates an empty environment recorded at the given resolution depth.
33    pub fn with_depth(depth: usize) -> Self {
34        Self {
35            captures: BTreeMap::new(),
36            depth,
37        }
38    }
39
40    /// Returns the resolution depth recorded for this environment.
41    pub fn depth(&self) -> usize {
42        self.depth
43    }
44
45    /// Sets the recorded resolution depth.
46    pub fn set_depth(&mut self, depth: usize) {
47        self.depth = depth;
48    }
49
50    /// Applies the substitution to `expr`, recursively replacing bound
51    /// variables with their values.
52    pub fn apply(&self, expr: &Expr) -> Expr {
53        match expr {
54            Expr::Local(var) => match self.captures.get(var) {
55                Some(bound) => self.apply(bound),
56                None => Expr::Local(var.clone()),
57            },
58            Expr::List(items) => Expr::List(items.iter().map(|item| self.apply(item)).collect()),
59            Expr::Vector(items) => {
60                Expr::Vector(items.iter().map(|item| self.apply(item)).collect())
61            }
62            Expr::Map(entries) => Expr::Map(
63                entries
64                    .iter()
65                    .map(|(key, value)| (self.apply(key), self.apply(value)))
66                    .collect(),
67            ),
68            Expr::Set(items) => Expr::Set(items.iter().map(|item| self.apply(item)).collect()),
69            Expr::Call { operator, args } => Expr::Call {
70                operator: Box::new(self.apply(operator)),
71                args: args.iter().map(|arg| self.apply(arg)).collect(),
72            },
73            Expr::Infix {
74                operator,
75                left,
76                right,
77            } => Expr::Infix {
78                operator: operator.clone(),
79                left: Box::new(self.apply(left)),
80                right: Box::new(self.apply(right)),
81            },
82            Expr::Prefix { operator, arg } => Expr::Prefix {
83                operator: operator.clone(),
84                arg: Box::new(self.apply(arg)),
85            },
86            Expr::Postfix { operator, arg } => Expr::Postfix {
87                operator: operator.clone(),
88                arg: Box::new(self.apply(arg)),
89            },
90            Expr::Block(items) => Expr::Block(items.iter().map(|item| self.apply(item)).collect()),
91            Expr::Quote { mode, expr } => Expr::Quote {
92                mode: *mode,
93                expr: Box::new(self.apply(expr)),
94            },
95            Expr::Annotated { expr, annotations } => Expr::Annotated {
96                expr: Box::new(self.apply(expr)),
97                annotations: annotations
98                    .iter()
99                    .map(|(name, value)| (name.clone(), self.apply(value)))
100                    .collect(),
101            },
102            Expr::Extension { tag, payload } => Expr::Extension {
103                tag: tag.clone(),
104                payload: Box::new(self.apply(payload)),
105            },
106            other => other.clone(),
107        }
108    }
109
110    /// Returns the term directly bound to `var`, if any.
111    pub fn get(&self, var: &Symbol) -> Option<&Expr> {
112        self.captures.get(var)
113    }
114
115    /// Binds `var` to `value`, honoring the [`OccursCheck`] policy.
116    ///
117    /// Returns an error when an enabled occurs check detects that `var` occurs
118    /// in `value` (which would build a cyclic term).
119    pub fn bind(&mut self, var: Symbol, value: Expr, occurs_check: OccursCheck) -> Result<()> {
120        if matches!(occurs_check, OccursCheck::Always) && occurs(var.clone(), &value, self) {
121            return Err(sim_kernel::Error::Eval(format!(
122                "occurs check failed for ?{}",
123                var.name
124            )));
125        }
126        self.captures.insert(var, value);
127        Ok(())
128    }
129
130    /// Unifies two terms, extending the substitution in place.
131    ///
132    /// Returns `true` when the terms unify and `false` on a structural
133    /// mismatch; errors propagate only from a failed occurs check.
134    pub fn unify(
135        &mut self,
136        cx: &mut Cx,
137        left: &Expr,
138        right: &Expr,
139        occurs_check: OccursCheck,
140    ) -> Result<bool> {
141        let left = self.apply(left);
142        let right = self.apply(right);
143        if left.canonical_eq(&right) {
144            return Ok(true);
145        }
146
147        let left_match = self.shape_unify(cx, &left, &right, occurs_check)?;
148        let right_match = self.shape_unify(cx, &right, &left, occurs_check)?;
149        match (left_match, right_match) {
150            (ShapeUnify::Accepted(next), _) | (_, ShapeUnify::Accepted(next)) => {
151                *self = next;
152                Ok(true)
153            }
154            (ShapeUnify::Unsupported, _) | (_, ShapeUnify::Unsupported) => {
155                unify_ground(cx, self, &left, &right, occurs_check)
156            }
157            (ShapeUnify::Rejected, ShapeUnify::Rejected) => Ok(false),
158        }
159    }
160
161    fn shape_unify(
162        &self,
163        cx: &mut Cx,
164        pattern: &Expr,
165        subject: &Expr,
166        occurs_check: OccursCheck,
167    ) -> Result<ShapeUnify> {
168        let Some(shape) = shape_from_pattern(cx, pattern) else {
169            return Ok(ShapeUnify::Unsupported);
170        };
171        let matched = shape.check_expr(cx, subject)?;
172        if !matched.accepted {
173            return Ok(ShapeUnify::Rejected);
174        }
175        let mut next = self.clone();
176        if next.merge_shape_captures(cx, &matched.captures, occurs_check)? {
177            Ok(ShapeUnify::Accepted(next))
178        } else {
179            Ok(ShapeUnify::Rejected)
180        }
181    }
182
183    fn merge_shape_captures(
184        &mut self,
185        cx: &mut Cx,
186        captures: &ShapeBindings,
187        occurs_check: OccursCheck,
188    ) -> Result<bool> {
189        for (var, value) in captures.exprs() {
190            if !self.merge_shape_capture(cx, var.clone(), value.clone(), occurs_check)? {
191                return Ok(false);
192            }
193        }
194        Ok(true)
195    }
196
197    fn merge_shape_capture(
198        &mut self,
199        cx: &mut Cx,
200        var: Symbol,
201        value: Expr,
202        occurs_check: OccursCheck,
203    ) -> Result<bool> {
204        let value = self.apply(&value);
205        if let Some(bound) = self.captures.get(&var).cloned() {
206            let bound = self.apply(&bound);
207            return self.unify(cx, &bound, &value, occurs_check);
208        }
209        self.bind(var, value, occurs_check)?;
210        Ok(true)
211    }
212
213    /// Collects the distinct logic variables appearing in `expr`.
214    pub fn free_vars(&self, expr: &Expr) -> Vec<Symbol> {
215        let mut vars = BTreeSet::new();
216        collect_vars(expr, &mut vars);
217        vars.into_iter().collect()
218    }
219
220    /// Projects the current bindings into kernel `ShapeBindings`.
221    pub fn to_shape_bindings(&self, _cx: &mut Cx) -> Result<ShapeBindings> {
222        let mut bindings = ShapeBindings::new();
223        for (name, expr) in &self.captures {
224            bindings.bind_expr(name.clone(), self.apply(expr));
225        }
226        Ok(bindings)
227    }
228
229    /// Builds an accepting kernel `ShapeMatch` whose captures are this
230    /// environment's bindings.
231    pub fn as_shape_match(&self, cx: &mut Cx) -> Result<ShapeMatch> {
232        Ok(ShapeMatch {
233            accepted: true,
234            captures: self.to_shape_bindings(cx)?,
235            score: MatchScore::exact(100),
236            diagnostics: Vec::new(),
237        })
238    }
239}
240
241enum ShapeUnify {
242    Accepted(LogicEnv),
243    Rejected,
244    Unsupported,
245}
246
247fn shape_from_pattern(cx: &mut Cx, pattern: &Expr) -> Option<Arc<dyn Shape>> {
248    match pattern {
249        Expr::Local(var) => Some(Arc::new(CaptureShape::new(var.clone(), Arc::new(AnyShape)))),
250        Expr::List(items) => {
251            let item_shapes = items
252                .iter()
253                .map(|item| shape_from_pattern(cx, item))
254                .collect::<Option<Vec<_>>>()?;
255            Some(Arc::new(ListShape::new(item_shapes)))
256        }
257        Expr::Symbol(symbol) => resolve_shape_symbol(cx, symbol)
258            .or_else(|| Some(Arc::new(ExactExprShape::new(pattern.clone())))),
259        other if !contains_local(other) => Some(Arc::new(ExactExprShape::new(other.clone()))),
260        _ => None,
261    }
262}
263
264fn resolve_shape_symbol(cx: &mut Cx, symbol: &Symbol) -> Option<Arc<dyn Shape>> {
265    let value = cx.resolve_shape(symbol).ok()?;
266    let shape = value.object().downcast_ref::<ShapeObject>()?;
267    Some(Arc::clone(&shape.shape))
268}
269
270fn unify_ground(
271    cx: &mut Cx,
272    env: &mut LogicEnv,
273    left: &Expr,
274    right: &Expr,
275    occurs_check: OccursCheck,
276) -> Result<bool> {
277    match (left, right) {
278        (Expr::Nil, Expr::Nil)
279        | (Expr::Bool(_), Expr::Bool(_))
280        | (Expr::Number(_), Expr::Number(_))
281        | (Expr::Symbol(_), Expr::Symbol(_))
282        | (Expr::Local(_), Expr::Local(_))
283        | (Expr::String(_), Expr::String(_))
284        | (Expr::Bytes(_), Expr::Bytes(_)) => Ok(left.canonical_eq(right)),
285        (Expr::List(left_items), Expr::List(right_items))
286        | (Expr::Vector(left_items), Expr::Vector(right_items))
287        | (Expr::Set(left_items), Expr::Set(right_items))
288        | (Expr::Block(left_items), Expr::Block(right_items)) => {
289            unify_slices(cx, env, left_items, right_items, occurs_check)
290        }
291        (Expr::Map(left_entries), Expr::Map(right_entries)) => {
292            if left_entries.len() != right_entries.len() {
293                return Ok(false);
294            }
295            for ((left_key, left_value), (right_key, right_value)) in
296                left_entries.iter().zip(right_entries.iter())
297            {
298                if !env.unify(cx, left_key, right_key, occurs_check)? {
299                    return Ok(false);
300                }
301                if !env.unify(cx, left_value, right_value, occurs_check)? {
302                    return Ok(false);
303                }
304            }
305            Ok(true)
306        }
307        (
308            Expr::Call {
309                operator: left_op,
310                args: left_args,
311            },
312            Expr::Call {
313                operator: right_op,
314                args: right_args,
315            },
316        ) => {
317            if left_args.len() != right_args.len()
318                || !env.unify(cx, left_op, right_op, occurs_check)?
319            {
320                return Ok(false);
321            }
322            unify_slices(cx, env, left_args, right_args, occurs_check)
323        }
324        (
325            Expr::Quote {
326                mode: left_mode,
327                expr: left_expr,
328            },
329            Expr::Quote {
330                mode: right_mode,
331                expr: right_expr,
332            },
333        ) => {
334            if left_mode != right_mode {
335                return Ok(false);
336            }
337            env.unify(cx, left_expr, right_expr, occurs_check)
338        }
339        (
340            Expr::Annotated {
341                expr: left_expr,
342                annotations: left_annotations,
343            },
344            Expr::Annotated {
345                expr: right_expr,
346                annotations: right_annotations,
347            },
348        ) => {
349            if left_annotations.len() != right_annotations.len()
350                || !env.unify(cx, left_expr, right_expr, occurs_check)?
351            {
352                return Ok(false);
353            }
354            for ((left_name, left_value), (right_name, right_value)) in
355                left_annotations.iter().zip(right_annotations.iter())
356            {
357                if left_name != right_name
358                    || !env.unify(cx, left_value, right_value, occurs_check)?
359                {
360                    return Ok(false);
361                }
362            }
363            Ok(true)
364        }
365        (
366            Expr::Extension {
367                tag: left_tag,
368                payload: left_payload,
369            },
370            Expr::Extension {
371                tag: right_tag,
372                payload: right_payload,
373            },
374        ) => Ok(left_tag == right_tag && env.unify(cx, left_payload, right_payload, occurs_check)?),
375        (
376            Expr::Infix {
377                operator: left_op,
378                left: left_a,
379                right: left_b,
380            },
381            Expr::Infix {
382                operator: right_op,
383                left: right_a,
384                right: right_b,
385            },
386        ) => Ok(left_op == right_op
387            && env.unify(cx, left_a, right_a, occurs_check)?
388            && env.unify(cx, left_b, right_b, occurs_check)?),
389        (
390            Expr::Prefix {
391                operator: left_op,
392                arg: left_arg,
393            },
394            Expr::Prefix {
395                operator: right_op,
396                arg: right_arg,
397            },
398        )
399        | (
400            Expr::Postfix {
401                operator: left_op,
402                arg: left_arg,
403            },
404            Expr::Postfix {
405                operator: right_op,
406                arg: right_arg,
407            },
408        ) => Ok(left_op == right_op && env.unify(cx, left_arg, right_arg, occurs_check)?),
409        _ => Ok(false),
410    }
411}
412
413fn unify_slices(
414    cx: &mut Cx,
415    env: &mut LogicEnv,
416    left: &[Expr],
417    right: &[Expr],
418    occurs_check: OccursCheck,
419) -> Result<bool> {
420    if left.len() != right.len() {
421        return Ok(false);
422    }
423    for (left_item, right_item) in left.iter().zip(right.iter()) {
424        if !env.unify(cx, left_item, right_item, occurs_check)? {
425            return Ok(false);
426        }
427    }
428    Ok(true)
429}
430
431fn occurs(var: Symbol, expr: &Expr, env: &LogicEnv) -> bool {
432    match env.apply(expr) {
433        Expr::Local(candidate) => candidate == var,
434        Expr::List(items) | Expr::Vector(items) | Expr::Set(items) | Expr::Block(items) => {
435            items.iter().any(|item| occurs(var.clone(), item, env))
436        }
437        Expr::Map(entries) => entries
438            .iter()
439            .any(|(key, value)| occurs(var.clone(), key, env) || occurs(var.clone(), value, env)),
440        Expr::Call { operator, args } => {
441            occurs(var.clone(), &operator, env)
442                || args.iter().any(|arg| occurs(var.clone(), arg, env))
443        }
444        Expr::Infix { left, right, .. } => {
445            occurs(var.clone(), &left, env) || occurs(var, &right, env)
446        }
447        Expr::Prefix { arg, .. } | Expr::Postfix { arg, .. } => occurs(var, &arg, env),
448        Expr::Quote { expr, .. } => occurs(var, &expr, env),
449        Expr::Annotated { expr, annotations } => {
450            occurs(var.clone(), &expr, env)
451                || annotations
452                    .iter()
453                    .any(|(_, value)| occurs(var.clone(), value, env))
454        }
455        Expr::Extension { payload, .. } => occurs(var, &payload, env),
456        _ => false,
457    }
458}
459
460fn contains_local(expr: &Expr) -> bool {
461    match expr {
462        Expr::Local(_) => true,
463        Expr::List(items) | Expr::Vector(items) | Expr::Set(items) | Expr::Block(items) => {
464            items.iter().any(contains_local)
465        }
466        Expr::Map(entries) => entries
467            .iter()
468            .any(|(key, value)| contains_local(key) || contains_local(value)),
469        Expr::Call { operator, args } => {
470            contains_local(operator) || args.iter().any(contains_local)
471        }
472        Expr::Infix { left, right, .. } => contains_local(left) || contains_local(right),
473        Expr::Prefix { arg, .. } | Expr::Postfix { arg, .. } => contains_local(arg),
474        Expr::Quote { expr, .. } => contains_local(expr),
475        Expr::Annotated { expr, annotations } => {
476            contains_local(expr) || annotations.iter().any(|(_, value)| contains_local(value))
477        }
478        Expr::Extension { payload, .. } => contains_local(payload),
479        _ => false,
480    }
481}
482
483fn collect_vars(expr: &Expr, vars: &mut BTreeSet<Symbol>) {
484    match expr {
485        Expr::Local(var) => {
486            vars.insert(var.clone());
487        }
488        Expr::List(items) | Expr::Vector(items) | Expr::Set(items) | Expr::Block(items) => {
489            for item in items {
490                collect_vars(item, vars);
491            }
492        }
493        Expr::Map(entries) => {
494            for (key, value) in entries {
495                collect_vars(key, vars);
496                collect_vars(value, vars);
497            }
498        }
499        Expr::Call { operator, args } => {
500            collect_vars(operator, vars);
501            for arg in args {
502                collect_vars(arg, vars);
503            }
504        }
505        Expr::Infix { left, right, .. } => {
506            collect_vars(left, vars);
507            collect_vars(right, vars);
508        }
509        Expr::Prefix { arg, .. } | Expr::Postfix { arg, .. } => collect_vars(arg, vars),
510        Expr::Quote { expr, .. } => collect_vars(expr, vars),
511        Expr::Annotated { expr, annotations } => {
512            collect_vars(expr, vars);
513            for (_, value) in annotations {
514                collect_vars(value, vars);
515            }
516        }
517        Expr::Extension { payload, .. } => collect_vars(payload, vars),
518        _ => {}
519    }
520}