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