1pub mod event;
39
40use rune_ast::Noun;
41use cyber_hemera as hemera;
42
43const GOLDILOCKS_PRIME: u64 = 0xFFFF_FFFF_0000_0001u64;
45
46#[derive(Debug, Clone, PartialEq)]
47pub struct InterpError {
48 pub message: String,
49}
50
51impl std::fmt::Display for InterpError {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 write!(f, "{}", self.message)
54 }
55}
56
57pub trait Host {
66 fn perform(&mut self, act: u64, args: &Noun, caps: &Noun) -> Result<Noun, InterpError>;
67}
68
69pub struct DenyHost;
72impl Host for DenyHost {
73 fn perform(&mut self, _act: u64, _args: &Noun, _caps: &Noun) -> Result<Noun, InterpError> {
74 Ok(Noun::Atom(0))
75 }
76}
77
78pub fn eval(subject: &Noun, formula: &Noun) -> Result<Noun, InterpError> {
83 eval_with_host(subject, formula, &mut DenyHost)
84}
85
86pub fn eval_with_host(subject: &Noun, formula: &Noun, host: &mut dyn Host) -> Result<Noun, InterpError> {
93 let mut subj = subject.clone();
94 let mut form = formula.clone();
95
96 loop {
97 match form {
98 Noun::Cell(ref fh, ref ft) if matches!(fh.as_ref(), Noun::Cell(..)) => {
100 let h = eval_with_host(&subj, fh, host)?;
101 let t = eval_with_host(&subj, ft, host)?;
102 return Ok(Noun::cell(h, t));
103 }
104
105 Noun::Cell(ref op, ref rest) => match op.as_ref() {
106
107 Noun::Atom(0) => return eval_axis(&subj, rest),
109
110 Noun::Atom(1) => return Ok(*rest.clone()),
112
113 Noun::Atom(2) => {
115 let (a, b) = pair(rest)?;
116 let new_subj = eval_with_host(&subj, &a, host)?;
117 let new_form = eval_with_host(&subj, &b, host)?;
118 subj = new_subj;
119 form = new_form;
120 continue;
121 }
122
123 Noun::Atom(3) => {
125 let (a, b) = pair(rest)?;
126 let ha = eval_with_host(&subj, &a, host)?;
127 let ta = eval_with_host(&subj, &b, host)?;
128 return Ok(Noun::cell(ha, ta));
129 }
130
131 Noun::Atom(4) => {
133 let (test_f, ynb) = pair(rest)?;
134 let (yes_f, no_f) = pair(&ynb)?;
135 let test_val = eval_with_host(&subj, &test_f, host)?;
136 let test_n = atom_u64(&test_val, "nox-4 branch: test must be atom")?;
137 form = if test_n == 0 { yes_f } else { no_f };
138 continue;
139 }
140
141 Noun::Atom(5) => {
143 let (a, b) = pair(rest)?;
144 let va = eval_atom_h(&subj, &a, host, "nox-5 add")?;
145 let vb = eval_atom_h(&subj, &b, host, "nox-5 add")?;
146 return Ok(Noun::Atom(add_field(va, vb)));
147 }
148
149 Noun::Atom(6) => {
151 let (a, b) = pair(rest)?;
152 let va = eval_atom_h(&subj, &a, host, "nox-6 sub")?;
153 let vb = eval_atom_h(&subj, &b, host, "nox-6 sub")?;
154 return Ok(Noun::Atom(sub_field(va, vb)));
155 }
156
157 Noun::Atom(7) => {
159 let (a, b) = pair(rest)?;
160 let va = eval_atom_h(&subj, &a, host, "nox-7 mul")?;
161 let vb = eval_atom_h(&subj, &b, host, "nox-7 mul")?;
162 return Ok(Noun::Atom(mul_field(va, vb)));
163 }
164
165 Noun::Atom(8) => {
167 let va = eval_atom_h(&subj, rest, host, "nox-8 inv")?;
168 return Ok(Noun::Atom(inv_field(va)));
169 }
170
171 Noun::Atom(9) => {
173 let (a, b) = pair(rest)?;
174 let ra = eval_with_host(&subj, &a, host)?;
175 let rb = eval_with_host(&subj, &b, host)?;
176 return Ok(Noun::Atom(if ra == rb { 0 } else { 1 }));
177 }
178
179 Noun::Atom(10) => {
181 let (a, b) = pair(rest)?;
182 let va = eval_atom_h(&subj, &a, host, "nox-10 lt")?;
183 let vb = eval_atom_h(&subj, &b, host, "nox-10 lt")?;
184 return Ok(Noun::Atom(if va < vb { 0 } else { 1 }));
185 }
186
187 Noun::Atom(11) => {
189 let (a, b) = pair(rest)?;
190 let va = eval_atom_h(&subj, &a, host, "nox-11 xor")?;
191 let vb = eval_atom_h(&subj, &b, host, "nox-11 xor")?;
192 return Ok(Noun::Atom(va ^ vb));
193 }
194
195 Noun::Atom(12) => {
197 let (a, b) = pair(rest)?;
198 let va = eval_atom_h(&subj, &a, host, "nox-12 and")?;
199 let vb = eval_atom_h(&subj, &b, host, "nox-12 and")?;
200 return Ok(Noun::Atom(va & vb));
201 }
202
203 Noun::Atom(13) => {
205 let va = eval_atom_h(&subj, rest, host, "nox-13 not")?;
206 return Ok(Noun::Atom(!va));
207 }
208
209 Noun::Atom(14) => {
211 let (a, n) = pair(rest)?;
212 let va = eval_atom_h(&subj, &a, host, "nox-14 shl")?;
213 let vn = eval_atom_h(&subj, &n, host, "nox-14 shl")?;
214 return Ok(Noun::Atom(va << (vn & 63)));
215 }
216
217 Noun::Atom(15) => {
219 let r = eval_with_host(&subj, rest, host)?;
220 return Ok(Noun::Atom(hash_noun(&r)));
221 }
222
223 Noun::Atom(16) => {
230 let (meta, cont) = pair(rest)?;
231 let (tag_noun, arg_f) = pair(&meta)?;
232 if let Noun::Atom(t) = tag_noun {
233 if rune_ast::act::is_act(t) {
234 let args = eval_with_host(&subj, &arg_f, host)?;
235 let caps = axis(&subj, rune_ast::act::CAPS_AXIS)
236 .unwrap_or(Noun::Atom(0));
237 let result = host.perform(t, &args, &caps)?;
238 subj = Noun::cell(result, subj);
239 form = cont;
240 continue;
241 }
242 }
243 form = cont;
244 continue;
245 }
246
247 Noun::Atom(17) => {
249 let (path_form, world_form) = pair(rest)?;
250 let path = eval_with_host(&subj, &path_form, host)?;
251 let world = eval_with_host(&subj, &world_form, host)?;
252 return Ok(scry_world(&path, &world));
253 }
254
255 _ => return Err(err(&format!("nox: unrecognized opcode {:?}", op))),
256 },
257
258 Noun::Atom(_) => return Err(err("nox: atom is not a valid formula")),
259 }
260 }
261}
262
263pub fn axis(noun: &Noun, addr: u64) -> Result<Noun, InterpError> {
269 match addr {
270 0 => match noun {
271 Noun::Cell(..) => Ok(Noun::Atom(0)),
272 Noun::Atom(v) => Ok(Noun::Atom(*v)),
273 },
274 1 => Ok(noun.clone()),
275 2 => match noun {
276 Noun::Cell(h, _) => Ok(*h.clone()),
277 _ => Err(err("nox-0 axis: /2 on atom")),
278 },
279 3 => match noun {
280 Noun::Cell(_, t) => Ok(*t.clone()),
281 _ => Err(err("nox-0 axis: /3 on atom")),
282 },
283 n => {
284 let parent = axis(noun, n / 2)?;
285 axis(&parent, 2 + (n % 2))
286 }
287 }
288}
289
290fn scry_world(path: &Noun, world: &Noun) -> Noun {
294 match world {
295 Noun::Atom(_) => Noun::Atom(0),
296 Noun::Cell(entry, rest) => {
297 match entry.as_ref() {
298 Noun::Cell(key, val) if key.as_ref() == path => *val.clone(),
299 _ => scry_world(path, rest),
300 }
301 }
302 }
303}
304
305fn eval_axis(subj: &Noun, addr_noun: &Noun) -> Result<Noun, InterpError> {
308 let n = match addr_noun {
309 Noun::Atom(n) => *n,
310 _ => return Err(err("nox-0 axis: address must be an atom")),
311 };
312 axis(subj, n)
313}
314
315fn pair(noun: &Noun) -> Result<(Noun, Noun), InterpError> {
317 match noun {
318 Noun::Cell(h, t) => Ok((*h.clone(), *t.clone())),
319 _ => Err(err("nox: expected cell")),
320 }
321}
322
323fn eval_atom_h(subj: &Noun, formula: &Noun, host: &mut dyn Host, ctx: &str) -> Result<u64, InterpError> {
325 let r = eval_with_host(subj, formula, host)?;
326 atom_u64(&r, ctx)
327}
328
329fn atom_u64(noun: &Noun, ctx: &str) -> Result<u64, InterpError> {
331 match noun {
332 Noun::Atom(n) => Ok(*n),
333 _ => Err(err(&format!("{}: expected atom, got cell", ctx))),
334 }
335}
336
337fn add_field(a: u64, b: u64) -> u64 {
340 ((a as u128 + b as u128) % GOLDILOCKS_PRIME as u128) as u64
342}
343
344fn sub_field(a: u64, b: u64) -> u64 {
345 ((a as u128 + GOLDILOCKS_PRIME as u128 - b as u128) % GOLDILOCKS_PRIME as u128) as u64
346}
347
348fn mul_field(a: u64, b: u64) -> u64 {
349 ((a as u128 * b as u128) % GOLDILOCKS_PRIME as u128) as u64
350}
351
352fn inv_field(a: u64) -> u64 {
355 if a == 0 {
356 return 0;
357 }
358 let p = GOLDILOCKS_PRIME;
360 let exp = p - 2;
361 let mut base = a as u128;
362 let mut result: u128 = 1;
363 let mut e = exp;
364 let m = p as u128;
365 while e > 0 {
366 if e & 1 == 1 {
367 result = result * base % m;
368 }
369 base = base * base % m;
370 e >>= 1;
371 }
372 result as u64
373}
374
375fn hash_noun(noun: &Noun) -> u64 {
383 let digest = hash_noun_bytes(noun);
384 u64::from_le_bytes(digest[..8].try_into().unwrap())
385}
386
387fn hash_noun_bytes(noun: &Noun) -> [u8; 32] {
388 match noun {
389 Noun::Atom(n) => *hemera::hash(&n.to_le_bytes()).as_bytes(),
390 Noun::Cell(h, t) => {
391 let hh = hash_noun_bytes(h);
392 let ht = hash_noun_bytes(t);
393 let mut buf = [0u8; 64];
394 buf[..32].copy_from_slice(&hh);
395 buf[32..].copy_from_slice(&ht);
396 *hemera::hash(&buf).as_bytes()
397 }
398 }
399}
400
401fn err(msg: &str) -> InterpError {
402 InterpError { message: msg.to_string() }
403}
404
405#[cfg(test)]
408mod tests {
409 use super::*;
410
411 fn a(n: u64) -> Noun { Noun::Atom(n) }
412 fn c(h: Noun, t: Noun) -> Noun { Noun::cell(h, t) }
413
414 #[test]
417 fn axis_identity() {
418 assert_eq!(eval(&a(42), &c(a(0), a(1))).unwrap(), a(42));
420 }
421
422 #[test]
423 fn axis_head() {
424 let s = c(a(1), a(2));
425 assert_eq!(eval(&s, &c(a(0), a(2))).unwrap(), a(1));
426 }
427
428 #[test]
429 fn axis_tail() {
430 let s = c(a(1), a(2));
431 assert_eq!(eval(&s, &c(a(0), a(3))).unwrap(), a(2));
432 }
433
434 #[test]
435 fn axis_deep_6() {
436 let s = c(c(a(1), a(2)), c(a(3), a(4)));
438 assert_eq!(eval(&s, &c(a(0), a(6))).unwrap(), a(3));
439 }
440
441 #[test]
442 fn axis_deep_7() {
443 let s = c(c(a(1), a(2)), c(a(3), a(4)));
445 assert_eq!(eval(&s, &c(a(0), a(7))).unwrap(), a(4));
446 }
447
448 #[test]
449 fn axis_deep_14() {
450 let s = c(c(c(a(1), a(2)), c(a(3), a(4))), c(c(a(5), a(6)), c(a(7), a(8))));
454 assert_eq!(eval(&s, &c(a(0), a(14))).unwrap(), a(7));
455 }
456
457 #[test]
458 fn axis_zero_hash_cell_stub() {
459 let s = c(a(10), a(20));
460 assert_eq!(eval(&s, &c(a(0), a(0))).unwrap(), a(0));
461 }
462
463 #[test]
464 fn axis_zero_hash_atom_stub() {
465 assert_eq!(eval(&a(99), &c(a(0), a(0))).unwrap(), a(99));
467 }
468
469 #[test]
472 fn quote_atom() {
473 assert_eq!(eval(&a(0), &c(a(1), a(42))).unwrap(), a(42));
474 }
475
476 #[test]
477 fn quote_cell() {
478 let v = c(a(1), a(2));
479 assert_eq!(eval(&a(0), &c(a(1), v.clone())).unwrap(), v);
480 }
481
482 #[test]
485 fn compose_identity_chain() {
486 let s = c(a(99), a(0));
492 let id = c(a(0), a(1));
493 let quote_head = c(a(1), c(a(0), a(2)));
494 let formula = c(a(2), c(id, quote_head));
495 assert_eq!(eval(&s, &formula).unwrap(), a(99));
496 }
497
498 #[test]
499 fn compose_quote_then_identity() {
500 let s = a(5);
508 let id = c(a(0), a(1));
509 let quote_id = c(a(1), id.clone());
510 let formula = c(a(2), c(id, quote_id));
511 assert_eq!(eval(&s, &formula).unwrap(), s);
512 }
513
514 #[test]
517 fn cons_two_literals() {
518 let formula = c(a(3), c(c(a(1), a(1)), c(a(1), a(2))));
520 assert_eq!(eval(&a(0), &formula).unwrap(), c(a(1), a(2)));
521 }
522
523 #[test]
526 fn branch_zero_takes_yes() {
527 let formula = c(a(4), c(c(a(1), a(0)), c(c(a(1), a(99)), c(a(1), a(0)))));
529 assert_eq!(eval(&a(0), &formula).unwrap(), a(99));
530 }
531
532 #[test]
533 fn branch_nonzero_takes_no() {
534 let formula = c(a(4), c(c(a(1), a(1)), c(c(a(1), a(99)), c(a(1), a(77)))));
536 assert_eq!(eval(&a(0), &formula).unwrap(), a(77));
537 }
538
539 #[test]
542 fn add_basic() {
543 let formula = c(a(5), c(c(a(1), a(5)), c(a(1), a(3))));
545 assert_eq!(eval(&a(0), &formula).unwrap(), a(8));
546 }
547
548 #[test]
549 fn add_wraps_goldilocks() {
550 let p_minus_1 = GOLDILOCKS_PRIME - 1;
552 let formula = c(a(5), c(c(a(1), a(p_minus_1)), c(a(1), a(1))));
553 assert_eq!(eval(&a(0), &formula).unwrap(), a(0));
554 }
555
556 #[test]
559 fn sub_basic() {
560 let formula = c(a(6), c(c(a(1), a(10)), c(a(1), a(3))));
562 assert_eq!(eval(&a(0), &formula).unwrap(), a(7));
563 }
564
565 #[test]
566 fn sub_wraps_around() {
567 let formula = c(a(6), c(c(a(1), a(0)), c(a(1), a(1))));
569 assert_eq!(eval(&a(0), &formula).unwrap(), a(GOLDILOCKS_PRIME - 1));
570 }
571
572 #[test]
575 fn mul_basic() {
576 let formula = c(a(7), c(c(a(1), a(6)), c(a(1), a(7))));
578 assert_eq!(eval(&a(0), &formula).unwrap(), a(42));
579 }
580
581 #[test]
584 fn inv_nonzero() {
585 let formula = c(a(8), c(a(1), a(2)));
587 let result = eval(&a(0), &formula).unwrap();
588 let Noun::Atom(r) = result else { panic!("expected atom") };
589 assert_eq!(mul_field(2, r), 1);
590 }
591
592 #[test]
593 fn inv_zero() {
594 let formula = c(a(8), c(a(1), a(0)));
596 assert_eq!(eval(&a(0), &formula).unwrap(), a(0));
597 }
598
599 #[test]
602 fn eq_atoms_equal() {
603 let formula = c(a(9), c(c(a(1), a(42)), c(a(1), a(42))));
605 assert_eq!(eval(&a(0), &formula).unwrap(), a(0));
606 }
607
608 #[test]
609 fn eq_atoms_not_equal() {
610 let formula = c(a(9), c(c(a(1), a(1)), c(a(1), a(2))));
612 assert_eq!(eval(&a(0), &formula).unwrap(), a(1));
613 }
614
615 #[test]
616 fn eq_cells_equal() {
617 let cell_val = c(a(1), a(2));
619 let formula = c(a(9), c(c(a(1), cell_val.clone()), c(a(1), cell_val)));
620 assert_eq!(eval(&a(0), &formula).unwrap(), a(0));
621 }
622
623 #[test]
624 fn eq_cells_not_equal() {
625 let formula = c(a(9), c(c(a(1), c(a(1), a(2))), c(a(1), c(a(1), a(3)))));
627 assert_eq!(eval(&a(0), &formula).unwrap(), a(1));
628 }
629
630 #[test]
633 fn lt_true() {
634 let formula = c(a(10), c(c(a(1), a(3)), c(a(1), a(7))));
636 assert_eq!(eval(&a(0), &formula).unwrap(), a(0));
637 }
638
639 #[test]
640 fn lt_false_equal() {
641 let formula = c(a(10), c(c(a(1), a(7)), c(a(1), a(7))));
643 assert_eq!(eval(&a(0), &formula).unwrap(), a(1));
644 }
645
646 #[test]
647 fn lt_false_greater() {
648 let formula = c(a(10), c(c(a(1), a(9)), c(a(1), a(3))));
650 assert_eq!(eval(&a(0), &formula).unwrap(), a(1));
651 }
652
653 #[test]
656 fn xor_basic() {
657 let formula = c(a(11), c(c(a(1), a(0b1010)), c(a(1), a(0b1100))));
659 assert_eq!(eval(&a(0), &formula).unwrap(), a(0b0110));
660 }
661
662 #[test]
665 fn and_basic() {
666 let formula = c(a(12), c(c(a(1), a(0b1100)), c(a(1), a(0b1010))));
667 assert_eq!(eval(&a(0), &formula).unwrap(), a(0b1000));
668 }
669
670 #[test]
673 fn not_basic() {
674 let formula = c(a(13), c(a(1), a(0)));
675 assert_eq!(eval(&a(0), &formula).unwrap(), a(!0u64));
676 }
677
678 #[test]
681 fn shl_basic() {
682 let formula = c(a(14), c(c(a(1), a(1)), c(a(1), a(3))));
684 assert_eq!(eval(&a(0), &formula).unwrap(), a(8));
685 }
686
687 #[test]
690 fn hash_atom_returns_atom() {
691 let formula = c(a(15), c(a(1), a(55)));
693 let result = eval(&a(0), &formula).unwrap();
694 assert!(matches!(result, Noun::Atom(_)));
695 }
696
697 #[test]
698 fn hash_atom_is_deterministic() {
699 let formula = c(a(15), c(a(1), a(42)));
700 let r1 = eval(&a(0), &formula).unwrap();
701 let r2 = eval(&a(0), &formula).unwrap();
702 assert_eq!(r1, r2);
703 }
704
705 #[test]
706 fn hash_atom_differs_by_value() {
707 let f1 = c(a(15), c(a(1), a(1)));
708 let f2 = c(a(15), c(a(1), a(2)));
709 let r1 = eval(&a(0), &f1).unwrap();
710 let r2 = eval(&a(0), &f2).unwrap();
711 assert_ne!(r1, r2);
712 }
713
714 #[test]
715 fn hash_cell_returns_atom() {
716 let formula = c(a(15), c(a(3), c(c(a(1), a(1)), c(a(1), a(2)))));
718 let result = eval(&a(0), &formula).unwrap();
719 assert!(matches!(result, Noun::Atom(_)));
720 }
721
722 #[test]
723 fn hash_cell_differs_from_atom() {
724 let atom_f = c(a(15), c(a(1), a(1)));
726 let cell_f = c(a(15), c(a(3), c(c(a(1), a(1)), c(a(1), a(2)))));
727 let ra = eval(&a(0), &atom_f).unwrap();
728 let rc = eval(&a(0), &cell_f).unwrap();
729 assert_ne!(ra, rc);
730 }
731
732 #[test]
735 fn hint_evaluates_body() {
736 let tag = c(a(1), a(42));
739 let selector = c(a(1), a(0));
740 let hint_meta = c(tag, selector);
741 let body = c(a(1), a(99));
742 let formula = c(a(16), c(hint_meta, body));
743 assert_eq!(eval(&a(0), &formula).unwrap(), a(99));
744 }
745
746 #[test]
747 fn hint_evaluates_arithmetic_body() {
748 let hint_meta = c(c(a(1), a(0)), c(a(1), a(0)));
750 let body = c(a(5), c(c(a(1), a(3)), c(a(1), a(4))));
751 let formula = c(a(16), c(hint_meta, body));
752 assert_eq!(eval(&a(0), &formula).unwrap(), a(7));
753 }
754
755 #[test]
756 fn host_call_returns_zero() {
757 let hint_meta = c(c(a(1), a(99)), c(a(1), a(0)));
759 let body = c(a(1), a(0)); let formula = c(a(16), c(hint_meta, body));
761 assert_eq!(eval(&a(0), &formula).unwrap(), a(0));
762 }
763
764 #[test]
767 fn look_stub_returns_zero() {
768 let formula = c(a(17), c(c(a(1), a(42)), c(a(1), a(0))));
770 assert_eq!(eval(&a(0), &formula).unwrap(), a(0));
771 }
772
773 #[test]
774 fn look_finds_entry_in_world() {
775 let world = c(c(a(42), a(99)), a(0));
778 let formula = c(a(17), c(c(a(1), a(42)), c(a(1), world)));
779 assert_eq!(eval(&a(0), &formula).unwrap(), a(99));
780 }
781
782 #[test]
783 fn look_misses_entry_returns_zero() {
784 let world = c(c(a(42), a(99)), a(0));
786 let formula = c(a(17), c(c(a(1), a(7)), c(a(1), world)));
787 assert_eq!(eval(&a(0), &formula).unwrap(), a(0));
788 }
789
790 #[test]
793 fn distribution_cell_formula() {
794 let formula = c(c(a(1), a(10)), c(a(1), a(20)));
797 assert_eq!(eval(&a(0), &formula).unwrap(), c(a(10), a(20)));
798 }
799
800 #[test]
801 fn distribution_nested() {
802 let formula = c(c(a(1), a(1)), c(a(1), a(2)));
804 assert_eq!(eval(&a(0), &formula).unwrap(), c(a(1), a(2)));
805 }
806
807 #[test]
810 fn compose_chaining_via_quote() {
811 let s = a(42);
816 let formula = c(a(2), c(c(a(0), a(1)), c(a(1), c(a(0), a(1)))));
817 assert_eq!(eval(&s, &formula).unwrap(), a(42));
818 }
819
820 #[test]
823 fn pub_axis_fn() {
824 let s = c(c(a(1), a(2)), c(a(3), a(4)));
825 assert_eq!(axis(&s, 1).unwrap(), s);
826 assert_eq!(axis(&s, 2).unwrap(), c(a(1), a(2)));
827 assert_eq!(axis(&s, 3).unwrap(), c(a(3), a(4)));
828 assert_eq!(axis(&s, 6).unwrap(), a(3));
829 assert_eq!(axis(&s, 7).unwrap(), a(4));
830 }
831
832 #[test]
835 fn field_add_sub_inverse() {
836 let x = 12345678u64;
837 let y = 87654321u64;
838 assert_eq!(sub_field(add_field(x, y), y), x);
839 }
840
841 #[test]
842 fn field_mul_inv() {
843 let x = 7u64;
844 let ix = inv_field(x);
845 assert_eq!(mul_field(x, ix), 1);
846 }
847
848 use rune_ast::act;
851
852 struct Recorder { acts: Vec<(u64, Noun)>, caps: Noun, reply: Noun }
853 impl Host for Recorder {
854 fn perform(&mut self, act: u64, args: &Noun, caps: &Noun) -> Result<Noun, InterpError> {
855 self.acts.push((act, args.clone()));
856 self.caps = caps.clone();
857 Ok(self.reply.clone())
858 }
859 }
860
861 fn emit_act(n: u64) -> Noun {
863 c(a(16), c(c(a(act::EMIT), c(a(1), a(n))), c(a(0), a(2))))
864 }
865
866 #[test]
867 fn act_performs_and_splices_result() {
868 let mut h = Recorder { acts: vec![], caps: a(0), reply: a(55) };
869 let r = eval_with_host(&a(0), &emit_act(7), &mut h).unwrap();
870 assert_eq!(h.acts.len(), 1);
871 assert_eq!(h.acts[0].0, act::EMIT);
872 assert_eq!(h.acts[0].1, a(7)); assert_eq!(r, a(55)); }
875
876 #[test]
877 fn acts_compose_via_cons() {
878 let formula = c(a(3), c(emit_act(1), emit_act(2)));
880 let mut h = Recorder { acts: vec![], caps: a(0), reply: a(0) };
881 eval_with_host(&a(0), &formula, &mut h).unwrap();
882 let args: Vec<Noun> = h.acts.iter().map(|(_, n)| n.clone()).collect();
883 assert_eq!(args, vec![a(1), a(2)]);
884 }
885
886 #[test]
887 fn act_reads_caps_from_axis_30() {
888 let subj = c(a(0), c(a(1), c(a(2), c(a(123), a(0)))));
890 let mut h = Recorder { acts: vec![], caps: a(0), reply: a(0) };
891 eval_with_host(&subj, &emit_act(7), &mut h).unwrap();
892 assert_eq!(h.caps, a(123));
893 }
894
895 #[test]
896 fn pure_eval_noops_acts() {
897 assert_eq!(eval(&a(0), &emit_act(7)).unwrap(), a(0));
899 }
900}