Compare commits

...

3 Commits

Author SHA1 Message Date
Scott Richmond
759fc63cae ugh. spin my wheels a lot. decide to start work on the receive special form 2025-06-27 18:48:27 -04:00
Scott Richmond
8923581eed add sleep, which was unexpectedly titchy! 2025-06-27 14:27:42 -04:00
Scott Richmond
90505f89fe make some new process functions 2025-06-27 12:27:54 -04:00
7 changed files with 690 additions and 869 deletions

View File

@ -414,6 +414,7 @@ fn print! {
"Sends a text representation of Ludus values to the console."
(...args) -> {
let line = do args > map (string, _) > join (_, " ")
base :print! (args)
update! (console, append (_, line))
:ok
}
@ -1241,14 +1242,28 @@ fn link! {
"Creates a 'hard link' between two processes: if either one dies, they both do."
(pid1 as :keyword, pid2 as :keyword) -> link! (pid1, pid2, :report)
(pid1 as :keyword, pid2 as :keyword, :report) -> base :process (:link_report, pid1, pid2)
(pid1 as :keyword, pid2 as :keyword, :panic) -> base :process (:link_panic, pid1, pid2)
(pid1 as :keyword, pid2 as :keyword, :enforce) -> base :process (:link_enforce, pid1, pid2)
}
fn msgs () -> base :process (:msgs)
fn msgs {
"Returns the entire contents of the current process as a list. Leaves all messages in the process mailbox."
() -> base :process (:msgs)
}
fn flush! () -> base :process (:flush)
fn flush! {
"Clears the current process's mailbox."
() -> base :process (:flush)
}
fn sleep! (ms as :number) -> base :process (:sleep, ms)
fn flush_i! {
"Flushes the message at the indicated index in the current process's mailbox."
(i as :number) -> base :process (:flush_i, i)
}
fn sleep! {
"Puts the current process to sleep for at least the specified number of milliseconds."
(ms as :number) -> base :process (:sleep, ms)
}
#{
self
@ -1256,7 +1271,11 @@ fn sleep! (ms as :number) -> base :process (:sleep, ms)
msgs
spawn!
yield!
sleep!
alive?
flush!
flush_i!
link!
abs

View File

@ -121,7 +121,7 @@ A few thoughts:
* Function calls should be different from tuple pattern matching. Tuples are currently (and maybe forever?) allocated on the heap. Function calls should *not* have to pass through the heap. The good news: `Arguments` is already a different AST node type than `Tuple`; we'll want an `ArgumentsPattern` pattern node type that's different from (and thus compiled differently than) `TuplePattern`. They'll be similar--the matching logic is the same, after all--but the arguments will be on the stack already, and won't need to be unpacked in the same way.
- One difficulty will be matching against different arities? But actually, we should compile these arities as different functions.
- Given splats, can we actually compile functions into different arities? Consider the following:
```
```ludus
fn foo {
(x) -> & arity 1
(y, z) -> & arity 2
@ -161,7 +161,7 @@ The `loop` compilation is _almost_ the same as a function body. That said, the t
A few possibilities:
* Probably the best option: enforce a new requirement that splat patterns in function clauses *must* be longer than any explicit arity of the function. So, taking the above:
```
```ludus
fn foo {
(x) -> & arity 1
(y, z) -> & arity 2
@ -171,7 +171,7 @@ A few possibilities:
```
This would give you a validation error that splats must be longer than any other arity.
Similarly, we could enforce this:
```
```ludus
fn foo {
(x) -> & arity 1
(x, y) -> & arity 2
@ -340,7 +340,7 @@ And then: quality of life improvements:
### Bugs discovered while trying to compile prelude
#### 2025-06-20
Consider the following code:
```
```ludus
fn one {
(x as :number) -> {
fn two () -> :number
@ -400,7 +400,7 @@ What we need to have happen is that if a function is closing over values _inside
I think I need to consult Uncle Bob Nystrom to get a sense of what to do here.
***
So I found the minimal test case:
```
```ludus
let foo = {
let thing = :thing
let bar = :bar
@ -445,7 +445,7 @@ To the best of my ability to tell, `if` has proper stack behaviour.
So the question is what's happening in the interaction between the `jump_if_false` instruction and `recur`.
To wit, the following code works just fine:
```
```ludus
fn not {
(false) -> true
(nil) -> true
@ -463,7 +463,7 @@ loop ([1, 2, 3]) with {
```
But the following code does not:
```
```ludus
let test = 2
loop ([1, 2, 3]) with {
([]) -> false
@ -522,11 +522,7 @@ SOLUTION: test to see if the function has been forward-declared, and if it has,
NEW PROBLEM: a lot of instructions in the VM don't properly offset from the call frame's stack base, which leads to weirdness when doing things inside function calls.
NEW SOLUTION: create a function that does the offset properly, and replace everywhere we directly access the stack.
<<<<<<< HEAD
<<<<<<< Updated upstream
This is the thing I am about to do
||||||| Stash base
This is the thing I am about to do.
### I think the interpreter, uh, works?
#### 2025-06-24
@ -832,12 +828,12 @@ I've implemented what I decribe above. It works! I'm low-key astonished.
It perfectly handles an infinitely recurring process! What the fuck.
Anyway, things left to do:
* [ ] `receive` forms are the big one: they require threading through the whole interpreter
* [ ] implement the missing process functions at the end of prelude
* [x] implement the missing process functions at the end of prelude
* [ ] research how Elixir/Erlang's abstractions over processes work (I'm especially interested in how to make synchronous-looking calls); take a look especially at https://medium.com/qixxit-development/build-your-own-genserver-in-49-lines-of-code-1a9db07b6f13
* [ ] write some examples just using these simple tools (not even GenServer, etc.) to see how they work, and to start building curriculum
* [ ] develop a design for how to deal with asynchronous io with js
```
```ludus
fn agent/get (pid) -> {
send (pid, (:get, self ()))
receive {
@ -872,5 +868,199 @@ Two things that pop out to me:
* The way this works is just to yield immediately. This actually makes a lot of sense. If we put them next to one another, there's no risk that there'll be backlogged `(:response, x)` messages in the mbx, right? But that makes me a little queasy.
* The way `gen_server` works is pretty deep inversion of control; you effectively write callbacks for the `gen_server` to call. I'm not sure that's how we want to do things in Ludus; it's a handy pattern, and easy. But it's not simple. But also worth investigating. In any event, it's the foundation of all the other process patterns Elixir has developed. I need an intuiation around it.
### Rethinking reception
#### 2025-06-27
So one thing that's stuck with me is that in Elixir, `receive` isn't a special form: it's a function that takes a block.
It may be a macro, but it's still mostly normalish, and doesn't invovle compiler shenanigans.
So, this is what I want to write:
```ludus
fn receiver () -> receive {
:foo -> :bar
:bar -> :baz
:baz -> :quux
}
```
There's a way to model this without the magic of receive.
Imagine instead a function that just receives a message and matches against it:
```ludus
fn receive_msg (msg) -> match msg with {
:foo -> :bar
:bar -> :baz
:baz -> :quux
}
```
But if there's no matching message clause, we get a panic.
And panics stop the world.
Meanwhile, we need to know whether the message matched.
So this desugaring goes a little further:
```ludus
fn receive_msg (msg) -> match msg with {
:foo -> :bar
:bar -> :baz
:baz -> :quux
_ -> :does_not_understand
}
```
This way we avoid a panic when there's no matching message.
There's an easy wrapping function which looks like this:
```ludus
fn receive (receiver) -> {
let my_msgs = msgs ()
loop (my_msgs, 0) with {
([], _) -> yield! ()
(xs, i) -> match receiver(first (xs)) with {
:does_not_understand -> recur (rest (xs), inc (i))
x -> {
flush_n! (i)
x
}
}
}
}
receive (receive_msg)
```
There's a thing I both like and don't like about this.
The fact that we use a magic keyword, `:does_not_understand`, means it's actually easy to hook into the behaviour of not understanding.
I don't know if we should panic on a process receiving a message it doesn't understand.
Maybe we do that for now?
Crash early and often; thanks Erlang.
And so we do have to worry about the last clause.
At an implementation level, it's worth noting that we're not optimizing for scanning through the messages--we'll scan through any messages we don't understand every time we call `receive`.
That's probably fine!
So now the thing, without sugar, is just:
```ludus
fn agent (x) -> receive (fn (msg) {
(:get, pid) -> {
send (pid, (:response, x))
agent (x)
}
(:set, y) -> agent(y)
(:update, f) -> agent (f (x))
_ -> :does_not_understand
})
```
So I don't need any sugar to make `receive` work?
And it doesn't even need to hook into the vm?
What?
#### some time later
It turns out I do.
The problem is that the `flush_i` instruction never gets called from Ludus in a thing like an agent, because of the recursive call.
So the flushing would need to happen in the receiver.
A few things that are wrong right now:
* [ ] `loop`/`recur` is still giving me a very headache. It breaks stack discipline to avoid packing tuples into a heap-allocated vec. There may be a way to fix this in the current compiler scheme around how I do and don't handle arguments--make recur stupider and don't bother loading anything. A different solution would be to desugar loop into an anonymous function call. A final solution would be just to use a heap-allocated tuple.
Minimal failing case:
```ludus
match (nil) with {
(x) -> match x with {
(y) -> recur (y)
}
}
```
* [ ] The amount of sugar needed to get to `receive` without making a special form is too great.
In particular, function arities need to be changed, flushes need to be inserted, anonymous lambdas need to be created (which can't be multi-clause), etc.
* [ ] There was another bug that I was going to write down and fix, but I forgot what it was. Something with processes.
* [ ] Also: the `butlast` bug is still outstanding: `base :slice` causes a panic in that function, but not the call to the Ludus function. Still have to investigate that one.
* [ ] In testing this, it's looking like `match` is misbehaving; none of the matches that *should* happen in my fully sugarless `receive` testing are matching how they ought.
I haven't got to a minimal case, but here's what's not working:
```ludus
fn receive (receiver) -> {
fn looper {
([], _) -> yield! ()
(xs, i) -> {
print!("looping through messages:", xs)
match receiver (first (xs), i) with {
:does_not_understand -> looper (rest (xs), inc (i))
x -> x
}}
}
print! ("receiving in", self (), "with messages", msgs())
looper (msgs (), 0)
}
fn agent (x) -> receive (fn (msg, i) -> {
print!("received msg in agent: ", msg)
match msg with {
(:get, pid) -> {
flush_i! (i)
print!("getted from {pid}")
send (pid, (:response, x))
agent (x)
}
(:set, y) -> {flush_i!(i); print!("setted! {y}"); agent (y)}
(:update, f) -> {flush_i!(i);print!("updated: {f}"); agent (f (x))}
y -> {print!("no agent reception match!!!! {y}");:does_not_understand}
}
})
fn agent/get (pid) -> {
send (pid, (:get, self ()))
yield! ()
receive (fn (msg, i) -> match msg with {
(:response, x) -> {flush_i! (i); x}
})
}
fn agent/set (pid, val) -> send (pid, (:set, val))
fn agent/update (pid, f) -> send (pid, (:update, f))
let counter = spawn! (fn () -> agent (0))
agent/set (counter, 12)
agent/update (counter, inc)
agent/update (counter, mult(_, 3))
agent/get (counter)
```
_I haven't been able to recreate this, and the code above is subtle enough that the Ludus may be behaving as expected; the following works as expected:_
```ludus
fn receive (receiver) -> {
print! ("receiving in", self (), "with msgs", msgs())
if empty? (msgs ())
then {yield! (); receive (receiver)}
else do msgs () > first > receiver
}
fn foo? (val) -> receive (fn (msg) -> match report!("scrutinee is", msg) with {
(:report) -> {
print! ("LUDUS SAYS ==> value is {val}")
flush! ()
foo? (val)
}
(:set, x) -> {
print! ("LUDUS SAYS ==> foo! was {val}, now is {x}")
flush! ()
foo? (x)
}
(:get, pid) -> {
print! ("LUDUS SAYS ==> value is {val}")
send (pid, (:response, val))
flush! ()
foo? (val)
}
x -> print! ("LUDUS SAYS ==> no match, got {x}")
})
let foo = spawn! (fn () -> foo? (42))
print! (foo)
send (foo, (:set, 23))
yield! ()
send (foo, (:get, self ()))
yield! ()
fn id (x) -> x
receive(id)
```

View File

@ -1,16 +1,35 @@
fn simple_reporter () -> {
print! (self (), msgs ())
fn receive (receiver) -> {
print! ("receiving in", self (), "with msgs", msgs())
if empty? (msgs ())
then {yield! (); receive (receiver)}
else do msgs () > first > receiver
}
fn hanger () -> hanger ()
let foo = spawn! (hanger)
let bar = spawn! (simple_reporter)
let baz = spawn! (fn () -> panic! :oops)
send (foo, [:foo, :bar, :baz])
send (bar, (1, 2, 3))
fn foo? (val) -> receive (fn (msg) -> match report!("scrutinee is", msg) with {
(:report) -> {
print! ("LUDUS SAYS ==> value is {val}")
flush! ()
foo? (val)
}
(:set, x) -> {
print! ("LUDUS SAYS ==> foo! was {val}, now is {x}")
flush! ()
foo? (x)
}
(:get, pid) -> {
print! ("LUDUS SAYS ==> value is {val}")
send (pid, (:response, val))
flush! ()
foo? (val)
}
x -> print! ("LUDUS SAYS ==> no match, got {x}")
})
let foo = spawn! (fn () -> foo? (42))
print! (foo)
send (foo, (:set, 23))
yield! ()
:done
send (foo, (:get, self ()))
yield! ()
fn id (x) -> x
receive(id)

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@ use imbl::HashMap;
use wasm_bindgen::prelude::*;
const DEBUG_SCRIPT_COMPILE: bool = false;
const DEBUG_SCRIPT_RUN: bool = true;
const DEBUG_SCRIPT_RUN: bool = false;
const DEBUG_PRELUDE_COMPILE: bool = false;
const DEBUG_PRELUDE_RUN: bool = false;
@ -184,8 +184,8 @@ pub fn ludus(src: String) -> String {
// TODO: use serde_json to make this more robust?
format!(
"{{\"result\":\"{output}\",\"io\":{{\"stdout\":{{\"proto\":[\"text-stream\",\"0.1.0\"],\"data\":\"{console}\"}},\"turtle\":{{\"proto\":[\"turtle-graphics\",\"0.1.0\"],\"data\":{commands}}}}}}}"
)
"{{\"result\":\"{output}\",\"io\":{{\"stdout\":{{\"proto\":[\"text-stream\",\"0.1.0\"],\"data\":\"{console}\"}},\"turtle\":{{\"proto\":[\"turtle-graphics\",\"0.1.0\"],\"data\":{commands}}}}}}}"
)
}
pub fn fmt(src: &'static str) -> Result<String, String> {

104
src/vm.rs
View File

@ -13,7 +13,7 @@ use std::fmt;
use std::mem::swap;
use std::rc::Rc;
const MAX_REDUCTIONS: usize = 100;
const MAX_REDUCTIONS: usize = 1000;
#[derive(Debug, Clone, PartialEq)]
pub enum Panic {
@ -91,7 +91,7 @@ pub struct Creature {
pub result: Option<Result<Value, Panic>>,
debug: bool,
last_code: usize,
pub id: &'static str,
pub pid: &'static str,
pub mbx: VecDeque<Value>,
pub reductions: usize,
pub zoo: Rc<RefCell<Zoo>>,
@ -100,7 +100,7 @@ pub struct Creature {
impl std::fmt::Display for Creature {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Creature. {} @{}", self.id, self.ip)
write!(f, "Creature. {} @{}", self.pid, self.ip)
}
}
@ -136,7 +136,7 @@ impl Creature {
result: None,
debug,
last_code: 0,
id: "",
pid: "",
zoo,
mbx: VecDeque::new(),
reductions: 0,
@ -197,7 +197,7 @@ impl Creature {
.join("/");
println!(
"{:04}: [{inner}] ({register}) {} {{{mbx}}}",
self.last_code, self.id
self.last_code, self.pid
);
}
@ -230,11 +230,13 @@ impl Creature {
pub fn panic(&mut self, msg: &'static str) {
let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack());
println!("process {} panicked!\n{msg}", self.pid);
self.result = Some(Err(Panic::String(msg)));
}
pub fn panic_with(&mut self, msg: String) {
let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack());
println!("process {} panicked!\n{msg}", self.pid);
self.result = Some(Err(Panic::String(msg)));
}
@ -266,22 +268,35 @@ impl Creature {
}
fn handle_msg(&mut self, args: Vec<Value>) {
println!("message received! {}", args[0]);
println!("message received by {}: {}", self.pid, args[0]);
let Value::Keyword(msg) = args.first().unwrap() else {
return self.panic("malformed message to Process");
};
match *msg {
"self" => self.push(Value::Keyword(self.id)),
"self" => self.push(Value::Keyword(self.pid)),
"msgs" => {
let msgs = self.mbx.iter().cloned().collect::<Vec<_>>();
let msgs = Vector::from(msgs);
println!(
"delivering messages: {}",
msgs.iter()
.map(|x| x.show())
.collect::<Vec<_>>()
.join(" | ")
);
self.push(Value::List(Box::new(msgs)));
}
"send" => {
let Value::Keyword(pid) = args[1] else {
return self.panic("malformed pid");
};
if self.id == pid {
println!(
"sending msg from {} to {} of {}",
self.pid,
pid,
args[2].show()
);
if self.pid == pid {
self.mbx.push_back(args[2].clone());
} else {
self.zoo
@ -289,30 +304,92 @@ impl Creature {
.borrow_mut()
.send_msg(pid, args[2].clone());
}
self.push(Value::Nil);
self.push(Value::Keyword("ok"));
}
"spawn" => {
println!("spawning new process!");
let f = args[1].clone();
let proc = Creature::spawn(f, self.zoo.clone(), self.debug);
let id = self.zoo.as_ref().borrow_mut().put(proc);
println!("spawning new process {id}!");
self.push(Value::Keyword(id));
}
"yield" => {
self.r#yield = true;
self.push(Value::Nil);
println!("yielding from {}", self.pid);
self.push(Value::Keyword("ok"));
}
"alive" => {
let Value::Keyword(pid) = args[1].clone() else {
unreachable!();
};
let is_alive = self.zoo.as_ref().borrow().is_alive(pid);
if is_alive {
self.push(Value::True)
} else {
self.push(Value::False)
}
}
"link" => todo!(),
"flush" => {
self.mbx = VecDeque::new();
println!("flushing messages in {}", self.pid);
self.push(Value::Keyword("ok"));
}
"sleep" => {
println!("sleeping {} for {}", self.pid, args[1]);
let Value::Number(ms) = args[1] else {
unreachable!()
};
self.zoo.as_ref().borrow_mut().sleep(self.pid, ms as usize);
self.r#yield = true;
self.push(Value::Keyword("ok"));
}
// "flush_i" => {
// let Value::Number(n) = args[1] else {
// unreachable!()
// };
// println!("flushing message at {n}");
// self.mbx.remove(n as usize);
// println!(
// "mailbox is now: {}",
// self.mbx
// .iter()
// .map(|msg| msg.to_string())
// .collect::<Vec<_>>()
// .join(" | ")
// );
// self.push(Value::Keyword("ok"));
// }
msg => panic!("Process does not understand message: {msg}"),
}
}
pub fn interpret(&mut self) {
println!("starting process {}", self.pid);
println!(
"mbx: {}",
self.mbx
.iter()
.map(|x| x.show())
.collect::<Vec<_>>()
.join(" | ")
);
loop {
if self.at_end() {
self.result = Some(Ok(self.stack.pop().unwrap()));
let result = self.stack.pop().unwrap();
println!("process {} has returned {result}", self.pid);
self.result = Some(Ok(result));
return;
}
if self.reductions >= MAX_REDUCTIONS || self.r#yield {
if self.r#yield {
println!("process {} has explicitly yielded", self.pid);
return;
}
if self.reductions >= MAX_REDUCTIONS {
println!(
"process {} is yielding after {MAX_REDUCTIONS} reductions",
self.pid
);
return;
}
let code = self.read();
@ -1118,6 +1195,7 @@ impl Creature {
self.push(value);
}
None => {
println!("process {} has returned with {}", self.pid, value);
self.result = Some(Ok(value));
return;
}

View File

@ -4,7 +4,9 @@ use crate::vm::{Creature, Panic};
use ran::ran_u8;
use std::cell::RefCell;
use std::collections::HashMap;
use std::mem::swap;
use std::rc::Rc;
use std::time::{Duration, Instant};
const ANIMALS: [&str; 24] = [
"tortoise",
@ -67,7 +69,9 @@ pub struct Zoo {
ids: HashMap<&'static str, usize>,
dead: Vec<&'static str>,
kill_list: Vec<&'static str>,
active: usize,
sleeping: HashMap<&'static str, (Instant, Duration)>,
active_idx: usize,
active_id: &'static str,
}
impl Zoo {
@ -78,7 +82,9 @@ impl Zoo {
ids: HashMap::new(),
kill_list: vec![],
dead: vec![],
active: 0,
sleeping: HashMap::new(),
active_idx: 0,
active_id: "",
}
}
@ -100,7 +106,7 @@ impl Zoo {
if self.empty.is_empty() {
let id = self.new_id();
let idx = self.procs.len();
proc.id = id;
proc.pid = id;
self.procs.push(Status::Nested(proc));
self.ids.insert(id, idx);
id
@ -108,7 +114,7 @@ impl Zoo {
let idx = self.empty.pop().unwrap();
let rand = ran_u8() as usize % 24;
let id = format!("{}_{idx}", ANIMALS[rand]).leak();
proc.id = id;
proc.pid = id;
self.ids.insert(id, idx);
self.procs[idx] = Status::Nested(proc);
id
@ -119,75 +125,101 @@ impl Zoo {
self.kill_list.push(id);
}
pub fn sleep(&mut self, id: &'static str, ms: usize) {
self.sleeping
.insert(id, (Instant::now(), Duration::from_millis(ms as u64)));
}
pub fn is_alive(&self, id: &'static str) -> bool {
if self.kill_list.contains(&id) {
return false;
}
let idx = self.ids.get(id);
match idx {
Some(idx) => match self.procs.get(*idx) {
Some(proc) => match proc {
Status::Empty => false,
Status::Borrowed => true,
Status::Nested(_) => true,
},
None => false,
},
None => false,
}
}
pub fn clean_up(&mut self) {
while let Some(id) = self.kill_list.pop() {
if let Some(idx) = self.ids.get(id) {
println!("buried process {id}");
self.procs[*idx] = Status::Empty;
self.empty.push(*idx);
self.ids.remove(id);
self.dead.push(id);
}
}
self.sleeping
.retain(|_, (instant, duration)| instant.elapsed() < *duration);
println!(
"currently sleeping processes: {}",
self.sleeping
.keys()
.map(|id| id.to_string())
.collect::<Vec<_>>()
.join(" | ")
);
}
pub fn catch(&mut self, id: &'static str) -> Creature {
if let Some(idx) = self.ids.get(id) {
let mut proc = Status::Borrowed;
std::mem::swap(&mut proc, &mut self.procs[*idx]);
swap(&mut proc, &mut self.procs[*idx]);
let Status::Nested(proc) = proc else {
unreachable!("tried to borrow an empty or already-borrowed process");
unreachable!("tried to borrow an empty or already-borrowed process {id}");
};
proc
} else {
unreachable!("tried to borrow a non-existent process");
unreachable!("tried to borrow a non-existent process {id}");
}
}
pub fn release(&mut self, proc: Creature) {
let id = proc.id;
let id = proc.pid;
if let Some(idx) = self.ids.get(id) {
let mut proc = Status::Nested(proc);
std::mem::swap(&mut proc, &mut self.procs[*idx]);
} else {
unreachable!("tried to return a process the world doesn't know about");
swap(&mut proc, &mut self.procs[*idx]);
}
// Removed because, well, we shouldn't have creatures we don't know about
// And since zoo.next now cleans (and thus kills) before the world releases its active process
// We'll die if we execute this check
// else {
// unreachable!("tried to return a process the world doesn't know about");
// }
}
pub fn is_available(&self) -> bool {
match &self.procs[self.active_idx] {
Status::Empty => false,
Status::Borrowed => false,
Status::Nested(proc) => !self.sleeping.contains_key(proc.pid),
}
}
pub fn next(&mut self, curr_id: &'static str) -> &'static str {
println!("getting next process from {curr_id}");
println!(
"current procs in zoo:\n{}",
self.procs
.iter()
.map(|proc| proc.to_string())
.collect::<Vec<_>>()
.join("//")
);
println!("ids: {:?}", self.ids);
let idx = self.ids.get(curr_id).unwrap();
println!("current idx: {idx}");
if *idx != self.active {
panic!(
"tried to get next creature after {curr_id} while {} is active",
self.active
pub fn next(&mut self) -> &'static str {
self.active_idx = (self.active_idx + 1) % self.procs.len();
while !self.is_available() {
self.clean_up();
self.active_idx = (self.active_idx + 1) % self.procs.len();
println!(
"testing process availability: {}",
self.procs[self.active_idx]
);
}
self.active = (self.active + 1) % self.procs.len();
println!("active idx is now: {}", self.active);
while self.procs[self.active] == Status::Empty {
let new_active_idx = (self.active + 1) % self.procs.len();
println!("new active idx: {new_active_idx}");
println!("new active process is: {}", self.procs[new_active_idx]);
self.active = (self.active + 1) % self.procs.len();
}
println!("found next proc: {}", &self.procs[self.active]);
match &self.procs[self.active] {
Status::Empty => unreachable!(),
Status::Borrowed => panic!(
"encountered unexpectedly borrowed process at idx {}",
self.active
),
Status::Nested(proc) => proc.id,
match &self.procs[self.active_idx] {
Status::Empty | Status::Borrowed => unreachable!(),
Status::Nested(proc) => proc.pid,
}
}
@ -221,73 +253,25 @@ impl World {
}
}
// pub fn spawn(&mut self, proc: Creature) -> Value {
// let id = self.zoo.put(proc);
// Value::Keyword(id)
// }
// pub fn send_msg(&mut self, id: &'static str, msg: Value) {
// let mut proc = self.zoo.catch(id);
// proc.receive(msg);
// self.zoo.release(proc);
// }
fn next(&mut self) {
let id = self
.zoo
.as_ref()
.borrow_mut()
.next(self.active.as_ref().unwrap().id);
println!("next id is {id}");
let mut active = None;
std::mem::swap(&mut active, &mut self.active);
let mut holding_pen = self.zoo.as_ref().borrow_mut().catch(id);
let mut active = active.unwrap();
std::mem::swap(&mut active, &mut holding_pen);
println!("now in the holding pen: {}", holding_pen.id);
holding_pen.reset_reductions();
self.zoo.as_ref().borrow_mut().release(holding_pen);
let mut active = Some(active);
std::mem::swap(&mut active, &mut self.active);
swap(&mut active, &mut self.active);
let mut zoo = self.zoo.as_ref().borrow_mut();
zoo.release(active.unwrap());
let new_active_id = zoo.next();
let mut new_active_proc = zoo.catch(new_active_id);
new_active_proc.reset_reductions();
let mut new_active_opt = Some(new_active_proc);
swap(&mut new_active_opt, &mut self.active);
}
// pub fn sleep(&mut self, id: &'static str) {
// // check if the id is the actually active process
// if self.active.id != id {
// panic!("attempted to sleep a process from outside that process: active = {}; to sleep: = {id}", self.active.id);
// }
// self.next(id);
// }
// pub fn panic(&mut self, id: &'static str, panic: Panic) {
// // TODO: devise some way of linking processes (study the BEAM on this)
// // check if the id is active
// if self.active.id != id {
// panic!("attempted to panic from a process from outside that process: active = {}; panicking = {id}; panic = {panic}", self.active.id);
// }
// // check if the process is `main`, and crash the program if it is
// if self.main == id {
// self.result = self.active.result.clone();
// }
// // kill the process
// self.zoo.kill(id);
// self.next(id);
// }
// pub fn complete(&mut self) {
// if self.main == self.active.id {
// self.result = self.active.result.clone();
// }
// self.next(id);
// }
pub fn activate_main(&mut self) {
let main = self.zoo.as_ref().borrow_mut().catch(self.main);
self.active = Some(main);
}
pub fn active_id(&mut self) -> &'static str {
self.active.as_ref().unwrap().id
self.active.as_ref().unwrap().pid
}
pub fn kill_active(&mut self) {
@ -299,16 +283,15 @@ impl World {
&self.active.as_ref().unwrap().result
}
pub fn clean_up(&mut self) {
self.zoo.as_ref().borrow_mut().clean_up()
}
pub fn run(&mut self) {
self.activate_main();
loop {
println!("entering world loop");
println!(
"entering world loop; active process is {}",
self.active_id()
);
self.active.as_mut().unwrap().interpret();
println!("interpreted loop");
println!("yielded from {}", self.active_id());
match self.active_result() {
None => (),
Some(_) => {
@ -316,12 +299,17 @@ impl World {
self.result = self.active_result().clone();
return;
}
println!(
"process {} died with {:?}",
self.active_id(),
self.active_result().clone()
);
self.kill_active();
}
}
println!("getting next process");
self.next();
self.clean_up();
// self.clean_up();
}
}