1use std::any::{Any, TypeId};
9
10use slotmap::new_key_type;
11
12use crate::{cell::CellId, relationship::RelationshipId};
13
14new_key_type! {
15 pub struct ConditionalId;
17}
18
19type MatchExprFn = Box<dyn Fn(&[&dyn Any]) -> Result<Box<dyn Any>, anyhow::Error>>;
24
25pub struct MatchExpr(pub(crate) MatchSource);
37
38pub(crate) enum MatchSource {
39 Cell(CellId),
42 Expr(MatchExprData),
44}
45
46pub(crate) struct MatchExprData {
47 pub(crate) inputs: Vec<CellId>,
48 pub(crate) input_types: Vec<TypeId>,
49 pub(crate) output_type: TypeId,
50 pub(crate) eq_fn: fn(&dyn Any, &dyn Any) -> bool,
51 pub(crate) function: MatchExprFn,
52}
53
54impl MatchExpr {
55 #[must_use]
61 pub fn cell(cell: CellId) -> Self {
62 MatchExpr(MatchSource::Cell(cell))
63 }
64
65 #[must_use]
73 pub fn new<F>(
74 inputs: Vec<CellId>,
75 input_types: Vec<TypeId>,
76 output_type: TypeId,
77 eq_fn: fn(&dyn Any, &dyn Any) -> bool,
78 f: F,
79 ) -> Self
80 where
81 F: Fn(&[&dyn Any]) -> Result<Box<dyn Any>, anyhow::Error> + 'static,
82 {
83 debug_assert_eq!(inputs.len(), input_types.len());
84 MatchExpr(MatchSource::Expr(MatchExprData {
85 inputs,
86 input_types,
87 output_type,
88 eq_fn,
89 function: Box::new(f),
90 }))
91 }
92
93 #[must_use]
101 pub fn from_fn_1<A, T, F>(input: CellId, f: F) -> Self
102 where
103 A: Any + 'static,
104 T: Any + PartialEq + 'static,
105 F: Fn(&A) -> Result<T, anyhow::Error> + 'static,
106 {
107 MatchExpr::new(
108 vec![input],
109 vec![TypeId::of::<A>()],
110 TypeId::of::<T>(),
111 |a, b| a.downcast_ref::<T>() == b.downcast_ref::<T>(),
112 move |args| {
113 let a = args[0]
114 .downcast_ref::<A>()
115 .expect("type checked at add_conditional");
116 Ok(Box::new(f(a)?) as Box<dyn Any>)
117 },
118 )
119 }
120
121 #[must_use]
129 pub fn from_fn_2<A, B, T, F>(inputs: [CellId; 2], f: F) -> Self
130 where
131 A: Any + 'static,
132 B: Any + 'static,
133 T: Any + PartialEq + 'static,
134 F: Fn(&A, &B) -> Result<T, anyhow::Error> + 'static,
135 {
136 MatchExpr::new(
137 inputs.to_vec(),
138 vec![TypeId::of::<A>(), TypeId::of::<B>()],
139 TypeId::of::<T>(),
140 |a, b| a.downcast_ref::<T>() == b.downcast_ref::<T>(),
141 move |args| {
142 let a = args[0]
143 .downcast_ref::<A>()
144 .expect("type checked at add_conditional");
145 let b = args[1]
146 .downcast_ref::<B>()
147 .expect("type checked at add_conditional");
148 Ok(Box::new(f(a, b)?) as Box<dyn Any>)
149 },
150 )
151 }
152}
153
154pub(crate) struct Branch {
157 pub(crate) keys: Vec<Box<dyn Any>>,
159 pub(crate) relationships: Vec<RelationshipId>,
161}
162
163pub(crate) struct ConditionalData {
165 pub(crate) source: MatchSource,
167 pub(crate) branches: Vec<Branch>,
169 pub(crate) default: Vec<RelationshipId>,
171}
172
173impl ConditionalData {
174 pub(crate) fn match_cells(&self) -> &[CellId] {
177 match &self.source {
178 MatchSource::Cell(id) => std::slice::from_ref(id),
179 MatchSource::Expr(expr) => &expr.inputs,
180 }
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn conditional_id_is_copy() {
190 fn takes_copy<T: Copy>(_: T) {}
191 takes_copy(ConditionalId::default());
192 }
193
194 #[test]
195 fn match_expr_cell_wraps_a_single_cell() {
196 use slotmap::SlotMap;
197 let mut map: SlotMap<CellId, ()> = SlotMap::with_key();
198 let cell = map.insert(());
199 let expr = MatchExpr::cell(cell);
200 match expr.0 {
201 MatchSource::Cell(id) => assert_eq!(id, cell),
202 MatchSource::Expr(_) => panic!("expected Cell variant"),
203 }
204 }
205
206 #[test]
207 fn match_expr_from_fn_1_stores_correct_type_ids_and_computes_value() {
208 use slotmap::SlotMap;
209 let mut map: SlotMap<CellId, ()> = SlotMap::with_key();
210 let a = map.insert(());
211
212 let expr = MatchExpr::from_fn_1(a, |x: &i32| Ok(*x * 2));
213 match expr.0 {
214 MatchSource::Expr(data) => {
215 assert_eq!(data.inputs, vec![a]);
216 assert_eq!(data.input_types, vec![TypeId::of::<i32>()]);
217 assert_eq!(data.output_type, TypeId::of::<i32>());
218 let x: i32 = 5;
219 let result = (data.function)(&[&x]).unwrap();
220 assert_eq!(*result.downcast_ref::<i32>().unwrap(), 10);
221 let y: i32 = 10;
222 assert!((data.eq_fn)(&y, &10_i32));
223 assert!(!(data.eq_fn)(&y, &11_i32));
224 }
225 MatchSource::Cell(_) => panic!("expected Expr variant"),
226 }
227 }
228
229 #[test]
230 fn match_expr_from_fn_2_stores_correct_type_ids_and_computes_value() {
231 use slotmap::SlotMap;
232 let mut map: SlotMap<CellId, ()> = SlotMap::with_key();
233 let a = map.insert(());
234 let b = map.insert(());
235
236 let expr = MatchExpr::from_fn_2([a, b], |x: &bool, y: &bool| Ok(*x && *y));
237 match expr.0 {
238 MatchSource::Expr(data) => {
239 assert_eq!(data.inputs, vec![a, b]);
240 assert_eq!(
241 data.input_types,
242 vec![TypeId::of::<bool>(), TypeId::of::<bool>()]
243 );
244 assert_eq!(data.output_type, TypeId::of::<bool>());
245 let x = true;
246 let y = false;
247 let result = (data.function)(&[&x, &y]).unwrap();
248 assert!(!*result.downcast_ref::<bool>().unwrap());
249 }
250 MatchSource::Cell(_) => panic!("expected Expr variant"),
251 }
252 }
253
254 #[test]
255 fn match_cells_returns_single_cell_for_cell_variant() {
256 use slotmap::SlotMap;
257 let mut map: SlotMap<CellId, ()> = SlotMap::with_key();
258 let cell = map.insert(());
259 let data = ConditionalData {
260 source: MatchSource::Cell(cell),
261 branches: Vec::new(),
262 default: Vec::new(),
263 };
264 assert_eq!(data.match_cells(), &[cell]);
265 }
266
267 #[test]
268 fn match_cells_returns_all_inputs_for_expr_variant() {
269 use slotmap::SlotMap;
270 let mut map: SlotMap<CellId, ()> = SlotMap::with_key();
271 let a = map.insert(());
272 let b = map.insert(());
273 let expr = MatchExpr::from_fn_2([a, b], |x: &i32, y: &i32| Ok(x + y));
274 let MatchSource::Expr(data) = expr.0 else {
275 panic!("expected Expr variant")
276 };
277 let cond = ConditionalData {
278 source: MatchSource::Expr(data),
279 branches: Vec::new(),
280 default: Vec::new(),
281 };
282 assert_eq!(cond.match_cells(), &[a, b]);
283 }
284
285 #[test]
286 fn match_expr_new_reports_the_error_a_failing_function_returns() {
287 use slotmap::SlotMap;
288 let mut map: SlotMap<CellId, ()> = SlotMap::with_key();
289 let a = map.insert(());
290
291 let expr = MatchExpr::new(
292 vec![a],
293 vec![TypeId::of::<i32>()],
294 TypeId::of::<i32>(),
295 |x, y| x.downcast_ref::<i32>() == y.downcast_ref::<i32>(),
296 |_args| Err(anyhow::anyhow!("boom")),
297 );
298 let MatchSource::Expr(data) = expr.0 else {
299 panic!("expected Expr variant")
300 };
301 let x: i32 = 1;
302 let err = (data.function)(&[&x]).unwrap_err();
303 assert_eq!(err.to_string(), "boom");
304 }
305}