Skip to main content

sim_lib_view/
stack.rs

1//! The lens stack: the ordered set of lenses available for a value.
2//!
3//! A value has many peer lenses. The stack is the dispatcher's full ranking
4//! rather than just its winner: every eligible lens for a value, best first,
5//! ending at the universal default. The operator flips between entries with
6//! `intent/set-lens` (see [`crate::set_lens`]); switching never touches the
7//! value.
8
9use sim_kernel::{Cx, Expr, Result, Symbol};
10
11use crate::contract::LensKind;
12use crate::dispatch::{DispatchContext, DispatchReason, LensRegistry, best_shape_score};
13
14/// One entry in a value's lens stack.
15#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct LensStackEntry {
17    /// The lens id.
18    pub lens_id: Symbol,
19    /// Why and how strongly it matched.
20    pub reason: DispatchReason,
21}
22
23impl LensRegistry {
24    /// Build the ordered lens stack of `kind` for `target`: every eligible lens
25    /// best first, with the universal default last. Capability-denied lenses are
26    /// excluded.
27    pub fn lens_stack(
28        &self,
29        cx: &mut Cx,
30        kind: LensKind,
31        target: &Expr,
32        ctx: &DispatchContext,
33    ) -> Result<Vec<LensStackEntry>> {
34        // (tier, score, quality, -cost, id, reason). Higher tuple ranks first.
35        let mut ranked: Vec<(i32, i32, i32, i32, Symbol, DispatchReason)> = Vec::new();
36        for lens in self.lenses() {
37            if lens.meta.kind != kind || !self.allowed(lens, ctx) {
38                continue;
39            }
40            let meta = &lens.meta;
41            if meta.universal_default {
42                ranked.push((
43                    0,
44                    0,
45                    meta.quality,
46                    -meta.cost,
47                    meta.id.clone(),
48                    DispatchReason::UniversalDefault,
49                ));
50            } else if let Some(score) = best_shape_score(cx, lens, target)? {
51                ranked.push((
52                    2,
53                    score,
54                    meta.quality,
55                    -meta.cost,
56                    meta.id.clone(),
57                    DispatchReason::ShapeMatch(score),
58                ));
59            } else if class_matches(meta, ctx) {
60                ranked.push((
61                    1,
62                    0,
63                    meta.quality,
64                    -meta.cost,
65                    meta.id.clone(),
66                    DispatchReason::ClassMatch,
67                ));
68            }
69        }
70        // Sort best first; the stable sort keeps registration order on ties.
71        ranked.sort_by_key(|entry| std::cmp::Reverse((entry.0, entry.1, entry.2, entry.3)));
72        Ok(ranked
73            .into_iter()
74            .map(|(_, _, _, _, lens_id, reason)| LensStackEntry { lens_id, reason })
75            .collect())
76    }
77}
78
79fn class_matches(meta: &crate::contract::LensMeta, ctx: &DispatchContext) -> bool {
80    match &ctx.value_class {
81        Some(class) => meta.claimed_classes.contains(class),
82        None => false,
83    }
84}