send messages, motherfucker!

This commit is contained in:
Scott Richmond 2025-06-26 20:30:40 -04:00
parent c144702b98
commit 888f5b62da
7 changed files with 284 additions and 81 deletions

View File

@ -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

View File

@ -1 +1,11 @@
let true = false
let pid = spawn! (fn () -> {
print! (self ())
print! (msgs ())
})
send (pid, :foo)
send (pid, :bar)
yield! ()
:done

View File

@ -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))),

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,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();

143
src/vm.rs
View File

@ -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<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 world: Grasp,
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,
@ -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<RefCell<Zoo>>, 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::<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) {
@ -179,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());
@ -237,13 +265,54 @@ 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 {
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());

View File

@ -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<Status>,
empty: Vec<usize>,
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::<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",
@ -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<Rc<RefCell<World>>>;
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<RefCell<Zoo>>,
active: Option<Creature>,
main: &'static str,
handle: Grasp,
pub result: Option<Result<Value, Panic>>,
}
impl World {
pub fn new(proc: Creature) -> Rc<RefCell<World>> {
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<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();
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();
}
}