diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index dfcb9f3..7021bf1 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1211,7 +1211,35 @@ 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) +} + +fn msgs () -> base :process (:msgs) + #{ + self + send + msgs + spawn! + yield! + abs abs add diff --git a/sandbox.ld b/sandbox.ld index 07a0e48..6c454cc 100644 --- a/sandbox.ld +++ b/sandbox.ld @@ -1 +1,11 @@ -let true = false +let pid = spawn! (fn () -> { + print! (self ()) + print! (msgs ()) +}) + +send (pid, :foo) +send (pid, :bar) + +yield! () + +:done diff --git a/src/base.rs b/src/base.rs index 7efa43f..d3ed96e 100644 --- a/src/base.rs +++ b/src/base.rs @@ -609,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))), diff --git a/src/chunk.rs b/src/chunk.rs index 6551278..7dfba57 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -20,6 +20,12 @@ pub struct Chunk { pub msgs: Vec, } +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(); diff --git a/src/lib.rs b/src/lib.rs index bbe9c50..063bee8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ 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; @@ -84,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!(), @@ -146,10 +147,8 @@ pub fn ludus(src: String) -> String { let vm_chunk = compiler.chunk; - let vm = Creature::new(vm_chunk, DEBUG_SCRIPT_RUN); - let grip = World::new(vm); - grip.borrow_mut().run(); - let world = grip.borrow(); + let mut world = World::new(vm_chunk, DEBUG_SCRIPT_RUN); + world.run(); let result = world.result.clone().unwrap(); let console = postlude.get("console").unwrap(); diff --git a/src/vm.rs b/src/vm.rs index bd1150d..add7909 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -4,7 +4,7 @@ use crate::op::Op; use crate::parser::Ast; use crate::spans::Spanned; use crate::value::{LFn, Value}; -use crate::world::Grasp; +use crate::world::Zoo; use imbl::{HashMap, Vector}; use num_traits::FromPrimitive; use std::cell::RefCell; @@ -77,26 +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, pub call_stack: Vec, 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>, debug: bool, last_code: usize, pub id: &'static str, - pub world: Grasp, pub mbx: VecDeque, pub reductions: usize, + pub zoo: Rc>, + 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>, debug: bool) -> Creature { let lfn = LFn::Defined { name: "user script", doc: None, @@ -106,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>, debug: bool) -> Creature { let base_frame = CallFrame { - function: base_fn.clone(), + function, stack_base: 0, ip: 0, arity: 0, @@ -117,16 +130,17 @@ 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: "", - world: None, + zoo, mbx: VecDeque::new(), reductions: 0, + r#yield: false, } } @@ -134,6 +148,11 @@ impl Creature { 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); } @@ -165,12 +184,21 @@ impl Creature { } let inner = inner.join("|"); let register = self - .return_register + .register .iter() .map(|val| val.to_string()) .collect::>() .join(","); - println!("{:04}: [{inner}] ({register})", self.last_code); + let mbx = self + .mbx + .iter() + .map(|val| val.show()) + .collect::>() + .join("/"); + println!( + "{:04}: [{inner}] ({register}) {} {{{mbx}}}", + self.last_code, self.id + ); } fn print_debug(&self) { @@ -179,12 +207,12 @@ impl Creature { self.chunk().dissasemble_instr(&mut ip); } - pub fn run(&mut self) -> &Result { - while self.result.is_none() { - self.interpret(); - } - self.result.as_ref().unwrap() - } + // pub fn run(&mut self) -> &Result { + // 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()); @@ -237,13 +265,54 @@ impl Creature { self.ip >= self.chunk().bytecode.len() } + fn handle_msg(&mut self, args: Vec) { + 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::>(); + 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 { + if self.reductions >= MAX_REDUCTIONS || self.r#yield { return; } let code = self.read(); @@ -321,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); } } @@ -814,6 +883,7 @@ impl Creature { self.push(Value::Partial(Rc::new(partial))); } TailCall => { + self.reduce(); let arity = self.read(); let called = self.pop(); @@ -826,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!( @@ -835,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(); @@ -843,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; } @@ -936,6 +1010,7 @@ impl Creature { } } Call => { + self.reduce(); let arity = self.read(); let called = self.pop(); @@ -945,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!( @@ -1030,11 +1109,19 @@ 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); - self.push(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()); diff --git a/src/world.rs b/src/world.rs index a9db6b2..f44515e 100644 --- a/src/world.rs +++ b/src/world.rs @@ -1,3 +1,4 @@ +use crate::chunk::Chunk; use crate::value::Value; use crate::vm::{Creature, Panic}; use ran::ran_u8; @@ -39,12 +40,33 @@ 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, empty: Vec, ids: HashMap<&'static str, usize>, dead: Vec<&'static str>, + kill_list: Vec<&'static str>, active: usize, } @@ -54,6 +76,7 @@ impl Zoo { procs: vec![], empty: vec![], ids: HashMap::new(), + kill_list: vec![], dead: vec![], active: 0, } @@ -93,11 +116,17 @@ impl Zoo { } 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); + 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.dead.push(id); + } } } @@ -125,7 +154,18 @@ impl Zoo { } 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::>() + .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", @@ -133,9 +173,14 @@ impl Zoo { ); } self.active = (self.active + 1) % self.procs.len(); - while self.procs[self.active] != Status::Empty { + 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!( @@ -145,61 +190,63 @@ impl Zoo { Status::Nested(proc) => proc.id, } } -} -pub type Grasp = Option>>; + pub fn send_msg(&mut self, id: &'static str, msg: Value) { + let Some(idx) = self.ids.get(id) else { + return; + }; + self.procs[*idx].receive(msg); + } +} #[derive(Debug, Clone, PartialEq)] pub struct World { - zoo: Zoo, + zoo: Rc>, active: Option, main: &'static str, - handle: Grasp, pub result: Option>, } impl World { - pub fn new(proc: Creature) -> Rc> { - let mut zoo = Zoo::new(); - let id = zoo.put(proc); - let world = 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, - handle: None, result: None, - }; - let handle = Rc::new(RefCell::new(world)); - let grasped = handle.clone(); - let mut world = grasped.as_ref().borrow_mut(); - world.handle = Some(grasped.clone()); - - let mut caught = world.zoo.catch(id); - caught.world = world.handle.clone(); - world.zoo.release(caught); - - handle + } } - pub fn spawn(&mut self, proc: Creature) -> Value { - let id = self.zoo.put(proc); - Value::Keyword(id) - } + // 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); - } + // 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.next(self.active.as_ref().unwrap().id); + 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.catch(id); + 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); - self.zoo.release(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); } @@ -234,22 +281,47 @@ impl World { // self.next(id); // } - pub fn run(&mut self) { - let main = self.zoo.catch(self.main); + 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 + } + + pub fn kill_active(&mut self) { + let id = self.active_id(); + self.zoo.as_ref().borrow_mut().kill(id); + } + + pub fn active_result(&mut self) -> &Option> { + &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(); - match self.active.as_ref().unwrap().result { + println!("interpreted loop"); + match self.active_result() { None => (), Some(_) => { - if self.active.as_ref().unwrap().id == self.main { - self.result = self.active.as_ref().unwrap().result.clone(); + if self.active_id() == self.main { + self.result = self.active_result().clone(); return; } - self.zoo.kill(self.active.as_ref().unwrap().id); + self.kill_active(); } } + println!("getting next process"); self.next(); + self.clean_up(); } }