1use std::collections::HashMap;
2
3use tokio::sync::mpsc::{channel, Receiver, Sender, WeakSender};
4
5use crate::floor::machine::Fault;
6use crate::floor::{list, list_elements, pair, rc_val, Val};
7use crate::link::{self, Frame};
8use crate::sched::{self, addr, Addr, Heap, Id};
9use crate::wire;
10
11fn sym(s: &str) -> Val {
12 Val::Sym(s.into())
13}
14
15pub type ConnId = u64;
17
18pub enum Event {
22 Post(Val, Val),
23 Link(ConnId, Sender<Frame>),
27 Down(ConnId),
29 Node(ConnId, Val),
40 Gone(String),
43}
44
45pub fn host(name: &str) -> Val {
48 list(&[sym("host"), sym(name)])
49}
50
51pub fn request(msg: &Val) -> Option<(Val, Val, Val)> {
54 match list_elements(msg)?.as_slice() {
55 [tag, from, id, body] if *tag == sym("req") => {
56 Some((from.clone(), id.clone(), body.clone()))
57 }
58 _ => None,
59 }
60}
61
62pub fn reply(id: Val, v: Val) -> Val {
64 list(&[sym("reply"), id, v])
65}
66
67fn lost(conn: ConnId, id: Id) -> Val {
71 let why = pair(sym("throw"), sym("noconnection"));
72 sched::down(Addr::Remote(conn, id).val(), why)
73}
74
75fn gone(target: Val) -> Val {
79 sched::down(target, pair(sym("throw"), sym("noproc")))
80}
81
82fn watch_of(id: Id) -> Val {
85 list(&[sym("watch"), Val::Num(id as i64)])
86}
87
88fn down_of(id: Id, result: Val) -> Val {
89 list(&[sym("down"), Val::Num(id as i64), result])
90}
91
92pub struct World {
94 heap: Heap,
95 hosts: HashMap<String, Sender<Val>>,
96 links: HashMap<ConnId, Sender<Frame>>,
97 watches: HashMap<ConnId, Vec<(Id, Id)>>,
100 peer_watches: HashMap<String, Vec<Id>>,
102 remote_watches: HashMap<Id, Vec<ConnId>>,
105 port: Option<Sender<Event>>,
108 back: WeakSender<Event>,
111 inbox: Receiver<Event>,
112}
113
114impl World {
115 pub fn new(budget: usize) -> World {
116 let (port, inbox) = channel(64);
117 let back = port.downgrade();
118 World {
119 heap: Heap::new(budget),
120 hosts: HashMap::new(),
121 links: HashMap::new(),
122 watches: HashMap::new(),
123 peer_watches: HashMap::new(),
124 remote_watches: HashMap::new(),
125 port: Some(port),
126 back,
127 inbox,
128 }
129 }
130
131 pub fn heap(&mut self) -> &mut Heap {
132 &mut self.heap
133 }
134
135 pub fn serve(&mut self, name: &str, tx: Sender<Val>) {
138 self.hosts.insert(name.to_string(), tx);
139 }
140
141 pub fn link(&mut self, conn: ConnId, tx: Sender<Frame>) {
144 self.links.insert(conn, tx);
145 }
146
147 pub fn port(&self) -> Sender<Event> {
150 self.port.clone().expect("the world has not started")
151 }
152
153 pub async fn run(mut self) -> Result<Vec<(Id, Val)>, Fault> {
156 self.port = None;
157 let mut ended = Vec::new();
158 loop {
159 self.heap.run()?;
160 let exits = self.heap.take_exits();
161 let mut woke = false;
166 for (watcher, target) in self.heap.take_watches() {
167 woke |= self.watch(watcher, target).await;
168 }
169 for (to, msg) in self.heap.take_away() {
170 woke |= self.forward(to, msg).await;
171 }
172 for (id, result) in &exits {
176 self.ended(*id, result).await;
177 }
178 ended.extend(exits);
179 if self.heap.idle() {
180 return Ok(ended);
181 }
182 if woke {
185 continue;
186 }
187 match self.inbox.recv().await {
188 Some(Event::Post(to, msg)) => self.heap.post(&to, msg),
189 Some(Event::Link(conn, tx)) => {
190 self.links.insert(conn, tx);
191 }
192 Some(Event::Down(conn)) => {
193 self.links.remove(&conn);
194 for (watcher, id) in self.watches.remove(&conn).unwrap_or_default() {
195 self.heap.post(&addr(watcher), lost(conn, id));
196 }
197 self.remote_watches.retain(|_, conns| {
198 conns.retain(|c| *c != conn);
199 !conns.is_empty()
200 });
201 }
202 Some(Event::Node(conn, body)) => self.node(conn, body).await,
203 Some(Event::Gone(name)) => {
204 self.hosts.remove(&name);
205 for watcher in self.peer_watches.remove(&name).unwrap_or_default() {
206 self.heap.post(&addr(watcher), gone(host(&name)));
207 }
208 }
209 None => return Ok(ended),
210 }
211 }
212 }
213
214 async fn watch(&mut self, watcher: Id, target: Val) -> bool {
219 if let Some(Addr::Remote(conn, id)) = Addr::of(&target) {
220 let (conn, id) = (*conn, *id);
221 if !self.links.contains_key(&conn) {
222 self.heap.post(&addr(watcher), lost(conn, id));
223 return true;
224 }
225 let waiting = self.watches.entry(conn).or_default();
229 let first = !waiting.iter().any(|(_, far)| *far == id);
230 waiting.push((watcher, id));
231 if first {
232 self.forward_to_link(conn, link::NODE, watch_of(id)).await;
233 }
234 return false;
235 }
236 match list_elements(&target).as_deref() {
237 Some([tag, Val::Sym(name)]) if *tag == sym("host") => {
238 let name = name.to_string();
239 self.watch_host(watcher, name)
240 }
241 _ => {
244 self.heap.post(&addr(watcher), gone(target));
245 true
246 }
247 }
248 }
249
250 fn watch_host(&mut self, watcher: Id, name: String) -> bool {
259 let peer = self.hosts.get(&name).cloned();
260 let (Some(peer), Some(back)) = (peer, self.back.upgrade()) else {
261 self.heap.post(&addr(watcher), gone(host(&name)));
262 return true;
263 };
264 let watchers = self.peer_watches.entry(name.clone()).or_default();
265 let first = watchers.is_empty();
266 watchers.push(watcher);
267 if first {
268 tokio::spawn(async move {
269 tokio::select! {
270 () = peer.closed() => {
271 let _ = back.send(Event::Gone(name)).await;
272 }
273 () = back.closed() => {}
274 }
275 });
276 }
277 false
278 }
279
280 async fn node(&mut self, conn: ConnId, body: Val) {
283 match list_elements(&body).as_deref() {
284 Some([verb, Val::Num(n)]) if *verb == sym("watch") => {
285 let id = *n as Id;
286 if self.heap.alive(id) {
287 self.remote_watches.entry(id).or_default().push(conn);
288 } else {
289 let why = pair(sym("throw"), sym("noproc"));
292 self.forward_to_link(conn, link::NODE, down_of(id, why))
293 .await;
294 }
295 }
296 Some([verb, Val::Num(n), result]) if *verb == sym("down") => {
297 let id = *n as Id;
298 let target = Addr::Remote(conn, id).val();
299 let mut waiting = self.watches.remove(&conn).unwrap_or_default();
300 waiting.retain(|(watcher, far)| {
301 if *far != id {
302 return true;
303 }
304 let told = sched::down(target.clone(), result.clone());
305 self.heap.post(&addr(*watcher), told);
306 false
307 });
308 if !waiting.is_empty() {
309 self.watches.insert(conn, waiting);
310 }
311 }
312 _ => {}
315 }
316 }
317
318 async fn ended(&mut self, id: Id, result: &Val) {
323 let Some(conns) = self.remote_watches.remove(&id) else {
324 return;
325 };
326 for conn in conns {
327 let carried = match link::departs(result, conn) {
328 true => result.clone(),
329 false => pair(sym("throw"), sym("third-party-address")),
330 };
331 self.forward_to_link(conn, link::NODE, down_of(id, carried))
332 .await;
333 }
334 }
335
336 async fn forward(&mut self, to: Val, msg: Val) -> bool {
341 if let Some(Addr::Remote(conn, id)) = Addr::of(&to) {
342 return self.forward_to_link(*conn, *id as u64, msg).await;
343 }
344 match list_elements(&to).as_deref() {
345 Some([tag, Val::Sym(name)]) if *tag == sym("host") => {
346 let name = name.to_string();
347 self.forward_to_host(&name, msg).await
348 }
349 _ => self.undeliverable(&msg, sym("not-local")),
350 }
351 }
352
353 async fn forward_to_host(&mut self, name: &str, msg: Val) -> bool {
354 let Some(tx) = self.hosts.get(name) else {
355 return self.undeliverable(&msg, pair(sym("no-host"), sym(name)));
356 };
357 if tx.send(msg.clone()).await.is_err() {
358 self.hosts.remove(name);
359 return self.undeliverable(&msg, pair(sym("no-host"), sym(name)));
360 }
361 false
362 }
363
364 async fn forward_to_link(&mut self, conn: ConnId, to: u64, msg: Val) -> bool {
368 let Some(tx) = self.links.get(&conn) else {
369 return self.undeliverable(&msg, pair(sym("no-link"), Val::Num(conn as i64)));
370 };
371 if !link::departs(&msg, conn) {
372 return self.undeliverable(&msg, sym("third-party-address"));
373 }
374 let body = wire::encode(&msg).expect("a payload is data");
375 if tx.send(Frame { to, body }).await.is_err() {
376 self.links.remove(&conn);
377 return self.undeliverable(&msg, pair(sym("no-link"), Val::Num(conn as i64)));
378 }
379 false
380 }
381
382 fn undeliverable(&mut self, msg: &Val, why: Val) -> bool {
383 let Some((from, id, _)) = request(msg) else {
384 return false;
385 };
386 let thrown = Val::Pair(rc_val(sym("throw")), rc_val(why));
387 self.heap.post(&from, reply(id, thrown));
388 true
389 }
390}
391
392#[cfg(test)]
393mod tests {
394 use super::*;
395 use crate::floor::host::Str;
396 use crate::floor::machine::{Machine, NoMail, Store};
397 use crate::floor::Env;
398
399 fn val(src: &str) -> Val {
400 let Val::Code(e) = crate::surface::compile(src).expect("compiles") else {
401 panic!("not code")
402 };
403 Machine::new(Env::default(), e)
404 .run(&mut Store::<NoMail>::default())
405 .expect("no fault")
406 }
407
408 fn ok(v: Val) -> Val {
409 pair(sym("ok"), v)
410 }
411
412 #[tokio::test]
413 async fn a_world_runs_a_heap_that_needs_nothing() {
414 let mut w = World::new(1000);
415 let id = w.heap().spawn(val("(lambda t (_) (+ 1 2))"));
416 assert_eq!(w.run().await.expect("no bug"), vec![(id, ok(Val::Num(3)))]);
417 }
418
419 #[tokio::test]
420 async fn a_world_gives_up_when_nothing_could_wake_it() {
421 let mut w = World::new(1000);
422 w.heap().spawn(val("(lambda t (_) (receive 0))"));
423 assert_eq!(w.run().await.expect("no bug"), vec![]);
424 }
425
426 #[tokio::test]
427 async fn a_message_crosses_a_link() {
428 let (near, far) = tokio::io::duplex(64);
429
430 let mut b = World::new(1000);
431 let there = b.heap().spawn(val("(lambda t (_) (receive))"));
432 b.link(0, crate::link::bridge(0, far, b.port()));
433
434 let mut a = World::new(1000);
435 let here = a
439 .heap()
440 .spawn(val("(lambda t (_) (begin (send (receive) 'hi) (receive)))"));
441 a.heap().post(&addr(here), Addr::Remote(0, there).val());
442 a.link(0, crate::link::bridge(0, near, a.port()));
443
444 let (ran_a, ran_b) = tokio::join!(a.run(), b.run());
445 assert_eq!(ran_a.expect("no bug"), vec![]);
446 assert_eq!(ran_b.expect("no bug"), vec![(there, ok(sym("hi")))]);
447 }
448
449 #[tokio::test]
450 async fn a_reply_finds_its_way_back_across_a_link() {
451 let (near, far) = tokio::io::duplex(64);
452
453 let mut b = World::new(1000);
454 let there = b
455 .heap()
456 .spawn(val("(lambda t (_) (send (car (receive)) 'pong))"));
457 b.link(0, crate::link::bridge(0, far, b.port()));
458
459 let mut a = World::new(1000);
460 let here = a
461 .heap()
462 .spawn(val("(lambda t (me) (begin (send (receive) (cons me 'ping))
463 (receive)))"));
464 a.heap().post(&addr(here), Addr::Remote(0, there).val());
465 a.link(0, crate::link::bridge(0, near, a.port()));
466
467 let (ran_a, ran_b) = tokio::join!(a.run(), b.run());
468 assert_eq!(ran_a.expect("no bug"), vec![(here, ok(sym("pong")))]);
469 assert_eq!(ran_b.expect("no bug"), vec![(there, ok(Val::Nil))]);
470 }
471
472 #[tokio::test]
473 async fn an_address_for_a_third_node_does_not_cross() {
474 let (near, _far) = tokio::io::duplex(64);
475 let mut w = World::new(1000);
476 let id = w.heap().spawn(val("(lambda t (me)
477 (let ((m (receive)))
478 (begin (send (car m) (list 'req me 7 (cdr m))) (receive))))"));
479 w.heap().post(
480 &addr(id),
481 pair(Addr::Remote(0, 1).val(), Addr::Remote(1, 2).val()),
482 );
483 w.link(0, crate::link::bridge(0, near, w.port()));
484 assert_eq!(
485 w.run().await.expect("no bug"),
486 vec![(
487 id,
488 ok(list(&[
489 sym("reply"),
490 Val::Num(7),
491 pair(sym("throw"), sym("third-party-address"))
492 ]))
493 )]
494 );
495 }
496
497 #[tokio::test]
498 async fn a_call_across_a_link_that_is_not_there_is_answered() {
499 let mut w = World::new(1000);
500 let id = w.heap().spawn(val("(lambda t (me)
501 (begin (send (receive) (list 'req me 7 'anything))
502 (receive)))"));
503 w.heap().post(&addr(id), Addr::Remote(7, 1).val());
504 assert_eq!(
505 w.run().await.expect("no bug"),
506 vec![(
507 id,
508 ok(list(&[
509 sym("reply"),
510 Val::Num(7),
511 pair(sym("throw"), pair(sym("no-link"), Val::Num(7)))
512 ]))
513 )]
514 );
515 }
516
517 #[tokio::test]
518 async fn a_watch_across_a_link_fires_when_the_link_dies() {
519 let (near, far) = tokio::io::duplex(64);
520 let mut w = World::new(1000);
521 let id = w
522 .heap()
523 .spawn(val("(lambda t (_) (begin (monitor (receive)) (receive)))"));
524 w.heap().post(&addr(id), Addr::Remote(0, 1).val());
525 w.link(0, crate::link::bridge(0, near, w.port()));
526 drop(far);
527 assert_eq!(
528 w.run().await.expect("no bug"),
529 vec![(
530 id,
531 ok(sched::down(
532 Addr::Remote(0, 1).val(),
533 pair(sym("throw"), sym("noconnection"))
534 ))
535 )]
536 );
537 }
538
539 #[tokio::test]
540 async fn a_watch_across_a_link_fires_when_the_far_task_ends() {
541 let (near, far) = tokio::io::duplex(64);
542
543 let mut b = World::new(1000);
544 let there = b.heap().spawn(val("(lambda t (_) (receive))"));
545 b.link(0, crate::link::bridge(0, far, b.port()));
546
547 let mut a = World::new(1000);
548 let here = a.heap().spawn(val("(lambda t (_) (let ((to (receive)))
549 (begin (monitor to) (send to 'bye) (receive))))"));
550 a.heap().post(&addr(here), Addr::Remote(0, there).val());
551 a.link(0, crate::link::bridge(0, near, a.port()));
552
553 let (ran_a, ran_b) = tokio::join!(a.run(), b.run());
554 assert_eq!(
555 ran_a.expect("no bug"),
556 vec![(
557 here,
558 ok(sched::down(Addr::Remote(0, there).val(), ok(sym("bye"))))
559 )]
560 );
561 assert_eq!(ran_b.expect("no bug"), vec![(there, ok(sym("bye")))]);
562 }
563
564 #[tokio::test]
565 async fn a_watch_on_a_far_task_that_has_ended_is_answered() {
566 let (near, far) = tokio::io::duplex(64);
567
568 let mut b = World::new(1000);
569 let there = b.heap().spawn(val("(lambda t (_) 'done)"));
572 b.heap().spawn(val("(lambda t (_) (receive))"));
577 b.link(0, crate::link::bridge(0, far, b.port()));
578
579 let mut a = World::new(1000);
580 let here = a
581 .heap()
582 .spawn(val("(lambda t (_) (begin (monitor (receive)) (receive)))"));
583 a.heap().post(&addr(here), Addr::Remote(0, there).val());
584 a.link(0, crate::link::bridge(0, near, a.port()));
585
586 let (ran_a, ran_b) = tokio::join!(a.run(), b.run());
587 assert_eq!(ran_b.expect("no bug"), vec![(there, ok(sym("done")))]);
588 assert_eq!(
589 ran_a.expect("no bug"),
590 vec![(
591 here,
592 ok(sched::down(
593 Addr::Remote(0, there).val(),
594 pair(sym("throw"), sym("noproc"))
595 ))
596 )]
597 );
598 }
599
600 #[tokio::test]
601 async fn a_watch_across_a_link_that_is_not_there_is_answered() {
602 let mut w = World::new(1000);
603 let id = w
604 .heap()
605 .spawn(val("(lambda t (_) (begin (monitor (receive)) (receive)))"));
606 w.heap().post(&addr(id), Addr::Remote(7, 1).val());
607 assert_eq!(
608 w.run().await.expect("no bug"),
609 vec![(
610 id,
611 ok(sched::down(
612 Addr::Remote(7, 1).val(),
613 pair(sym("throw"), sym("noconnection"))
614 ))
615 )]
616 );
617 }
618
619 #[tokio::test]
620 async fn a_syscall_is_a_send_and_a_receive() {
621 let mut w = World::new(1000);
622 let id = w.heap().spawn(val("(lambda t (me)
623 (begin (send '(host upper) (cons me 'ping)) (receive)))"));
624
625 let (tx, mut rx) = channel(8);
626 w.serve("upper", tx);
627 let back = w.port();
628 tokio::spawn(async move {
629 let msg = rx.recv().await.expect("a request");
630 let Val::Pair(reply_to, payload) = msg else {
631 panic!("not a request")
632 };
633 let answer = pair(sym("pong"), Val::clone(&payload));
634 back.send(Event::Post(Val::clone(&reply_to), answer))
635 .await
636 .expect("open");
637 });
638
639 assert_eq!(
640 w.run().await.expect("no bug"),
641 vec![(id, ok(pair(sym("pong"), sym("ping"))))]
642 );
643 }
644
645 #[tokio::test]
646 async fn a_call_to_an_adapter_that_ends_raises_at_the_call_site() {
647 let mut t = crate::tower::Tower::load().expect("loads");
648 let forms = crate::floor::read::read(
649 "(task-loop self
650 (lambda h (st msg)
651 (cons 'stop (attempt (lambda () (call '(host quiet) 'hi)))))
652 0)",
653 )
654 .expect("reads");
655 let entry = t
656 .task(&crate::surface::desugar_body(&forms).expect("desugars"))
657 .expect("no fault");
658
659 let mut w = World::new(1_000_000);
660 let id = w.heap().spawn(entry);
661 w.heap().post(&addr(id), sym("go"));
662
663 let (tx, mut rx) = channel(8);
664 w.serve("quiet", tx);
665 tokio::spawn(async move {
666 rx.recv().await.expect("a request");
667 });
668
669 let why = pair(
670 sym("callee-down"),
671 pair(host("quiet"), pair(sym("throw"), sym("noproc"))),
672 );
673 assert_eq!(
674 w.run().await.expect("no bug"),
675 vec![(id, ok(pair(sym("throw"), why)))]
676 );
677 }
678
679 #[tokio::test]
680 async fn an_object_task_calls_a_host_adapter() {
681 let mut t = crate::tower::Tower::load().expect("loads");
682 let forms = crate::floor::read::read(
683 "(task-loop self
684 (lambda h (st msg) (cons 'stop (call '(host echo) 'hi)))
685 0)",
686 )
687 .expect("reads");
688 let entry = t
689 .task(&crate::surface::desugar_body(&forms).expect("desugars"))
690 .expect("no fault");
691
692 let mut w = World::new(100_000);
693 let id = w.heap().spawn(entry);
694 w.heap().post(&addr(id), sym("go"));
695
696 let (tx, mut rx) = channel(8);
697 w.serve("echo", tx);
698 let back = w.port();
699 tokio::spawn(async move {
700 let msg = rx.recv().await.expect("a request");
701 let (from, rid, body) = request(&msg).expect("a request");
702 back.send(Event::Post(from, reply(rid, pair(sym("heard"), body))))
703 .await
704 .expect("open");
705 });
706
707 assert_eq!(
708 w.run().await.expect("no bug"),
709 vec![(id, ok(pair(sym("heard"), sym("hi"))))]
710 );
711 }
712
713 #[tokio::test]
714 async fn a_protected_region_can_still_suspend() {
715 let mut t = crate::tower::Tower::load().expect("loads");
716 let forms = crate::floor::read::read(
717 "(task-loop self
718 (lambda h (st msg)
719 (cons 'stop (attempt (lambda ()
720 (call '(host echo) 'hi)))))
721 0)",
722 )
723 .expect("reads");
724 let entry = t
725 .task(&crate::surface::desugar_body(&forms).expect("desugars"))
726 .expect("no fault");
727
728 let mut w = World::new(100_000);
729 let id = w.heap().spawn(entry);
730 w.heap().post(&addr(id), sym("go"));
731
732 let (tx, mut rx) = channel(8);
733 w.serve("echo", tx);
734 let back = w.port();
735 tokio::spawn(async move {
736 let msg = rx.recv().await.expect("a request");
737 let (from, rid, body) = request(&msg).expect("a request");
738 back.send(Event::Post(from, reply(rid, pair(sym("heard"), body))))
739 .await
740 .expect("open");
741 });
742
743 assert_eq!(
744 w.run().await.expect("no bug"),
745 vec![(id, ok(pair(sym("ok"), pair(sym("heard"), sym("hi")))))]
746 );
747 }
748
749 #[tokio::test]
750 async fn a_compiled_caller_calls_a_compiled_callee_rather_than_unfolding_it() {
751 let mut t = crate::tower::Tower::load().expect("loads");
752 let forms = crate::floor::read::read(
756 "(limit-turns self 100)
757 (define down (clambda down (n) (if (< n 1) 0 (down (- n 1)))))
758 (define go (clambda go (n) (down n)))
759 (go 5)",
760 )
761 .expect("reads");
762 let entry = t.script(&forms).expect("no fault");
763
764 let mut w = World::new(10_000);
765 let id = w.heap().spawn(entry);
766 w.heap().post(&addr(id), sym("run"));
767
768 assert_eq!(w.run().await.expect("no bug"), vec![(id, ok(Val::Num(0)))]);
769 }
770
771 #[tokio::test]
772 async fn a_file_runs_as_a_task_and_can_still_call() {
773 let mut t = crate::tower::Tower::load().expect("loads");
774 let forms = crate::floor::read::read(
775 "(define (twice x) (* 2 x))
776 (call '(host echo) (twice 21))",
777 )
778 .expect("reads");
779 let entry = t.script(&forms).expect("no fault");
780
781 let mut w = World::new(100_000);
782 let id = w.heap().spawn(entry);
783 w.heap().post(&addr(id), sym("run"));
784
785 let (tx, mut rx) = channel(8);
786 w.serve("echo", tx);
787 let back = w.port();
788 tokio::spawn(async move {
789 let msg = rx.recv().await.expect("a request");
790 let (from, rid, body) = request(&msg).expect("a request");
791 back.send(Event::Post(from, reply(rid, body)))
792 .await
793 .expect("open");
794 });
795
796 assert_eq!(w.run().await.expect("no bug"), vec![(id, ok(Val::Num(42)))]);
797 }
798
799 #[tokio::test]
800 async fn a_file_body_is_not_re_entered_by_a_message_that_arrives_during_it() {
801 use std::sync::atomic::{AtomicUsize, Ordering};
802 use std::sync::Arc;
803
804 let mut t = crate::tower::Tower::load().expect("loads");
805 let forms = crate::floor::read::read(
806 "(define a (call '(host echo) 1))
807 (define b (call '(host echo) 2))
808 (+ a b)",
809 )
810 .expect("reads");
811 let entry = t.script(&forms).expect("no fault");
812
813 let mut w = World::new(100_000);
814 let id = w.heap().spawn(entry);
815 w.heap().post(&addr(id), sym("run"));
816 w.heap().post(&addr(id), sym("poke"));
817
818 let (tx, mut rx) = channel(8);
819 w.serve("echo", tx);
820 let back = w.port();
821 let seen = Arc::new(AtomicUsize::new(0));
822 let count = seen.clone();
823 tokio::spawn(async move {
824 while let Some(msg) = rx.recv().await {
825 count.fetch_add(1, Ordering::SeqCst);
826 let (from, rid, body) = request(&msg).expect("a request");
827 back.send(Event::Post(from, reply(rid, body)))
828 .await
829 .expect("open");
830 }
831 });
832
833 assert_eq!(w.run().await.expect("no bug"), vec![(id, ok(Val::Num(3)))]);
834 assert_eq!(seen.load(Ordering::SeqCst), 2);
835 }
836
837 #[tokio::test]
838 async fn a_module_is_loaded_by_name_and_imported() {
839 assert_eq!(
840 loading(
841 "(import (load \"arith.naj\"))
842 (down (up 5))",
843 &[(
844 "arith.naj",
845 "(define base 10)
846 (list (cons 'up (lambda u (n) (+ n base)))
847 (cons 'down (lambda d (n) (- n base))))",
848 )],
849 )
850 .await,
851 ok(Val::Num(5))
852 );
853 }
854
855 #[tokio::test]
856 async fn a_module_may_load_another() {
857 assert_eq!(
858 loading(
859 "(import (load \"outer.naj\"))
860 (twice 21)",
861 &[
862 (
863 "outer.naj",
864 "(import (load \"inner.naj\"))
865 (list (cons 'twice (lambda t (n) (add n n))))",
866 ),
867 ("inner.naj", "(list (cons 'add (lambda a (x y) (+ x y))))"),
868 ],
869 )
870 .await,
871 ok(Val::Num(42))
872 );
873 }
874
875 #[tokio::test]
876 async fn a_module_cannot_see_the_scope_it_is_imported_into() {
877 let v = loading(
878 "(define secret 7)
879 (import (load \"peek.naj\"))
880 (get)",
881 &[("peek.naj", "(list (cons 'get (lambda g () secret)))")],
882 )
883 .await;
884 assert_eq!(v, pair(sym("throw"), pair(sym("unbound"), sym("secret"))));
885 }
886
887 #[tokio::test]
888 async fn a_call_answered_with_a_throw_raises_at_the_call_site() {
889 assert_eq!(
890 loading("(load \"missing.naj\")", &[]).await,
891 pair(sym("throw"), pair(sym("io"), Str::val("no such file")))
892 );
893 }
894
895 #[tokio::test]
896 async fn a_failed_call_is_catchable() {
897 assert_eq!(
898 loading(
899 "(attempt (lambda () (load \"missing.naj\")))",
900 &[("other.naj", "'()")],
901 )
902 .await,
903 ok(pair(
904 sym("throw"),
905 pair(sym("io"), Str::val("no such file"))
906 ))
907 );
908 }
909
910 const ROOT: &str = "/store";
911
912 #[tokio::test]
913 async fn a_module_is_resolved_through_the_lock_file() {
914 assert_eq!(
915 loading(
916 "(import (need 'arith))
917 (down (up 5))",
918 &[
919 (
920 "naj.lock",
921 "((narju-lock 1)
922 (modules
923 (arith (url \"https://forge/thorn/naj-arith\")
924 (rev \"a1b2c3\")
925 (path \"forge/thorn/naj-arith\")
926 (file \"arith.naj\"))))",
927 ),
928 (
929 "/store/forge/thorn/naj-arith/a1b2c3/arith.naj",
930 "(define base 10)
931 (list (cons 'up (lambda u (n) (+ n base)))
932 (cons 'down (lambda d (n) (- n base))))",
933 ),
934 ],
935 )
936 .await,
937 ok(Val::Num(5))
938 );
939 }
940
941 #[tokio::test]
942 async fn a_module_the_lock_does_not_carry_is_refused() {
943 assert_eq!(
944 loading(
945 "(attempt (lambda () (need 'missing)))",
946 &[("naj.lock", "((narju-lock 1) (modules))")],
947 )
948 .await,
949 ok(pair(
950 sym("throw"),
951 list(&[sym("no-such-module"), sym("missing")])
952 ))
953 );
954 }
955
956 #[tokio::test]
957 async fn a_locked_module_needs_another_through_its_own_lock() {
958 assert_eq!(
959 loading(
960 "(import (need 'outer))
961 (twice 21)",
962 &[
963 (
964 "naj.lock",
965 "((narju-lock 1)
966 (modules
967 (outer (url \"https://f/t/outer\") (rev \"r1\")
968 (path \"f/t/outer\") (file \"o.naj\"))))",
969 ),
970 (
971 "/store/f/t/outer/r1/naj.lock",
972 "((narju-lock 1)
973 (modules
974 (inner (url \"https://f/t/inner\") (rev \"r2\")
975 (path \"f/t/inner\") (file \"i.naj\"))))",
976 ),
977 (
978 "/store/f/t/outer/r1/o.naj",
979 "(import (need 'inner))
980 (list (cons 'twice (lambda t (n) (add n n))))",
981 ),
982 (
983 "/store/f/t/inner/r2/i.naj",
984 "(list (cons 'add (lambda a (x y) (+ x y))))",
985 ),
986 ],
987 )
988 .await,
989 ok(Val::Num(42))
990 );
991 }
992
993 #[tokio::test]
994 async fn two_modules_may_mean_different_things_by_one_name() {
995 assert_eq!(
996 loading(
997 "(import (need 'left))
998 (import (need 'right))
999 (list (l) (r))",
1000 &[
1001 (
1002 "naj.lock",
1003 "((narju-lock 1)
1004 (modules
1005 (left (url \"https://f/left\") (rev \"r1\")
1006 (path \"f/left\") (file \"m.naj\"))
1007 (right (url \"https://f/right\") (rev \"r1\")
1008 (path \"f/right\") (file \"m.naj\"))))",
1009 ),
1010 (
1011 "/store/f/left/r1/naj.lock",
1012 "((narju-lock 1)
1013 (modules
1014 (dep (url \"https://f/one\") (rev \"a\")
1015 (path \"f/one\") (file \"d.naj\"))))",
1016 ),
1017 (
1018 "/store/f/right/r1/naj.lock",
1019 "((narju-lock 1)
1020 (modules
1021 (dep (url \"https://f/two\") (rev \"b\")
1022 (path \"f/two\") (file \"d.naj\"))))",
1023 ),
1024 (
1025 "/store/f/left/r1/m.naj",
1026 "(import (need 'dep))
1027 (list (cons 'l (lambda l () (it))))",
1028 ),
1029 (
1030 "/store/f/right/r1/m.naj",
1031 "(import (need 'dep))
1032 (list (cons 'r (lambda r () (it))))",
1033 ),
1034 ("/store/f/one/a/d.naj", "(list (cons 'it (lambda i () 1)))",),
1035 ("/store/f/two/b/d.naj", "(list (cons 'it (lambda i () 2)))",),
1036 ],
1037 )
1038 .await,
1039 ok(list(&[Val::Num(1), Val::Num(2)]))
1040 );
1041 }
1042
1043 #[tokio::test]
1044 async fn a_module_may_be_more_than_one_file() {
1045 assert_eq!(
1046 loading(
1047 "(import (need 'lib))
1048 (top)",
1049 &[
1050 (
1051 "naj.lock",
1052 "((narju-lock 1)
1053 (modules
1054 (lib (url \"https://f/lib\") (rev \"r1\")
1055 (path \"f/lib\") (file \"src/m.naj\"))))",
1056 ),
1057 (
1058 "/store/f/lib/r1/naj.lock",
1059 "((narju-lock 1)
1060 (modules
1061 (dep (url \"https://f/dep\") (rev \"a\")
1062 (path \"f/dep\") (file \"d.naj\"))))",
1063 ),
1064 (
1065 "/store/f/lib/r1/src/m.naj",
1066 "(import (load \"src/helper.naj\"))
1067 (list (cons 'top (lambda t () (helped))))",
1068 ),
1069 (
1070 "/store/f/lib/r1/src/helper.naj",
1071 "(import (need 'dep))
1072 (list (cons 'helped (lambda h () (it))))",
1073 ),
1074 ("/store/f/dep/a/d.naj", "(list (cons 'it (lambda i () 7)))"),
1075 ],
1076 )
1077 .await,
1078 ok(Val::Num(7))
1079 );
1080 }
1081
1082 #[tokio::test]
1083 async fn a_module_with_no_lock_of_its_own_cannot_need() {
1084 assert_eq!(
1085 loading(
1086 "(attempt (lambda () (need 'solo)))",
1087 &[
1088 (
1089 "naj.lock",
1090 "((narju-lock 1)
1091 (modules
1092 (solo (url \"https://f/solo\") (rev \"r1\")
1093 (path \"f/solo\") (file \"s.naj\"))))",
1094 ),
1095 (
1096 "/store/f/solo/r1/s.naj",
1097 "(import (need 'anything))
1098 (list)",
1099 ),
1100 ],
1101 )
1102 .await,
1103 ok(pair(
1104 sym("throw"),
1105 pair(sym("io"), Str::val("no such file"))
1106 ))
1107 );
1108 }
1109
1110 #[tokio::test]
1111 async fn a_lock_from_a_later_format_is_refused() {
1112 assert_eq!(
1113 loading(
1114 "(attempt (lambda () (need 'x)))",
1115 &[("naj.lock", "((narju-lock 2) (modules))")],
1116 )
1117 .await,
1118 ok(pair(
1119 sym("throw"),
1120 list(&[sym("lock-version"), Val::Num(2)])
1121 ))
1122 );
1123 }
1124
1125 async fn loading(src: &str, files: &[(&str, &str)]) -> Val {
1126 let mut t = crate::tower::Tower::load().expect("loads");
1127 let forms = crate::floor::read::read(src).expect("reads");
1128 let entry = t.script(&forms).expect("no fault");
1129
1130 let mut w = World::new(10_000_000);
1131 let id = w.heap().spawn(entry);
1132 w.heap().post(&addr(id), sym("run"));
1133
1134 let (tx, mut rx) = channel(8);
1135 w.serve("files", tx);
1136 let back = w.port();
1137 let table: Vec<(String, String)> = files
1138 .iter()
1139 .map(|(p, s)| ((*p).to_string(), (*s).to_string()))
1140 .collect();
1141 tokio::spawn(async move {
1142 while let Some(msg) = rx.recv().await {
1143 let (from, rid, body) = request(&msg).expect("a request");
1144 let asked = list_elements(&body).expect("a list");
1145 let v = match asked.as_slice() {
1146 [verb] if *verb == sym("root") => Str::val(ROOT),
1147 [verb, path] if *verb == sym("read") => {
1148 let want = Str::of(path).expect("a path");
1149 match table.iter().find(|(p, _)| p == want) {
1150 Some((_, text)) => Str::val(text.clone()),
1151 None => pair(sym("throw"), pair(sym("io"), Str::val("no such file"))),
1152 }
1153 }
1154 _ => panic!("unexpected {body}"),
1155 };
1156 if back.send(Event::Post(from, reply(rid, v))).await.is_err() {
1157 return;
1158 }
1159 }
1160 });
1161
1162 let ended = w.run().await.expect("no bug");
1163 assert_eq!(ended.len(), 1);
1164 ended[0].1.clone()
1165 }
1166
1167 #[tokio::test]
1168 async fn the_prompt_carries_its_environment_from_line_to_line() {
1169 let said = console(
1170 &[
1171 "(define (double x) (* 2 x))",
1172 "(double 21)",
1173 "",
1174 "(car 3)",
1175 "\"hi\"",
1176 ],
1177 100_000,
1178 )
1179 .await;
1180 assert_eq!(
1181 said,
1182 ["'double", "42", "('error 'wrong-type . 'car)", "\"hi\""]
1183 );
1184 }
1185
1186 #[tokio::test]
1187 async fn the_prompt_takes_an_import_a_line_at_a_time() {
1188 let said = console(
1189 &["(import (list (cons 'z 9) (cons 'w 4)))", "(+ z w)"],
1190 100_000,
1191 )
1192 .await;
1193 assert_eq!(said, ["('z 'w)", "13"]);
1194 }
1195
1196 async fn console(lines: &[&str], budget: usize) -> Vec<String> {
1197 use std::sync::{Arc, Mutex};
1198
1199 let mut t = crate::tower::Tower::load().expect("loads");
1200 let entry = t.repl().expect("no fault");
1201
1202 let mut w = World::new(budget);
1203 let id = w.heap().spawn(entry);
1204 w.heap().post(&addr(id), sym("go"));
1205
1206 let (tx, mut rx) = channel(8);
1207 w.serve("stdin", tx.clone());
1208 w.serve("stdout", tx);
1209 let back = w.port();
1210 let said = Arc::new(Mutex::new(Vec::new()));
1211 let out = Arc::clone(&said);
1212 let mut script = lines.iter().map(|s| s.to_string()).collect::<Vec<_>>();
1213 script.reverse();
1214
1215 tokio::spawn(async move {
1216 while let Some(msg) = rx.recv().await {
1217 let (from, rid, body) = request(&msg).expect("a request");
1218 let v = match list_elements(&body).as_deref() {
1219 Some([verb, _]) if *verb == sym("read") => match script.pop() {
1220 Some(line) => Str::val(line),
1221 None => sym("eof"),
1222 },
1223 Some([verb, arg]) if *verb == sym("line") => {
1224 out.lock()
1225 .expect("no panic")
1226 .push(Str::of(arg).map_or_else(|| arg.to_string(), str::to_string));
1227 sym("ok")
1228 }
1229 _ => panic!("unexpected {body}"),
1230 };
1231 if back.send(Event::Post(from, reply(rid, v))).await.is_err() {
1232 return;
1233 }
1234 }
1235 });
1236
1237 assert_eq!(w.run().await.expect("no bug"), vec![(id, ok(sym("bye")))]);
1238 let held = said.lock().expect("no panic");
1239 held.clone()
1240 }
1241
1242 #[tokio::test]
1243 async fn a_call_to_a_peer_that_is_not_there_is_answered() {
1244 let mut w = World::new(1000);
1245 let id = w.heap().spawn(val("(lambda t (me)
1246 (begin (send '(host nowhere) (list 'req me 7 'anything))
1247 (receive)))"));
1248 assert_eq!(
1249 w.run().await.expect("no bug"),
1250 vec![(
1251 id,
1252 ok(list(&[
1253 sym("reply"),
1254 Val::Num(7),
1255 pair(sym("throw"), pair(sym("no-host"), sym("nowhere")))
1256 ]))
1257 )]
1258 );
1259 }
1260}