Compare commits

...

5 Commits

Author SHA1 Message Date
Scott Richmond
00ebac17ce some notes for tomorrow's work 2025-06-26 23:28:17 -04:00
Scott Richmond
888f5b62da send messages, motherfucker! 2025-06-26 20:30:40 -04:00
Scott Richmond
c144702b98 add a process value 2025-06-26 17:17:41 -04:00
Scott Richmond
801e5bcc01 devise a way of communicating between ludus and processes 2025-06-26 17:15:00 -04:00
Scott Richmond
b35657e698 refactor to have a world run a process 2025-06-26 16:11:35 -04:00
9 changed files with 525 additions and 96 deletions

View File

@ -1211,7 +1211,54 @@ fn penwidth {
box state = nil
fn self {
"Returns the current process's pid, as a keyword."
() -> base :process (:self)
}
fn send {
"Sends a message to the specified process."
(pid as :keyword, msg) -> base :process (:send, pid, msg)
}
fn spawn! {
"Spawns a new process running the function passed in."
(f as :fn) -> base :process (:spawn, f)
}
fn yield! {
"Forces a process to yield."
() -> base :process (:yield)
}
& TODO: implement these in the VM
fn alive? {
"Tells if the passed keyword is the id for a live process."
(pid as :keyword) -> base :process (:alive, pid)
}
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)
}
fn msgs () -> base :process (:msgs)
fn flush! () -> base :process (:flush)
fn sleep! (ms as :number) -> base :process (:sleep, ms)
#{
self
send
msgs
spawn!
yield!
alive?
link!
abs
abs
add

View File

@ -774,4 +774,103 @@ See https://github.com/vitejs/vite/discussions/12826.
Web, in some ways, is even more straightforward.
It produces an ESM that just works in the browser.
And
Put the rest of my thinking up in an issue on alea.
### Actor/Model
#### 2025-06-26
Okay, even though we're not fully hooked up with wasm yet, I've started working on the actor model.
I believe I have things broadly right.
The thing that I'm thinking about right now is how to connect ludus to the world and to processes.
It's not clear how to expose that plumbing.
The most obvious way to do this would be to make all the process stuff special forms.
But first, before I get there, this is what Elixir does.
* `receive` is a special form. It is strictly equivalent to `match`, but with the caveats that (a) if there are no messages to receive, the process yields, and (b) if there's no match for the first message, it matches against the second, and so on (keeping messages in the mailbox), and (c) if no messages match, the process yields as well.
Everything else is functions:
* `spawn/1` takes a function to execute
* `send/3` takes a pid and a message (the third argument is for options, which we won't have in Ludus), returning a pid
* `self/0` returns the current processes' pid
* `exit/2` takes a pid and a message, and kills the process
* `sleep/1` takes a duration in milliseconds and sleeps the process for a time. This isn't strictly speaking necessary, but it is a nice-to-have.
In Ludus, `receive` will need to be a special form.
We could make `self` a reserved word, emitting the instruction to get the current pid.
`spawn` I like as a special form: whatever the expression that comes after spawn is just deferred into the new process.
So in place of Elixir's `spawn(fn -> :something end)` in Ludus, we'd have `spawn :something`.
`send`, `exit`, and `sleep` are a little more interesting (difficult).
Each could be like `and` and `or`: special forms that _look_ like functions, but really aren't--implemented in the compiler.
Alternately, I could attempt to find a way to expose the vm to base.
That seems, frankly, rather more daunting in Rust.
But we could also just build out syntax?
For example, I had proposed the following desugaring:
`foo ::bar (baz)` -> `send (foo, (:bar, baz))`
I don't mind the sugar, but it feels like it's actually really important conceptually for Ludus that everything really is functions.
(Then again, the sugar feels weird, because `foo` is just a name bound to a keyword, so it's possible to do: `:foo ::bar (baz)`, which makes sense and is weird.).
The only disadvantage with making `send` a special form is that you can't pass it as a higher-order function.
`and` and `or` must be special forms, and don't make sense as higher-order functions because they have a different evaluation order than regular functions.
But that's not true of all the things here.
So how do we connect Ludus function calls to handles in the rust interpreter?
In place of base functions, we need something like messages to the process, that aren't mailbox processes but something else.
So, like, a special sort of Ludus value, only available in base, that represents the VM.
Call it `base :process`.
We call it like a function, but the VM instead responds to the various prompts.
Thus we can use it to communicate with the process.
`receive` will be tricky.
But everything else? Seems pretty straightforward.
#### Some time later...
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
* [ ] 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
```
fn agent/get (pid) -> {
send (pid, (:get, self ()))
receive {
(:response, value) -> value
}
}
fn agent/store (pid, x) -> {
send (pid, (:store, x))
:ok
}
fn agent/update (pix, f) -> {
send (pid, (:update, f))
}
fn agent (state) -> receive {
(:get, pid) -> {
send (pid, (:response, state))
agent (state)
}
(:update, pid, f) -> {
agent (f (state))
}
(:store, pid, x) -> {
agent (x)
}
}
```
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.

View File

@ -1,4 +1,16 @@
repeat 1 {
fd! (100)
rt! (0.25)
fn simple_reporter () -> {
print! (self (), msgs ())
}
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))
yield! ()
:done

View File

@ -382,6 +382,7 @@ pub fn r#type(x: &Value) -> Value {
Value::Box(_) => Value::Keyword("box"),
Value::BaseFn(_) => Value::Keyword("fn"),
Value::Partial(_) => Value::Keyword("fn"),
Value::Process => Value::Keyword("process"),
Value::Nothing => unreachable!(),
}
}
@ -608,6 +609,7 @@ pub fn make_base() -> Value {
("number", Value::BaseFn(BaseFn::Unary("number", number))),
("pi", Value::Number(std::f64::consts::PI)),
("print!", Value::BaseFn(BaseFn::Unary("print!", print))),
("process", Value::Process),
("random", Value::BaseFn(BaseFn::Nullary("random", random))),
("range", Value::BaseFn(BaseFn::Binary("range", range))),
("rest", Value::BaseFn(BaseFn::Unary("rest", rest))),

View File

@ -20,6 +20,12 @@ pub struct Chunk {
pub msgs: Vec<String>,
}
impl std::fmt::Display for Chunk {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Chunk.")
}
}
impl Chunk {
pub fn dissasemble_instr(&self, i: &mut usize) {
let op = Op::from_u8(self.bytecode[*i]).unwrap();

View File

@ -3,12 +3,14 @@ use imbl::HashMap;
use wasm_bindgen::prelude::*;
const DEBUG_SCRIPT_COMPILE: bool = false;
const DEBUG_SCRIPT_RUN: bool = false;
const DEBUG_SCRIPT_RUN: bool = true;
const DEBUG_PRELUDE_COMPILE: bool = false;
const DEBUG_PRELUDE_RUN: bool = false;
mod base;
mod world;
use crate::world::World;
mod spans;
use crate::spans::Spanned;
@ -82,8 +84,9 @@ fn prelude() -> HashMap<&'static str, Value> {
compiler.compile();
let chunk = compiler.chunk;
let mut vm = Creature::new(chunk, DEBUG_PRELUDE_RUN);
let prelude = vm.run().clone().unwrap();
let mut world = World::new(chunk, DEBUG_PRELUDE_RUN);
world.run();
let prelude = world.result.unwrap().unwrap();
match prelude {
Value::Dict(hashmap) => *hashmap,
_ => unreachable!(),
@ -144,8 +147,9 @@ pub fn ludus(src: String) -> String {
let vm_chunk = compiler.chunk;
let mut vm = Creature::new(vm_chunk, DEBUG_SCRIPT_RUN);
let result = vm.run();
let mut world = World::new(vm_chunk, DEBUG_SCRIPT_RUN);
world.run();
let result = world.result.clone().unwrap();
let console = postlude.get("console").unwrap();
let Value::Box(console) = console else {
@ -165,7 +169,6 @@ pub fn ludus(src: String) -> String {
unreachable!()
};
let commands = commands.borrow();
dbg!(&commands);
let commands = commands.to_json().unwrap();
let output = match result {
@ -176,7 +179,7 @@ pub fn ludus(src: String) -> String {
}
};
if DEBUG_SCRIPT_RUN {
vm.print_stack();
// vm.print_stack();
}
// TODO: use serde_json to make this more robust?

View File

@ -131,6 +131,7 @@ pub enum Value {
Fn(Rc<LFn>),
BaseFn(BaseFn),
Partial(Rc<Partial>),
Process,
}
impl PartialEq for Value {
@ -167,6 +168,7 @@ impl std::fmt::Display for Value {
Interned(str) => write!(f, "\"{str}\""),
String(str) => write!(f, "\"{str}\""),
Number(n) => write!(f, "{n}"),
Process => write!(f, "Process"),
Tuple(members) => write!(
f,
"({})",
@ -214,6 +216,7 @@ impl Value {
pub fn show(&self) -> String {
use Value::*;
let mut out = match &self {
Process => "Process".to_string(),
Nil => "nil".to_string(),
True => "true".to_string(),
False => "false".to_string(),
@ -308,6 +311,7 @@ impl Value {
pub fn stringify(&self) -> String {
use Value::*;
match &self {
Process => "process".to_string(),
Nil => "nil".to_string(),
True => "true".to_string(),
False => "false".to_string(),
@ -369,6 +373,7 @@ impl Value {
Fn(..) => "fn",
BaseFn(..) => "fn",
Partial(..) => "fn",
Process => "process",
}
}

154
src/vm.rs
View File

@ -4,13 +4,17 @@ use crate::op::Op;
use crate::parser::Ast;
use crate::spans::Spanned;
use crate::value::{LFn, Value};
use crate::world::Zoo;
use imbl::{HashMap, Vector};
use num_traits::FromPrimitive;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::fmt;
use std::mem::swap;
use std::rc::Rc;
const MAX_REDUCTIONS: usize = 100;
#[derive(Debug, Clone, PartialEq)]
pub enum Panic {
Str(&'static str),
@ -73,23 +77,35 @@ fn combine_bytes(high: u8, low: u8) -> usize {
out as usize
}
const REGISTER_SIZE: usize = 8;
#[derive(Debug, Clone, PartialEq)]
pub struct Creature {
pub stack: Vec<Value>,
pub call_stack: Vec<CallFrame>,
pub frame: CallFrame,
pub ip: usize,
pub return_register: [Value; 8],
pub register: [Value; REGISTER_SIZE],
pub matches: bool,
pub match_depth: u8,
pub result: Option<Result<Value, Panic>>,
debug: bool,
last_code: usize,
pub id: &'static str,
pub mbx: VecDeque<Value>,
pub reductions: usize,
pub zoo: Rc<RefCell<Zoo>>,
pub r#yield: bool,
}
impl std::fmt::Display for Creature {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Creature. {} @{}", self.id, self.ip)
}
}
impl Creature {
pub fn new(chunk: Chunk, debug: bool) -> Creature {
pub fn new(chunk: Chunk, zoo: Rc<RefCell<Zoo>>, debug: bool) -> Creature {
let lfn = LFn::Defined {
name: "user script",
doc: None,
@ -99,8 +115,12 @@ impl Creature {
closed: RefCell::new(vec![]),
};
let base_fn = Value::Fn(Rc::new(lfn));
Creature::spawn(base_fn, zoo, debug)
}
pub fn spawn(function: Value, zoo: Rc<RefCell<Zoo>>, debug: bool) -> Creature {
let base_frame = CallFrame {
function: base_fn.clone(),
function,
stack_base: 0,
ip: 0,
arity: 0,
@ -110,16 +130,33 @@ impl Creature {
call_stack: Vec::with_capacity(64),
frame: base_frame,
ip: 0,
return_register: [const { Value::Nothing }; 8],
register: [const { Value::Nothing }; REGISTER_SIZE],
matches: false,
match_depth: 0,
result: None,
debug,
last_code: 0,
id: "",
zoo,
mbx: VecDeque::new(),
reductions: 0,
r#yield: false,
}
}
pub fn reduce(&mut self) {
self.reductions += 1;
}
pub fn reset_reductions(&mut self) {
self.reductions = 0;
self.r#yield = false;
}
pub fn receive(&mut self, value: Value) {
self.mbx.push_back(value);
}
pub fn chunk(&self) -> &Chunk {
self.frame.chunk()
}
@ -147,12 +184,21 @@ impl Creature {
}
let inner = inner.join("|");
let register = self
.return_register
.register
.iter()
.map(|val| val.to_string())
.collect::<Vec<_>>()
.join(",");
println!("{:04}: [{inner}] ({register})", self.last_code);
let mbx = self
.mbx
.iter()
.map(|val| val.show())
.collect::<Vec<_>>()
.join("/");
println!(
"{:04}: [{inner}] ({register}) {} {{{mbx}}}",
self.last_code, self.id
);
}
fn print_debug(&self) {
@ -161,12 +207,12 @@ impl Creature {
self.chunk().dissasemble_instr(&mut ip);
}
pub fn run(&mut self) -> &Result<Value, Panic> {
while self.result.is_none() {
self.interpret();
}
self.result.as_ref().unwrap()
}
// pub fn run(&mut self) -> &Result<Value, Panic> {
// while self.result.is_none() {
// self.interpret();
// }
// self.result.as_ref().unwrap()
// }
pub fn call_stack(&mut self) -> String {
let mut stack = format!(" calling {}", self.frame.function.show());
@ -219,12 +265,56 @@ impl Creature {
self.ip >= self.chunk().bytecode.len()
}
fn handle_msg(&mut self, args: Vec<Value>) {
println!("message received! {}", 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)),
"msgs" => {
let msgs = self.mbx.iter().cloned().collect::<Vec<_>>();
let msgs = Vector::from(msgs);
self.push(Value::List(Box::new(msgs)));
}
"send" => {
let Value::Keyword(pid) = args[1] else {
return self.panic("malformed pid");
};
if self.id == pid {
self.mbx.push_back(args[2].clone());
} else {
self.zoo
.as_ref()
.borrow_mut()
.send_msg(pid, args[2].clone());
}
self.push(Value::Nil);
}
"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);
self.push(Value::Keyword(id));
}
"yield" => {
self.r#yield = true;
self.push(Value::Nil);
}
msg => panic!("Process does not understand message: {msg}"),
}
}
pub fn interpret(&mut self) {
loop {
if self.at_end() {
self.result = Some(Ok(self.stack.pop().unwrap()));
return;
}
if self.reductions >= MAX_REDUCTIONS || self.r#yield {
return;
}
let code = self.read();
if self.debug {
self.last_code = self.ip - 1;
@ -300,27 +390,27 @@ impl Creature {
self.push(value.clone());
}
Store => {
self.return_register[0] = self.pop();
self.register[0] = self.pop();
}
StoreN => {
let n = self.read() as usize;
for i in (0..n).rev() {
self.return_register[i] = self.pop();
self.register[i] = self.pop();
}
}
Stash => {
self.return_register[0] = self.peek().clone();
self.register[0] = self.peek().clone();
}
Load => {
let mut value = Value::Nothing;
swap(&mut self.return_register[0], &mut value);
swap(&mut self.register[0], &mut value);
self.push(value);
}
LoadN => {
let n = self.read() as usize;
for i in 0..n {
let mut value = Value::Nothing;
swap(&mut self.return_register[i], &mut value);
swap(&mut self.register[i], &mut value);
self.push(value);
}
}
@ -793,6 +883,7 @@ impl Creature {
self.push(Value::Partial(Rc::new(partial)));
}
TailCall => {
self.reduce();
let arity = self.read();
let called = self.pop();
@ -805,6 +896,10 @@ impl Creature {
}
match called {
Value::Process => {
let args = self.stack.split_off(self.stack.len() - arity as usize);
self.handle_msg(args);
}
Value::Fn(_) => {
if !called.as_fn().accepts(arity) {
return self.panic_with(format!(
@ -814,7 +909,7 @@ impl Creature {
}
// first put the arguments in the register
for i in 0..arity as usize {
self.return_register[arity as usize - i - 1] = self.pop();
self.register[arity as usize - i - 1] = self.pop();
}
// self.print_stack();
@ -822,9 +917,9 @@ impl Creature {
self.stack.truncate(self.frame.stack_base);
// then push the arguments back on the stack
let mut i = 0;
while i < 8 && self.return_register[i] != Value::Nothing {
while i < 8 && self.register[i] != Value::Nothing {
let mut value = Value::Nothing;
swap(&mut self.return_register[i], &mut value);
swap(&mut self.register[i], &mut value);
self.push(value);
i += 1;
}
@ -915,6 +1010,7 @@ impl Creature {
}
}
Call => {
self.reduce();
let arity = self.read();
let called = self.pop();
@ -924,6 +1020,10 @@ impl Creature {
}
match called {
Value::Process => {
let args = self.stack.split_off(self.stack.len() - arity as usize);
self.handle_msg(args);
}
Value::Fn(_) => {
if !called.as_fn().accepts(arity) {
return self.panic_with(format!(
@ -1009,12 +1109,20 @@ impl Creature {
if self.debug {
println!("== returning from {} ==", self.frame.function.show())
}
self.frame = self.call_stack.pop().unwrap();
self.ip = self.frame.ip;
let mut value = Value::Nothing;
swap(&mut self.return_register[0], &mut value);
swap(&mut self.register[0], &mut value);
match self.call_stack.pop() {
Some(frame) => {
self.ip = frame.ip;
self.frame = frame;
self.push(value);
}
None => {
self.result = Some(Ok(value));
return;
}
}
}
Print => {
println!("{}", self.pop().show());
self.push(Value::Keyword("ok"));

View File

@ -1,10 +1,12 @@
use crate::chunk::Chunk;
use crate::value::Value;
use crate::vm::{Creature, Panic};
use ran::ran_u8;
use std::collections::{HashMap, VecDeque};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
const ANIMALS: [&str; 24] = [
"turtle",
"tortoise",
"hare",
"squirrel",
@ -28,6 +30,7 @@ const ANIMALS: [&str; 24] = [
"giraffe",
"leopard",
"lion",
"hippopotamus",
];
#[derive(Debug, Clone, PartialEq)]
@ -37,12 +40,34 @@ enum Status {
Nested(Creature),
}
impl std::fmt::Display for Status {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Status::Empty => write!(f, "empty"),
Status::Borrowed => write!(f, "borrowed"),
Status::Nested(creature) => write!(f, "nested {creature}"),
}
}
}
impl Status {
pub fn receive(&mut self, msg: Value) {
match self {
Status::Nested(creature) => creature.receive(msg),
Status::Borrowed => println!("sending a message to a borrowed process"),
Status::Empty => println!("sending a message to a dead process"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
struct Zoo {
pub struct Zoo {
procs: Vec<Status>,
empty: Vec<usize>,
ids: HashMap<&'static str, usize>,
dead: Vec<&'static str>,
kill_list: Vec<&'static str>,
active: usize,
}
impl Zoo {
@ -51,15 +76,30 @@ impl Zoo {
procs: vec![],
empty: vec![],
ids: HashMap::new(),
kill_list: vec![],
dead: vec![],
active: 0,
}
}
fn random_id(&self) -> String {
let rand = ran_u8() as usize % 24;
let idx = self.procs.len();
format!("{}_{idx}", ANIMALS[rand])
}
fn new_id(&self) -> &'static str {
let mut new = self.random_id();
while self.dead.iter().any(|old| *old == new) {
new = self.random_id();
}
new.leak()
}
pub fn put(&mut self, mut proc: Creature) -> &'static str {
if self.empty.is_empty() {
let rand = ran_u8() as usize % 24;
let id = self.new_id();
let idx = self.procs.len();
let id = format!("{}_{idx}", ANIMALS[rand]).leak();
proc.id = id;
self.procs.push(Status::Nested(proc));
self.ids.insert(id, idx);
@ -76,13 +116,19 @@ impl Zoo {
}
pub fn kill(&mut self, id: &'static str) {
self.kill_list.push(id);
}
pub fn clean_up(&mut self) {
while let Some(id) = self.kill_list.pop() {
if let Some(idx) = self.ids.get(id) {
self.procs[*idx] = Status::Empty;
self.empty.push(*idx);
self.ids.remove(&id);
self.ids.remove(id);
self.dead.push(id);
}
}
}
pub fn catch(&mut self, id: &'static str) -> Creature {
if let Some(idx) = self.ids.get(id) {
@ -106,76 +152,177 @@ impl Zoo {
unreachable!("tried to return a process the world doesn't know about");
}
}
}
pub struct World {
procs: Zoo,
mbxes: HashMap<String, VecDeque<Value>>,
active: Creature,
// TODO: we need a lifetime here
main: &'static str,
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
);
}
impl World {
pub fn new(proc: Creature) -> World {
let mut creatures = Zoo::new();
let id = creatures.put(proc);
let caught = creatures.catch(id);
World {
procs: creatures,
mbxes: HashMap::new(),
active: caught,
main: id,
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,
}
pub fn spawn(&mut self, proc: Creature) -> Value {
let id = self.procs.put(proc);
Value::Keyword(id)
}
pub fn send_msg(&mut self, id: &'static str, msg: Value) {
let mbx = self.mbxes.get_mut(id).unwrap();
mbx.push_back(msg);
let Some(idx) = self.ids.get(id) else {
return;
};
self.procs[*idx].receive(msg);
}
}
pub fn sleep(&mut self, id: &'static str) {
// check if the id is the actually active process
// return it to the nursery
// get the next process from the nursery
#[derive(Debug, Clone, PartialEq)]
pub struct World {
zoo: Rc<RefCell<Zoo>>,
active: Option<Creature>,
main: &'static str,
pub result: Option<Result<Value, Panic>>,
}
pub fn get_msg(&self, id: &'static str) -> Option<(usize, Value)> {
// check if the id is of the active process
todo!()
impl World {
pub fn new(chunk: Chunk, debug: bool) -> World {
let zoo = Rc::new(RefCell::new(Zoo::new()));
let main = Creature::new(chunk, zoo.clone(), debug);
let id = zoo.as_ref().borrow_mut().put(main);
World {
zoo,
active: None,
main: id,
result: None,
}
}
pub fn match_msg(&mut self, id: &'static str, idx: usize) {
// again, check for activity
// delete the message at idx, which we gave along with the value as the tuple in get_msg
// 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);
}
pub fn r#yield(&mut self, id: &'static str) {
// check if the id is active
// swap out the currently active process for the next one
// 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 panic(&mut self, id: &'static str) {
// TODO: devise some way of linking processes (study the BEAM on this)
// check if the id is active
// check if the process is `main`, and crash the program if it is
// kill the process
// swap out this process for the next one
pub fn active_id(&mut self) -> &'static str {
self.active.as_ref().unwrap().id
}
pub fn complete(&mut self, id: &'static str, value: Value) {
// check if the id is active
// check if the process is main
// if it is: stash the value somehow and exit the program cleanly
pub fn kill_active(&mut self) {
let id = self.active_id();
self.zoo.as_ref().borrow_mut().kill(id);
}
pub fn run(&mut self) -> Result<Value, Panic> {
todo!()
pub fn active_result(&mut self) -> &Option<Result<Value, Panic>> {
&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");
self.active.as_mut().unwrap().interpret();
println!("interpreted loop");
match self.active_result() {
None => (),
Some(_) => {
if self.active_id() == self.main {
self.result = self.active_result().clone();
return;
}
self.kill_active();
}
}
println!("getting next process");
self.next();
self.clean_up();
}
}
// TODO: