diff --git a/src/lib.rs b/src/lib.rs index 958b2c2..5babf43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ const DEBUG_PRELUDE_COMPILE: bool = false; const DEBUG_PRELUDE_RUN: bool = false; mod base; +mod world; mod spans; use crate::spans::Spanned; @@ -34,7 +35,7 @@ mod value; use value::Value; mod vm; -use vm::Vm; +use vm::Creature; const PRELUDE: &str = include_str!("../assets/test_prelude.ld"); @@ -81,7 +82,7 @@ fn prelude() -> HashMap<&'static str, Value> { compiler.compile(); let chunk = compiler.chunk; - let mut vm = Vm::new(chunk, DEBUG_PRELUDE_RUN); + let mut vm = Creature::new(chunk, DEBUG_PRELUDE_RUN); let prelude = vm.run().clone().unwrap(); match prelude { Value::Dict(hashmap) => *hashmap, @@ -143,7 +144,7 @@ pub fn ludus(src: String) -> String { let vm_chunk = compiler.chunk; - let mut vm = Vm::new(vm_chunk, DEBUG_SCRIPT_RUN); + let mut vm = Creature::new(vm_chunk, DEBUG_SCRIPT_RUN); let result = vm.run(); let console = postlude.get("console").unwrap(); diff --git a/src/vm.rs b/src/vm.rs index e2260fe..1fcbde1 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -12,14 +12,6 @@ use std::mem::swap; use std::rc::Rc; #[derive(Debug, Clone, PartialEq)] -// pub struct Panic { -// pub input: &'static str, -// pub src: &'static str, -// pub msg: String, -// pub span: SimpleSpan, -// pub trace: Vec, -// pub extra: String, -// } pub enum Panic { Str(&'static str), String(String), @@ -44,6 +36,7 @@ pub struct Trace { pub src: &'static str, } +#[derive(Debug, Clone, PartialEq)] pub struct CallFrame { pub function: Value, pub arity: u8, @@ -80,7 +73,8 @@ fn combine_bytes(high: u8, low: u8) -> usize { out as usize } -pub struct Vm { +#[derive(Debug, Clone, PartialEq)] +pub struct Creature { pub stack: Vec, pub call_stack: Vec, pub frame: CallFrame, @@ -91,10 +85,11 @@ pub struct Vm { pub result: Option>, debug: bool, last_code: usize, + pub id: &'static str, } -impl Vm { - pub fn new(chunk: Chunk, debug: bool) -> Vm { +impl Creature { + pub fn new(chunk: Chunk, debug: bool) -> Creature { let lfn = LFn::Defined { name: "user script", doc: None, @@ -110,7 +105,7 @@ impl Vm { ip: 0, arity: 0, }; - Vm { + Creature { stack: vec![], call_stack: Vec::with_capacity(64), frame: base_frame, @@ -121,6 +116,7 @@ impl Vm { result: None, debug, last_code: 0, + id: "", } } diff --git a/src/world.rs b/src/world.rs new file mode 100644 index 0000000..61a5d97 --- /dev/null +++ b/src/world.rs @@ -0,0 +1,205 @@ +use crate::value::Value; +use crate::vm::{Creature, Panic}; +use ran::ran_u8; +use std::collections::{HashMap, VecDeque}; + +const ANIMALS: [&str; 24] = [ + "turtle", + "tortoise", + "hare", + "squirrel", + "hawk", + "woodpecker", + "cardinal", + "coyote", + "raccoon", + "rat", + "axolotl", + "cormorant", + "duck", + "orca", + "humbpack", + "tern", + "quokka", + "koala", + "kangaroo", + "zebra", + "hyena", + "giraffe", + "leopard", + "lion", +]; + +#[derive(Debug, Clone, PartialEq)] +enum Status { + Empty, + Borrowed, + Nested(Creature), +} + +#[derive(Debug, Clone, PartialEq)] +struct Zoo { + procs: Vec, + empty: Vec, + ids: HashMap<&'static str, usize>, + dead: Vec<&'static str>, +} + +impl Zoo { + pub fn new() -> Zoo { + Zoo { + procs: vec![], + empty: vec![], + ids: HashMap::new(), + dead: vec![], + } + } + + pub fn put(&mut self, mut proc: Creature) -> &'static str { + if self.empty.is_empty() { + let rand = ran_u8() as usize % 24; + 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); + id + } else { + let idx = self.empty.pop().unwrap(); + let rand = ran_u8() as usize % 24; + let id = format!("{}_{idx}", ANIMALS[rand]).leak(); + proc.id = id; + self.ids.insert(id, idx); + self.procs[idx] = Status::Nested(proc); + id + } + } + + pub fn kill(&mut self, id: &'static str) { + if let Some(idx) = self.ids.get(id) { + self.procs[*idx] = Status::Empty; + self.empty.push(*idx); + 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) { + let mut proc = Status::Borrowed; + std::mem::swap(&mut proc, &mut self.procs[*idx]); + let Status::Nested(proc) = proc else { + unreachable!("tried to borrow an empty or already-borrowed process"); + }; + proc + } else { + unreachable!("tried to borrow a non-existent process"); + } + } + + pub fn release(&mut self, proc: Creature) { + let id = proc.id; + 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"); + } + } +} + +pub struct World { + procs: Zoo, + mbxes: HashMap>, + active: Creature, + // TODO: we need a lifetime here + main: &'static str, +} + +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, + } + } + + 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); + } + + 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 + } + + pub fn get_msg(&self, id: &'static str) -> Option<(usize, Value)> { + // check if the id is of the active process + todo!() + } + + 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 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 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 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 run(&mut self) -> Result { + todo!() + } + + // TODO: + // * [ ] Maybe I need to write this from the bottom up? + // What do processes need to do? + // - [ ] send a message to another process + // - [ ] tell the world to spawn a new process, get the pid back + // - [ ] receive its messages (always until something matches, or sleep if nothing matches) + // - [ ] delete a message from the mbx if it's a match (by idx) + // - [ ] yield + // - [ ] panic + // - [ ] complete + // Thus the other side of this looks like: + // * [x] Spawn a process + // * [x] +} + +// Okay, some more thinking +// The world and process can't have mutable references to one another +// They will each need an Rc> +// All the message passing and world/proc communication will happen through there +// And ownership goes World -> Process A -> World -> Process B + +// Both the world and a process will have an endless `loop`. +// But I already have three terms: Zoo, Creature, and World +// That should be enough indirection? +// To solve tomorrow.