Skip to main content

tidepool_effect/
machine.rs

1use crate::dispatch::{DispatchEffect, EffectContext};
2use crate::error::EffectError;
3use tidepool_eval::heap::Heap;
4use tidepool_eval::value::Value;
5use tidepool_repr::{CoreExpr, DataConId, DataConTable};
6
7pub struct EffectMachine<'a> {
8    table: &'a DataConTable,
9    heap: &'a mut dyn Heap,
10    val_id: DataConId,
11    e_id: DataConId,
12    leaf_id: DataConId,
13    node_id: DataConId,
14    union_id: DataConId,
15}
16
17impl<'a> EffectMachine<'a> {
18    pub fn new(table: &'a DataConTable, heap: &'a mut dyn Heap) -> Result<Self, EffectError> {
19        let val_id = table
20            .get_by_name("Val")
21            .ok_or(EffectError::MissingConstructor { name: "Val" })?;
22        let e_id = table
23            .get_by_name("E")
24            .ok_or(EffectError::MissingConstructor { name: "E" })?;
25        let leaf_id = table
26            .get_by_name("Leaf")
27            .ok_or(EffectError::MissingConstructor { name: "Leaf" })?;
28        let node_id = table
29            .get_by_name("Node")
30            .ok_or(EffectError::MissingConstructor { name: "Node" })?;
31        let union_id = table
32            .get_by_name("Union")
33            .ok_or(EffectError::MissingConstructor { name: "Union" })?;
34        Ok(Self {
35            table,
36            heap,
37            val_id,
38            e_id,
39            leaf_id,
40            node_id,
41            union_id,
42        })
43    }
44
45    /// Run an Eff expression to completion with the given handler HList.
46    /// Backward-compatible: uses U=() (no user data).
47    pub fn run<H: DispatchEffect>(
48        &mut self,
49        expr: &CoreExpr,
50        handlers: &mut H,
51    ) -> Result<Value, EffectError> {
52        self.run_with_user(expr, handlers, &())
53    }
54
55    /// Run an Eff expression with user data threaded through to handlers.
56    pub fn run_with_user<U, H: DispatchEffect<U>>(
57        &mut self,
58        expr: &CoreExpr,
59        handlers: &mut H,
60        user: &U,
61    ) -> Result<Value, EffectError> {
62        let env = tidepool_eval::eval::env_from_datacon_table(self.table);
63        let mut current = tidepool_eval::eval::eval(expr, &env, self.heap)?;
64
65        loop {
66            let forced = tidepool_eval::eval::force(current, self.heap)?;
67            match forced {
68                Value::Con(id, ref fields) if id == self.val_id => {
69                    // Val x — pure result, done. Deep force to eliminate any ThunkRefs.
70                    let result = fields
71                        .first()
72                        .cloned()
73                        .unwrap_or(Value::Lit(tidepool_repr::Literal::LitInt(0)));
74                    return Ok(tidepool_eval::eval::deep_force(result, self.heap)?);
75                }
76                Value::Con(id, ref fields) if id == self.e_id => {
77                    // E (Union tag# req) k
78                    if fields.len() != 2 {
79                        return Err(EffectError::FieldCountMismatch {
80                            constructor: "E",
81                            expected: 2,
82                            got: fields.len(),
83                        });
84                    }
85                    let union_val = tidepool_eval::eval::deep_force(fields[0].clone(), self.heap)?;
86                    let k = tidepool_eval::eval::force(fields[1].clone(), self.heap)?;
87
88                    // Destructure Union(tag, req)
89                    let (tag, request) = match union_val {
90                        Value::Con(uid, ref ufields) if uid == self.union_id => {
91                            if ufields.len() != 2 {
92                                return Err(EffectError::FieldCountMismatch {
93                                    constructor: "Union",
94                                    expected: 2,
95                                    got: ufields.len(),
96                                });
97                            }
98                            let tag = match &ufields[0] {
99                                Value::Lit(tidepool_repr::Literal::LitWord(w)) => *w,
100                                Value::Lit(tidepool_repr::Literal::LitInt(i)) => *i as u64,
101                                other => {
102                                    return Err(EffectError::UnexpectedValue {
103                                        context: "Union tag (Word#/Int#)",
104                                        got: format!("{:?}", other),
105                                    })
106                                }
107                            };
108                            // deep_force the request so FromCore never sees ThunkRef
109                            let req = ufields[1].clone();
110                            (tag, req)
111                        }
112                        other => {
113                            return Err(EffectError::UnexpectedValue {
114                                context: "Union constructor",
115                                got: format!("{:?}", other),
116                            })
117                        }
118                    };
119
120                    // Dispatch to handler
121                    let cx = EffectContext::with_user(self.table, user);
122                    let response = handlers.dispatch(tag, &request, &cx)?;
123
124                    // Apply continuation
125                    current = self.apply_cont(k, response)?;
126                }
127                other => {
128                    return Err(EffectError::UnexpectedValue {
129                        context: "Val or E constructor",
130                        got: format!("{:?}", other),
131                    });
132                }
133            }
134        }
135    }
136
137    /// Apply a Leaf/Node continuation tree to a value.
138    fn apply_cont(&mut self, k: Value, arg: Value) -> Result<Value, EffectError> {
139        let k = tidepool_eval::eval::force(k, self.heap)?;
140        match k {
141            Value::Con(id, ref fields) if id == self.leaf_id => {
142                // Leaf(f) — apply f to arg
143                if fields.len() != 1 {
144                    return Err(EffectError::FieldCountMismatch {
145                        constructor: "Leaf",
146                        expected: 1,
147                        got: fields.len(),
148                    });
149                }
150                let f = tidepool_eval::eval::force(fields[0].clone(), self.heap)?;
151                Ok(self.apply_closure(f, arg)?)
152            }
153            Value::Con(id, ref fields) if id == self.node_id => {
154                // Node(k1, k2) — apply k1, then compose with k2
155                if fields.len() != 2 {
156                    return Err(EffectError::FieldCountMismatch {
157                        constructor: "Node",
158                        expected: 2,
159                        got: fields.len(),
160                    });
161                }
162                let k1 = fields[0].clone();
163                let k2 = fields[1].clone();
164                let result = self.apply_cont(k1, arg)?;
165                let forced = tidepool_eval::eval::force(result, self.heap)?;
166
167                match forced {
168                    Value::Con(vid, ref vfields) if vid == self.val_id => {
169                        // k1 returned Val(y) — feed y to k2
170                        let y = vfields
171                            .first()
172                            .cloned()
173                            .unwrap_or(Value::Lit(tidepool_repr::Literal::LitInt(0)));
174                        self.apply_cont(k2, y)
175                    }
176                    Value::Con(eid, ref efields) if eid == self.e_id => {
177                        // k1 yielded E(union, k') — compose: E(union, Node(k', k2))
178                        if efields.len() != 2 {
179                            return Err(EffectError::FieldCountMismatch {
180                                constructor: "E (continuation)",
181                                expected: 2,
182                                got: efields.len(),
183                            });
184                        }
185                        let union_val = efields[0].clone();
186                        let k_prime = efields[1].clone();
187                        let new_k = Value::Con(self.node_id, vec![k_prime, k2]);
188                        Ok(Value::Con(self.e_id, vec![union_val, new_k]))
189                    }
190                    other => Err(EffectError::UnexpectedValue {
191                        context: "Val or E after applying k1",
192                        got: format!("{:?}", other),
193                    }),
194                }
195            }
196            Value::Closure(..) => {
197                // Raw closure (degenerate continuation)
198                Ok(self.apply_closure(k, arg)?)
199            }
200            other => Err(EffectError::UnexpectedValue {
201                context: "Leaf or Node continuation",
202                got: format!("{:?}", other),
203            }),
204        }
205    }
206
207    /// Apply a single closure to a value.
208    fn apply_closure(&mut self, closure: Value, arg: Value) -> Result<Value, EffectError> {
209        match closure {
210            Value::Closure(env, binder, body) => {
211                let new_env = env.update(binder, arg);
212                Ok(tidepool_eval::eval::eval(&body, &new_env, self.heap)?)
213            }
214            Value::ConFun(tag, arity, mut args) => {
215                args.push(arg);
216                if args.len() == arity {
217                    Ok(Value::Con(tag, args))
218                } else {
219                    Ok(Value::ConFun(tag, arity, args))
220                }
221            }
222            other => Err(EffectError::UnexpectedValue {
223                context: "closure",
224                got: format!("{:?}", other),
225            }),
226        }
227    }
228}
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233    use tidepool_eval::heap::VecHeap;
234    use tidepool_repr::datacon::DataCon;
235    use tidepool_repr::datacon_table::DataConTable;
236    use tidepool_repr::types::{DataConId, Literal, VarId};
237    use tidepool_repr::{CoreExpr, CoreFrame, RecursiveTree};
238
239    fn make_test_table() -> DataConTable {
240        let mut table = DataConTable::new();
241        table.insert(DataCon {
242            id: DataConId(1),
243            name: "Val".to_string(),
244            tag: 1,
245            rep_arity: 1,
246            field_bangs: vec![],
247            qualified_name: None,
248        });
249        table.insert(DataCon {
250            id: DataConId(2),
251            name: "E".to_string(),
252            tag: 2,
253            rep_arity: 2,
254            field_bangs: vec![],
255            qualified_name: None,
256        });
257        table.insert(DataCon {
258            id: DataConId(3),
259            name: "Leaf".to_string(),
260            tag: 1,
261            rep_arity: 1,
262            field_bangs: vec![],
263            qualified_name: None,
264        });
265        table.insert(DataCon {
266            id: DataConId(4),
267            name: "Node".to_string(),
268            tag: 2,
269            rep_arity: 2,
270            field_bangs: vec![],
271            qualified_name: None,
272        });
273        table.insert(DataCon {
274            id: DataConId(5),
275            name: "Union".to_string(),
276            tag: 1,
277            rep_arity: 2,
278            field_bangs: vec![],
279            qualified_name: None,
280        });
281        table
282    }
283
284    #[test]
285    fn test_effect_machine_pure_val() {
286        // Eff expression that is just Val(42)
287        let table = make_test_table();
288        let mut heap = VecHeap::new();
289
290        // Build: Con(Val, [Lit(42)])
291        let expr: CoreExpr = RecursiveTree {
292            nodes: vec![
293                CoreFrame::Lit(Literal::LitInt(42)),
294                CoreFrame::Con {
295                    tag: DataConId(1), // Val
296                    fields: vec![0],
297                },
298            ],
299        };
300
301        let mut handlers = frunk::HNil;
302        let mut machine = EffectMachine::new(&table, &mut heap).unwrap();
303        let result = machine.run(&expr, &mut handlers).unwrap();
304
305        match result {
306            Value::Lit(Literal::LitInt(n)) => assert_eq!(n, 42),
307            other => panic!("Expected Lit(42), got {:?}", other),
308        }
309    }
310
311    #[test]
312    fn test_effect_machine_single_effect() {
313        // Build: E(Union(0, Lit(99)), Leaf(\x -> Val(x)))
314        // Handler at tag 0 receives Lit(99), returns Lit(100)
315        let table = make_test_table();
316        let mut heap = VecHeap::new();
317
318        let expr: CoreExpr = RecursiveTree {
319            nodes: vec![
320                // 0: Var(x) -- will be the Val payload
321                CoreFrame::Var(VarId(100)),
322                // 1: Con(Val, [Var(x)]) -- Val(x)
323                CoreFrame::Con {
324                    tag: DataConId(1), // Val
325                    fields: vec![0],
326                },
327                // 2: Lam(x, Con(Val, [Var(x)])) -- \x -> Val(x)
328                CoreFrame::Lam {
329                    binder: VarId(100),
330                    body: 1,
331                },
332                // 3: Con(Leaf, [lam]) -- Leaf(\x -> Val(x))
333                CoreFrame::Con {
334                    tag: DataConId(3), // Leaf
335                    fields: vec![2],
336                },
337                // 4: Lit(99) -- the request
338                CoreFrame::Lit(Literal::LitInt(99)),
339                // 5: Lit(0) -- tag Word# 0
340                CoreFrame::Lit(Literal::LitWord(0)),
341                // 6: Con(Union, [tag, req]) -- Union(0, 99)
342                CoreFrame::Con {
343                    tag: DataConId(5), // Union
344                    fields: vec![5, 4],
345                },
346                // 7: Con(E, [union, k]) -- E(Union(0, 99), Leaf(\x -> Val(x)))
347                CoreFrame::Con {
348                    tag: DataConId(2), // E
349                    fields: vec![6, 3],
350                },
351            ],
352        };
353
354        // Simple handler: receives any value, returns Lit(100)
355        use crate::dispatch::{EffectContext, EffectHandler};
356        use tidepool_bridge::FromCore;
357
358        struct TestReq(i64);
359        impl FromCore for TestReq {
360            fn from_value(
361                value: &Value,
362                _table: &DataConTable,
363            ) -> Result<Self, tidepool_bridge::BridgeError> {
364                match value {
365                    Value::Lit(Literal::LitInt(n)) => Ok(TestReq(*n)),
366                    _ => Err(tidepool_bridge::BridgeError::TypeMismatch {
367                        expected: "LitInt".into(),
368                        got: format!("{:?}", value),
369                    }),
370                }
371            }
372        }
373
374        struct TestHandler;
375        impl EffectHandler for TestHandler {
376            type Request = TestReq;
377            fn handle(&mut self, req: TestReq, _cx: &EffectContext) -> Result<Value, EffectError> {
378                // Echo back the request + 1
379                Ok(Value::Lit(Literal::LitInt(req.0 + 1)))
380            }
381        }
382
383        let mut handlers = frunk::hlist![TestHandler];
384        let mut machine = EffectMachine::new(&table, &mut heap).unwrap();
385        let result = machine.run(&expr, &mut handlers).unwrap();
386
387        match result {
388            Value::Lit(Literal::LitInt(n)) => assert_eq!(n, 100),
389            other => panic!("Expected Lit(100), got {:?}", other),
390        }
391    }
392
393    #[test]
394    fn test_run_with_user_data() {
395        // Same as single_effect but handler reads user data to compute response
396        let table = make_test_table();
397        let mut heap = VecHeap::new();
398
399        let expr: CoreExpr = RecursiveTree {
400            nodes: vec![
401                CoreFrame::Var(VarId(100)),
402                CoreFrame::Con {
403                    tag: DataConId(1),
404                    fields: vec![0],
405                },
406                CoreFrame::Lam {
407                    binder: VarId(100),
408                    body: 1,
409                },
410                CoreFrame::Con {
411                    tag: DataConId(3),
412                    fields: vec![2],
413                },
414                CoreFrame::Lit(Literal::LitInt(10)),
415                CoreFrame::Lit(Literal::LitWord(0)),
416                CoreFrame::Con {
417                    tag: DataConId(5),
418                    fields: vec![5, 4],
419                },
420                CoreFrame::Con {
421                    tag: DataConId(2),
422                    fields: vec![6, 3],
423                },
424            ],
425        };
426
427        use crate::dispatch::{EffectContext, EffectHandler};
428        use tidepool_bridge::FromCore;
429
430        struct TestReq(i64);
431        impl FromCore for TestReq {
432            fn from_value(
433                value: &Value,
434                _table: &DataConTable,
435            ) -> Result<Self, tidepool_bridge::BridgeError> {
436                match value {
437                    Value::Lit(Literal::LitInt(n)) => Ok(TestReq(*n)),
438                    _ => Err(tidepool_bridge::BridgeError::TypeMismatch {
439                        expected: "LitInt".into(),
440                        got: format!("{:?}", value),
441                    }),
442                }
443            }
444        }
445
446        struct UserData {
447            multiplier: i64,
448        }
449
450        struct UserHandler;
451        impl EffectHandler<UserData> for UserHandler {
452            type Request = TestReq;
453            fn handle(
454                &mut self,
455                req: TestReq,
456                cx: &EffectContext<'_, UserData>,
457            ) -> Result<Value, EffectError> {
458                Ok(Value::Lit(Literal::LitInt(req.0 * cx.user().multiplier)))
459            }
460        }
461
462        let user = UserData { multiplier: 5 };
463        let mut handlers = frunk::hlist![UserHandler];
464        let mut machine = EffectMachine::new(&table, &mut heap).unwrap();
465        let result = machine.run_with_user(&expr, &mut handlers, &user).unwrap();
466
467        match result {
468            Value::Lit(Literal::LitInt(n)) => assert_eq!(n, 50), // 10 * 5
469            other => panic!("Expected Lit(50), got {:?}", other),
470        }
471    }
472}