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_else(|| EffectError::MissingConstructor { name: "Val" })?;
22 let e_id = table
23 .get_by_name("E")
24 .ok_or_else(|| EffectError::MissingConstructor { name: "E" })?;
25 let leaf_id = table
26 .get_by_name("Leaf")
27 .ok_or_else(|| EffectError::MissingConstructor { name: "Leaf" })?;
28 let node_id = table
29 .get_by_name("Node")
30 .ok_or_else(|| EffectError::MissingConstructor { name: "Node" })?;
31 let union_id = table
32 .get_by_name("Union")
33 .ok_or_else(|| 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 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 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 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 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 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 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 let cx = EffectContext::with_user(self.table, user);
122 let response = handlers.dispatch(tag, &request, &cx)?;
123
124 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 #[cfg(feature = "async")]
140 pub async fn run_async<U: Sync, H: crate::dispatch::AsyncDispatchEffect<U>>(
141 &mut self,
142 expr: &CoreExpr,
143 handlers: &mut H,
144 user: &U,
145 ) -> Result<Value, EffectError> {
146 let env = tidepool_eval::eval::env_from_datacon_table(self.table);
147 let mut current = tidepool_eval::eval::eval(expr, &env, self.heap)?;
148
149 loop {
150 let forced = tidepool_eval::eval::force(current, self.heap)?;
151 match forced {
152 Value::Con(id, ref fields) if id == self.val_id => {
153 let result = fields
154 .first()
155 .cloned()
156 .unwrap_or(Value::Lit(tidepool_repr::Literal::LitInt(0)));
157 return Ok(tidepool_eval::eval::deep_force(result, self.heap)?);
158 }
159 Value::Con(id, ref fields) if id == self.e_id => {
160 if fields.len() != 2 {
161 return Err(EffectError::FieldCountMismatch {
162 constructor: "E",
163 expected: 2,
164 got: fields.len(),
165 });
166 }
167 let union_val = tidepool_eval::eval::deep_force(fields[0].clone(), self.heap)?;
168 let k = tidepool_eval::eval::force(fields[1].clone(), self.heap)?;
169
170 let (tag, request) = match union_val {
171 Value::Con(uid, ref ufields) if uid == self.union_id => {
172 if ufields.len() != 2 {
173 return Err(EffectError::FieldCountMismatch {
174 constructor: "Union",
175 expected: 2,
176 got: ufields.len(),
177 });
178 }
179 let tag = match &ufields[0] {
180 Value::Lit(tidepool_repr::Literal::LitWord(w)) => *w,
181 Value::Lit(tidepool_repr::Literal::LitInt(i)) => *i as u64,
182 other => {
183 return Err(EffectError::UnexpectedValue {
184 context: "Union tag (Word#/Int#)",
185 got: format!("{:?}", other),
186 })
187 }
188 };
189 let req = ufields[1].clone();
190 (tag, req)
191 }
192 other => {
193 return Err(EffectError::UnexpectedValue {
194 context: "Union constructor",
195 got: format!("{:?}", other),
196 })
197 }
198 };
199
200 let cx = EffectContext::with_user(self.table, user);
201 let response = handlers.dispatch(tag, &request, &cx).await?;
202
203 current = self.apply_cont(k, response)?;
204 }
205 other => {
206 return Err(EffectError::UnexpectedValue {
207 context: "Val or E constructor",
208 got: format!("{:?}", other),
209 });
210 }
211 }
212 }
213 }
214
215 fn apply_cont(&mut self, k: Value, arg: Value) -> Result<Value, EffectError> {
217 let k = tidepool_eval::eval::force(k, self.heap)?;
218 match k {
219 Value::Con(id, ref fields) if id == self.leaf_id => {
220 if fields.len() != 1 {
222 return Err(EffectError::FieldCountMismatch {
223 constructor: "Leaf",
224 expected: 1,
225 got: fields.len(),
226 });
227 }
228 let f = tidepool_eval::eval::force(fields[0].clone(), self.heap)?;
229 Ok(self.apply_closure(f, arg)?)
230 }
231 Value::Con(id, ref fields) if id == self.node_id => {
232 if fields.len() != 2 {
234 return Err(EffectError::FieldCountMismatch {
235 constructor: "Node",
236 expected: 2,
237 got: fields.len(),
238 });
239 }
240 let k1 = fields[0].clone();
241 let k2 = fields[1].clone();
242 let result = self.apply_cont(k1, arg)?;
243 let forced = tidepool_eval::eval::force(result, self.heap)?;
244
245 match forced {
246 Value::Con(vid, ref vfields) if vid == self.val_id => {
247 let y = vfields
249 .first()
250 .cloned()
251 .unwrap_or(Value::Lit(tidepool_repr::Literal::LitInt(0)));
252 self.apply_cont(k2, y)
253 }
254 Value::Con(eid, ref efields) if eid == self.e_id => {
255 if efields.len() != 2 {
257 return Err(EffectError::FieldCountMismatch {
258 constructor: "E (continuation)",
259 expected: 2,
260 got: efields.len(),
261 });
262 }
263 let union_val = efields[0].clone();
264 let k_prime = efields[1].clone();
265 let new_k = Value::Con(self.node_id, vec![k_prime, k2]);
266 Ok(Value::Con(self.e_id, vec![union_val, new_k]))
267 }
268 other => Err(EffectError::UnexpectedValue {
269 context: "Val or E after applying k1",
270 got: format!("{:?}", other),
271 }),
272 }
273 }
274 Value::Closure(..) => {
275 Ok(self.apply_closure(k, arg)?)
277 }
278 other => Err(EffectError::UnexpectedValue {
279 context: "Leaf or Node continuation",
280 got: format!("{:?}", other),
281 }),
282 }
283 }
284
285 fn apply_closure(&mut self, closure: Value, arg: Value) -> Result<Value, EffectError> {
287 match closure {
288 Value::Closure(env, binder, body) => {
289 let new_env = env.update(binder, arg);
290 Ok(tidepool_eval::eval::eval(&body, &new_env, self.heap)?)
291 }
292 Value::ConFun(tag, arity, mut args) => {
293 args.push(arg);
294 if args.len() == arity {
295 Ok(Value::Con(tag, args))
296 } else {
297 Ok(Value::ConFun(tag, arity, args))
298 }
299 }
300 other => Err(EffectError::UnexpectedValue {
301 context: "closure",
302 got: format!("{:?}", other),
303 }),
304 }
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311 use tidepool_eval::heap::VecHeap;
312 use tidepool_repr::datacon::DataCon;
313 use tidepool_repr::datacon_table::DataConTable;
314 use tidepool_repr::types::{DataConId, Literal, VarId};
315 use tidepool_repr::{CoreExpr, CoreFrame, RecursiveTree};
316
317 fn make_test_table() -> DataConTable {
318 let mut table = DataConTable::new();
319 table.insert(DataCon {
320 id: DataConId(1),
321 name: "Val".to_string(),
322 tag: 1,
323 rep_arity: 1,
324 field_bangs: vec![],
325 });
326 table.insert(DataCon {
327 id: DataConId(2),
328 name: "E".to_string(),
329 tag: 2,
330 rep_arity: 2,
331 field_bangs: vec![],
332 });
333 table.insert(DataCon {
334 id: DataConId(3),
335 name: "Leaf".to_string(),
336 tag: 1,
337 rep_arity: 1,
338 field_bangs: vec![],
339 });
340 table.insert(DataCon {
341 id: DataConId(4),
342 name: "Node".to_string(),
343 tag: 2,
344 rep_arity: 2,
345 field_bangs: vec![],
346 });
347 table.insert(DataCon {
348 id: DataConId(5),
349 name: "Union".to_string(),
350 tag: 1,
351 rep_arity: 2,
352 field_bangs: vec![],
353 });
354 table
355 }
356
357 #[test]
358 fn test_effect_machine_pure_val() {
359 let table = make_test_table();
361 let mut heap = VecHeap::new();
362
363 let expr: CoreExpr = RecursiveTree {
365 nodes: vec![
366 CoreFrame::Lit(Literal::LitInt(42)),
367 CoreFrame::Con {
368 tag: DataConId(1), fields: vec![0],
370 },
371 ],
372 };
373
374 let mut handlers = frunk::HNil;
375 let mut machine = EffectMachine::new(&table, &mut heap).unwrap();
376 let result = machine.run(&expr, &mut handlers).unwrap();
377
378 match result {
379 Value::Lit(Literal::LitInt(n)) => assert_eq!(n, 42),
380 other => panic!("Expected Lit(42), got {:?}", other),
381 }
382 }
383
384 #[test]
385 fn test_effect_machine_single_effect() {
386 let table = make_test_table();
389 let mut heap = VecHeap::new();
390
391 let expr: CoreExpr = RecursiveTree {
392 nodes: vec![
393 CoreFrame::Var(VarId(100)),
395 CoreFrame::Con {
397 tag: DataConId(1), fields: vec![0],
399 },
400 CoreFrame::Lam {
402 binder: VarId(100),
403 body: 1,
404 },
405 CoreFrame::Con {
407 tag: DataConId(3), fields: vec![2],
409 },
410 CoreFrame::Lit(Literal::LitInt(99)),
412 CoreFrame::Lit(Literal::LitWord(0)),
414 CoreFrame::Con {
416 tag: DataConId(5), fields: vec![5, 4],
418 },
419 CoreFrame::Con {
421 tag: DataConId(2), fields: vec![6, 3],
423 },
424 ],
425 };
426
427 use crate::dispatch::{EffectContext, EffectHandler};
429 use tidepool_bridge::FromCore;
430
431 struct TestReq(i64);
432 impl FromCore for TestReq {
433 fn from_value(
434 value: &Value,
435 _table: &DataConTable,
436 ) -> Result<Self, tidepool_bridge::BridgeError> {
437 match value {
438 Value::Lit(Literal::LitInt(n)) => Ok(TestReq(*n)),
439 _ => Err(tidepool_bridge::BridgeError::TypeMismatch {
440 expected: "LitInt".into(),
441 got: format!("{:?}", value),
442 }),
443 }
444 }
445 }
446
447 struct TestHandler;
448 impl EffectHandler for TestHandler {
449 type Request = TestReq;
450 fn handle(
451 &mut self,
452 req: TestReq,
453 _cx: &EffectContext,
454 ) -> Result<Value, EffectError> {
455 Ok(Value::Lit(Literal::LitInt(req.0 + 1)))
457 }
458 }
459
460 let mut handlers = frunk::hlist![TestHandler];
461 let mut machine = EffectMachine::new(&table, &mut heap).unwrap();
462 let result = machine.run(&expr, &mut handlers).unwrap();
463
464 match result {
465 Value::Lit(Literal::LitInt(n)) => assert_eq!(n, 100),
466 other => panic!("Expected Lit(100), got {:?}", other),
467 }
468 }
469
470 #[test]
471 fn test_run_with_user_data() {
472 let table = make_test_table();
474 let mut heap = VecHeap::new();
475
476 let expr: CoreExpr = RecursiveTree {
477 nodes: vec![
478 CoreFrame::Var(VarId(100)),
479 CoreFrame::Con {
480 tag: DataConId(1),
481 fields: vec![0],
482 },
483 CoreFrame::Lam {
484 binder: VarId(100),
485 body: 1,
486 },
487 CoreFrame::Con {
488 tag: DataConId(3),
489 fields: vec![2],
490 },
491 CoreFrame::Lit(Literal::LitInt(10)),
492 CoreFrame::Lit(Literal::LitWord(0)),
493 CoreFrame::Con {
494 tag: DataConId(5),
495 fields: vec![5, 4],
496 },
497 CoreFrame::Con {
498 tag: DataConId(2),
499 fields: vec![6, 3],
500 },
501 ],
502 };
503
504 use crate::dispatch::{EffectContext, EffectHandler};
505 use tidepool_bridge::FromCore;
506
507 struct TestReq(i64);
508 impl FromCore for TestReq {
509 fn from_value(
510 value: &Value,
511 _table: &DataConTable,
512 ) -> Result<Self, tidepool_bridge::BridgeError> {
513 match value {
514 Value::Lit(Literal::LitInt(n)) => Ok(TestReq(*n)),
515 _ => Err(tidepool_bridge::BridgeError::TypeMismatch {
516 expected: "LitInt".into(),
517 got: format!("{:?}", value),
518 }),
519 }
520 }
521 }
522
523 struct UserData {
524 multiplier: i64,
525 }
526
527 struct UserHandler;
528 impl EffectHandler<UserData> for UserHandler {
529 type Request = TestReq;
530 fn handle(
531 &mut self,
532 req: TestReq,
533 cx: &EffectContext<'_, UserData>,
534 ) -> Result<Value, EffectError> {
535 Ok(Value::Lit(Literal::LitInt(req.0 * cx.user().multiplier)))
536 }
537 }
538
539 let user = UserData { multiplier: 5 };
540 let mut handlers = frunk::hlist![UserHandler];
541 let mut machine = EffectMachine::new(&table, &mut heap).unwrap();
542 let result = machine.run_with_user(&expr, &mut handlers, &user).unwrap();
543
544 match result {
545 Value::Lit(Literal::LitInt(n)) => assert_eq!(n, 50), other => panic!("Expected Lit(50), got {:?}", other),
547 }
548 }
549}
550
551#[cfg(all(test, feature = "async"))]
552mod async_tests {
553 use super::*;
554 use crate::dispatch::{AsyncEffectHandler, EffectContext};
555 use tidepool_bridge::FromCore;
556 use tidepool_eval::heap::VecHeap;
557 use tidepool_eval::value::Value;
558 use tidepool_repr::datacon::DataCon;
559 use tidepool_repr::datacon_table::DataConTable;
560 use tidepool_repr::types::{DataConId, Literal, VarId};
561 use tidepool_repr::{CoreExpr, CoreFrame, RecursiveTree};
562
563 fn make_test_table() -> DataConTable {
564 let mut table = DataConTable::new();
565 table.insert(DataCon {
566 id: DataConId(1),
567 name: "Val".to_string(),
568 tag: 1,
569 rep_arity: 1,
570 field_bangs: vec![],
571 });
572 table.insert(DataCon {
573 id: DataConId(2),
574 name: "E".to_string(),
575 tag: 2,
576 rep_arity: 2,
577 field_bangs: vec![],
578 });
579 table.insert(DataCon {
580 id: DataConId(3),
581 name: "Leaf".to_string(),
582 tag: 1,
583 rep_arity: 1,
584 field_bangs: vec![],
585 });
586 table.insert(DataCon {
587 id: DataConId(4),
588 name: "Node".to_string(),
589 tag: 2,
590 rep_arity: 2,
591 field_bangs: vec![],
592 });
593 table.insert(DataCon {
594 id: DataConId(5),
595 name: "Union".to_string(),
596 tag: 1,
597 rep_arity: 2,
598 field_bangs: vec![],
599 });
600 table
601 }
602
603 fn make_single_effect_expr() -> CoreExpr {
604 RecursiveTree {
606 nodes: vec![
607 CoreFrame::Var(VarId(100)),
608 CoreFrame::Con {
609 tag: DataConId(1),
610 fields: vec![0],
611 },
612 CoreFrame::Lam {
613 binder: VarId(100),
614 body: 1,
615 },
616 CoreFrame::Con {
617 tag: DataConId(3),
618 fields: vec![2],
619 },
620 CoreFrame::Lit(Literal::LitInt(99)),
621 CoreFrame::Lit(Literal::LitWord(0)),
622 CoreFrame::Con {
623 tag: DataConId(5),
624 fields: vec![5, 4],
625 },
626 CoreFrame::Con {
627 tag: DataConId(2),
628 fields: vec![6, 3],
629 },
630 ],
631 }
632 }
633
634 struct TestReq(i64);
635 impl FromCore for TestReq {
636 fn from_value(
637 value: &Value,
638 _table: &DataConTable,
639 ) -> Result<Self, tidepool_bridge::BridgeError> {
640 match value {
641 Value::Lit(Literal::LitInt(n)) => Ok(TestReq(*n)),
642 _ => Err(tidepool_bridge::BridgeError::TypeMismatch {
643 expected: "LitInt".into(),
644 got: format!("{:?}", value),
645 }),
646 }
647 }
648 }
649
650 struct AsyncTestHandler;
651 impl AsyncEffectHandler for AsyncTestHandler {
652 type Request = TestReq;
653 fn handle(
654 &mut self,
655 req: TestReq,
656 _cx: &EffectContext<'_, ()>,
657 ) -> impl std::future::Future<Output = Result<Value, EffectError>> + Send + '_ {
658 async move { Ok(Value::Lit(Literal::LitInt(req.0 + 1))) }
659 }
660 }
661
662 #[tokio::test]
663 async fn test_async_single_effect() {
664 let table = make_test_table();
665 let mut heap = VecHeap::new();
666 let expr = make_single_effect_expr();
667
668 let mut handlers = frunk::hlist![AsyncTestHandler];
669 let mut machine = EffectMachine::new(&table, &mut heap).unwrap();
670 let result = machine.run_async(&expr, &mut handlers, &()).await.unwrap();
671
672 match result {
673 Value::Lit(Literal::LitInt(n)) => assert_eq!(n, 100),
674 other => panic!("Expected Lit(100), got {:?}", other),
675 }
676 }
677
678 #[tokio::test]
679 async fn test_async_with_user_data() {
680 let table = make_test_table();
681 let mut heap = VecHeap::new();
682 let expr: CoreExpr = RecursiveTree {
683 nodes: vec![
684 CoreFrame::Var(VarId(100)),
685 CoreFrame::Con {
686 tag: DataConId(1),
687 fields: vec![0],
688 },
689 CoreFrame::Lam {
690 binder: VarId(100),
691 body: 1,
692 },
693 CoreFrame::Con {
694 tag: DataConId(3),
695 fields: vec![2],
696 },
697 CoreFrame::Lit(Literal::LitInt(10)),
698 CoreFrame::Lit(Literal::LitWord(0)),
699 CoreFrame::Con {
700 tag: DataConId(5),
701 fields: vec![5, 4],
702 },
703 CoreFrame::Con {
704 tag: DataConId(2),
705 fields: vec![6, 3],
706 },
707 ],
708 };
709
710 struct UserData {
711 multiplier: i64,
712 }
713
714 struct AsyncUserHandler;
715 impl AsyncEffectHandler<UserData> for AsyncUserHandler {
716 type Request = TestReq;
717 fn handle(
718 &mut self,
719 req: TestReq,
720 cx: &EffectContext<'_, UserData>,
721 ) -> impl std::future::Future<Output = Result<Value, EffectError>> + Send + '_ {
722 let result = req.0 * cx.user().multiplier;
723 async move { Ok(Value::Lit(Literal::LitInt(result))) }
724 }
725 }
726
727 let user = UserData { multiplier: 7 };
728 let mut handlers = frunk::hlist![AsyncUserHandler];
729 let mut machine = EffectMachine::new(&table, &mut heap).unwrap();
730 let result = machine
731 .run_async(&expr, &mut handlers, &user)
732 .await
733 .unwrap();
734
735 match result {
736 Value::Lit(Literal::LitInt(n)) => assert_eq!(n, 70), other => panic!("Expected Lit(70), got {:?}", other),
738 }
739 }
740
741 #[tokio::test]
742 async fn test_async_pure_val() {
743 let table = make_test_table();
744 let mut heap = VecHeap::new();
745 let expr: CoreExpr = RecursiveTree {
746 nodes: vec![
747 CoreFrame::Lit(Literal::LitInt(42)),
748 CoreFrame::Con {
749 tag: DataConId(1),
750 fields: vec![0],
751 },
752 ],
753 };
754
755 let mut handlers = frunk::HNil;
756 let mut machine = EffectMachine::new(&table, &mut heap).unwrap();
757 let result = machine.run_async(&expr, &mut handlers, &()).await.unwrap();
758
759 match result {
760 Value::Lit(Literal::LitInt(n)) => assert_eq!(n, 42),
761 other => panic!("Expected Lit(42), got {:?}", other),
762 }
763 }
764}