1use sim_kernel::{CapabilityName, Cx, Error, Expr, Result, Symbol};
18
19use crate::contract::{Lens, LensKind};
20
21pub struct DispatchContext<'a> {
24 pub explicit: Option<Symbol>,
26 pub preference: Option<Symbol>,
28 pub active_mode: Option<Symbol>,
30 pub value_class: Option<Symbol>,
32 pub granted: &'a dyn Fn(&CapabilityName) -> bool,
34}
35
36impl<'a> DispatchContext<'a> {
37 pub fn permissive(grant_all: &'a dyn Fn(&CapabilityName) -> bool) -> Self {
39 Self {
40 explicit: None,
41 preference: None,
42 active_mode: None,
43 value_class: None,
44 granted: grant_all,
45 }
46 }
47}
48
49#[derive(Clone, Debug, PartialEq, Eq)]
51pub enum DispatchReason {
52 Explicit,
54 Preference,
56 ShapeMatch(i32),
58 ClassMatch,
60 UniversalDefault,
62}
63
64#[derive(Clone, Debug, PartialEq, Eq)]
66pub struct DispatchOutcome {
67 pub lens_id: Symbol,
69 pub reason: DispatchReason,
71}
72
73#[derive(Default)]
75pub struct LensRegistry {
76 lenses: Vec<Lens>,
77}
78
79impl LensRegistry {
80 pub fn new() -> Self {
82 Self::default()
83 }
84
85 pub fn register(&mut self, lens: Lens) {
87 self.lenses.push(lens);
88 }
89
90 pub fn get(&self, id: &Symbol) -> Option<&Lens> {
92 self.lenses.iter().find(|lens| &lens.meta.id == id)
93 }
94
95 pub fn lenses(&self) -> &[Lens] {
97 &self.lenses
98 }
99
100 pub fn dispatch_view(
102 &self,
103 cx: &mut Cx,
104 target: &Expr,
105 ctx: &DispatchContext,
106 ) -> Result<DispatchOutcome> {
107 self.dispatch(cx, LensKind::View, target, ctx)
108 }
109
110 pub fn dispatch_editor(
112 &self,
113 cx: &mut Cx,
114 target: &Expr,
115 ctx: &DispatchContext,
116 ) -> Result<DispatchOutcome> {
117 self.dispatch(cx, LensKind::Editor, target, ctx)
118 }
119
120 pub fn dispatch(
122 &self,
123 cx: &mut Cx,
124 kind: LensKind,
125 target: &Expr,
126 ctx: &DispatchContext,
127 ) -> Result<DispatchOutcome> {
128 if let Some(outcome) = self.pick_named(&ctx.explicit, kind, ctx, DispatchReason::Explicit) {
130 return Ok(outcome);
131 }
132 if let Some(outcome) =
134 self.pick_named(&ctx.preference, kind, ctx, DispatchReason::Preference)
135 {
136 return Ok(outcome);
137 }
138 if let Some(outcome) = self.pick_shape_match(cx, kind, target, ctx)? {
140 return Ok(outcome);
141 }
142 if let Some(outcome) = self.pick_class_match(kind, ctx) {
144 return Ok(outcome);
145 }
146 if let Some(lens) = self.lenses.iter().find(|lens| {
148 lens.meta.kind == kind && lens.meta.universal_default && self.allowed(lens, ctx)
149 }) {
150 return Ok(DispatchOutcome {
151 lens_id: lens.meta.id.clone(),
152 reason: DispatchReason::UniversalDefault,
153 });
154 }
155 Err(Error::HostError(format!(
156 "no {kind:?} lens available for the value (not even a universal default)"
157 )))
158 }
159
160 pub(crate) fn allowed(&self, lens: &Lens, ctx: &DispatchContext) -> bool {
161 lens.meta
162 .required_capabilities
163 .iter()
164 .all(|capability| (ctx.granted)(capability))
165 }
166
167 fn pick_named(
168 &self,
169 id: &Option<Symbol>,
170 kind: LensKind,
171 ctx: &DispatchContext,
172 reason: DispatchReason,
173 ) -> Option<DispatchOutcome> {
174 let id = id.as_ref()?;
175 let lens = self.get(id)?;
176 if lens.meta.kind == kind && self.allowed(lens, ctx) {
177 Some(DispatchOutcome {
178 lens_id: id.clone(),
179 reason,
180 })
181 } else {
182 None
183 }
184 }
185
186 fn pick_shape_match(
187 &self,
188 cx: &mut Cx,
189 kind: LensKind,
190 target: &Expr,
191 ctx: &DispatchContext,
192 ) -> Result<Option<DispatchOutcome>> {
193 let mut best: Option<(i32, i32, i32, Symbol)> = None;
194 for lens in &self.lenses {
195 if lens.meta.kind != kind || lens.meta.universal_default || !self.allowed(lens, ctx) {
196 continue;
197 }
198 let Some(score) = best_shape_score(cx, lens, target)? else {
199 continue;
200 };
201 let candidate = (score, lens.meta.quality, -lens.meta.cost);
203 let better = match &best {
204 Some((bs, bq, bc, _)) => candidate > (*bs, *bq, *bc),
205 None => true,
206 };
207 if better {
208 best = Some((candidate.0, candidate.1, candidate.2, lens.meta.id.clone()));
209 }
210 }
211 Ok(best.map(|(score, _, _, lens_id)| DispatchOutcome {
212 lens_id,
213 reason: DispatchReason::ShapeMatch(score),
214 }))
215 }
216
217 fn pick_class_match(&self, kind: LensKind, ctx: &DispatchContext) -> Option<DispatchOutcome> {
218 let class = ctx.value_class.as_ref()?;
219 let mut best: Option<(i32, i32, Symbol)> = None;
220 for lens in &self.lenses {
221 if lens.meta.kind != kind
222 || lens.meta.universal_default
223 || !self.allowed(lens, ctx)
224 || !lens.meta.claimed_classes.contains(class)
225 {
226 continue;
227 }
228 let candidate = (lens.meta.quality, -lens.meta.cost);
229 let better = match &best {
230 Some((bq, bc, _)) => candidate > (*bq, *bc),
231 None => true,
232 };
233 if better {
234 best = Some((candidate.0, candidate.1, lens.meta.id.clone()));
235 }
236 }
237 best.map(|(_, _, lens_id)| DispatchOutcome {
238 lens_id,
239 reason: DispatchReason::ClassMatch,
240 })
241 }
242}
243
244pub(crate) fn best_shape_score(cx: &mut Cx, lens: &Lens, target: &Expr) -> Result<Option<i32>> {
246 let mut best: Option<i32> = None;
247 for shape_value in &lens.meta.claimed_shapes {
248 let Some(shape) = shape_value.object().as_shape() else {
249 continue;
250 };
251 let matched = shape.check_expr(cx, target)?;
252 if matched.accepted {
253 let score = matched.score.value();
254 best = Some(best.map_or(score, |current| current.max(score)));
255 }
256 }
257 Ok(best)
258}