improve panic message, slightly

This commit is contained in:
Scott Richmond 2025-07-05 22:40:11 -04:00
parent cefb29d610
commit 8dfb8c88fe
8 changed files with 182 additions and 101 deletions

View File

@ -1035,7 +1035,7 @@ fn spawn! {
} }
fn yield! { fn yield! {
"Forces a process to yield." "Forces a process to yield, allowing other processes to execute."
() -> base :process (:yield) () -> base :process (:yield)
} }
@ -1045,10 +1045,13 @@ fn alive? {
} }
fn link! { fn link! {
"Creates a link between two processes. There are two types of links: `:report`, which sends a message to pid1 when pid2 dies; and `:panic`, which causes a panic in one when the other dies. The default is `:report`." "Links this process to another process. When either one dies--panics or returns--both are shut down."
(pid1 as :keyword, pid2 as :keyword) -> link! (pid1, pid2, :report) (pid as :keyword) -> base :process (:link, pid)
(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 monitor! {
"Subscribes this process to another process's exit signals. There are two possibilities: a panic or a return. Exit signals are in the form of `(:exit, pid, (:ok, value)/(:err, msg))`."
(pid as :keyword) -> base :process (:monitor, pid)
} }
fn flush! { fn flush! {
@ -1061,6 +1064,25 @@ fn sleep! {
(ms as :number) -> base :process (:sleep, ms) (ms as :number) -> base :process (:sleep, ms)
} }
fn await! {
"Parks the current process until it receives an exit signal from the passed process. Returns the result of a successful execution or panics if the awaited process panics."
(pid as :keyword) -> {
monitor! (pid)
receive {
(:exit, _, (:ok, result)) -> result
(:exit, _, (:err, msg)) -> {
panic! "Monitored process {pid} panicked with {msg}" }
}
}
}
fn heed! {
"Parks the current process until it receives a reply, and returns whatever is replied. Causes a panic if it gets anything other than a `(:reply, result)` tuple."
() -> receive {
(:reply, result) -> result
}
}
& TODO: make this more robust, to handle multiple pending requests w/o data races & TODO: make this more robust, to handle multiple pending requests w/o data races
fn request_fetch! { fn request_fetch! {
(pid as :keyword, url as :string) -> { (pid as :keyword, url as :string) -> {
@ -1427,7 +1449,11 @@ fn llist {
flush! flush!
& wip actor functions & wip actor functions
& link! link!
monitor!
await!
heed!
spawn_turtle! spawn_turtle!
& shared memory w/ rust & shared memory w/ rust

View File

@ -1249,5 +1249,6 @@ And now, we build capacity for building projects.
* [ ] allow for multiple turtles * [ ] allow for multiple turtles
- [x] rework p5 function in `ludus.js` to properly parse commands targeted at different turtles - [x] rework p5 function in `ludus.js` to properly parse commands targeted at different turtles
- [ ] write ludus code to spawn & manipulate turtle actors - [x] write ludus code to spawn & manipulate turtle actors
- [ ] write linking logic so we can `await` turtles
* [ ] do some sample multiturtle sketches * [ ] do some sample multiturtle sketches

View File

@ -403,7 +403,7 @@ function __wbg_get_imports() {
_assertBoolean(ret); _assertBoolean(ret);
return ret; return ret;
}; };
imports.wbg.__wbindgen_closure_wrapper8094 = function() { return logError(function (arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper8073 = function() { return logError(function (arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20);
return ret; return ret;
}, arguments) }; }, arguments) };

BIN
pkg/rudus_bg.wasm (Stored with Git LFS)

Binary file not shown.

View File

@ -97,7 +97,7 @@ pub fn panic(panic: Panic) -> String {
// console_log!("Ludus panicked!: {panic}"); // console_log!("Ludus panicked!: {panic}");
// panic.call_stack.last().unwrap().chunk().dissasemble(); // panic.call_stack.last().unwrap().chunk().dissasemble();
// console_log!("{:?}", panic.call_stack.last().unwrap().chunk().spans); // console_log!("{:?}", panic.call_stack.last().unwrap().chunk().spans);
let mut msgs = vec!["Ludus panicked!".to_string()]; let mut msgs = vec![];
let msg = match panic.msg { let msg = match panic.msg {
PanicMsg::Generic(ref s) => s, PanicMsg::Generic(ref s) => s,
_ => &"no match".to_string(), _ => &"no match".to_string(),

View File

@ -163,14 +163,14 @@ pub async fn ludus(src: String) {
compiler.compile(); compiler.compile();
if DEBUG_SCRIPT_COMPILE { if DEBUG_SCRIPT_COMPILE {
println!("=== source code ==="); console_log!("=== source code ===");
println!("{src}"); console_log!("{src}");
compiler.disassemble(); compiler.disassemble();
println!("\n\n") console_log!("\n\n")
} }
if DEBUG_SCRIPT_RUN { if DEBUG_SCRIPT_RUN {
println!("=== vm run ==="); console_log!("=== vm run ===");
} }
let vm_chunk = compiler.chunk; let vm_chunk = compiler.chunk;

166
src/vm.rs
View File

@ -178,9 +178,10 @@ impl Creature {
.map(|val| val.show()) .map(|val| val.show())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("/"); .join("/");
println!( console_log!(
"{:04}: [{inner}] ({register}) {} {{{mbx}}}", "{:04}: [{inner}] ({register}) {} {{{mbx}}}",
self.last_code, self.pid self.last_code,
self.pid
); );
} }
@ -197,14 +198,6 @@ impl Creature {
// add it to our cloned stack // add it to our cloned stack
let mut call_stack = self.call_stack.clone(); let mut call_stack = self.call_stack.clone();
call_stack.push(frame); call_stack.push(frame);
// console_log!(
// "{}",
// call_stack
// .iter()
// .map(|s| s.to_string())
// .collect::<Vec<_>>()
// .join("\n")
// );
//make a panic //make a panic
let panic = Panic { let panic = Panic {
msg, msg,
@ -214,9 +207,10 @@ impl Creature {
// and gtfo // and gtfo
self.result = Some(Err(panic)); self.result = Some(Err(panic));
self.r#yield = true; self.r#yield = true;
self.broadcast_panic();
} }
fn panic_with(&mut self, msg: String) { pub fn panic_with(&mut self, msg: String) {
self.panic(PanicMsg::Generic(msg)); self.panic(PanicMsg::Generic(msg));
} }
@ -254,52 +248,84 @@ impl Creature {
if self.pid == pid { if self.pid == pid {
self.mbx.push_back(msg.clone()); self.mbx.push_back(msg.clone());
} else { } else {
self.zoo.as_ref().borrow_mut().send_msg(pid, msg); self.zoo.borrow_mut().send_msg(pid, msg);
} }
self.push(Value::Keyword("ok")); self.push(Value::Keyword("ok"));
} }
// TODO: fix these based on what I decide about `link` & `monitor` fn broadcast_panic(&mut self) {
fn link_report(&mut self, parent: Value, child: Value) { console_log!("broadcasting panic in {}", self.pid);
let (Value::Keyword(parent), Value::Keyword(child)) = (parent, child) else { let Err(panic) = self.result.clone().unwrap() else {
unreachable!("expected keyword pids in link_report"); unreachable!("expected panic in broadcast panic");
}; };
let child = if child == self.pid { let msg = Rc::new(crate::errors::panic(panic));
self for pid in self.parents.clone() {
} else { self.send_msg(
&mut self.zoo.borrow_mut().catch(child) Value::Keyword(pid),
}; Value::tuple(vec![
child.parents.push(parent); Value::Keyword("exit"),
Value::Keyword(self.pid),
Value::tuple(vec![Value::Keyword("err"), Value::String(msg.clone())]),
]),
)
}
for pid in self.siblings.clone() {
self.zoo.borrow_mut().kill_linked(pid);
}
} }
fn link_panic(&mut self, left_pid: Value, right_pid: Value) { fn broadcast_return(&mut self) {
let (Value::Keyword(left_id), Value::Keyword(right_id)) = (left_pid, right_pid) else { // console_log!("broadcasting return from {}", self.pid);
unreachable!("expected keyword pids in link_panic"); //TODO: work around this clone?
for pid in self.parents.clone() {
let result_tuple = Value::tuple(vec![
Value::Keyword("ok"),
self.result.clone().unwrap().unwrap(),
]);
let exit_signal = Value::tuple(vec![
Value::Keyword("exit"),
Value::Keyword(self.pid),
result_tuple,
]);
// console_log!("sending exit signal {exit_signal}");
self.send_msg(Value::Keyword(pid), exit_signal);
}
for pid in self.siblings.clone() {
self.zoo.borrow_mut().kill(pid);
}
}
// TODO: fix these based on what I decide about `link` & `monitor`
fn monitor(&mut self, other: Value) {
// console_log!("monitoring {other}");
let Value::Keyword(other) = other else {
unreachable!("expected keyword pid in monitor");
};
if other != self.pid {
let mut other = self.zoo.borrow_mut().catch(other);
other.parents.push(self.pid);
self.zoo.borrow_mut().release(other);
// console_log!("{} added to parents", self.pid);
}
self.push(Value::Keyword("ok"));
}
fn link(&mut self, other: Value) {
let Value::Keyword(other) = other else {
unreachable!("expected keyword pid in link");
}; };
// if we're linking to ourselves, we don't need to do anything // if we're linking to ourselves, we don't need to do anything
if left_id == self.pid && right_id == self.pid { if other != self.pid {
return; self.siblings.push(other);
} let mut other = self.zoo.borrow_mut().catch(other);
let mut zoo = self.zoo.borrow_mut(); other.siblings.push(self.pid);
// fancy footwork w/ cases: we can't catch ourselves in the zoo self.zoo.borrow_mut().release(other);
if left_id == self.pid {
self.siblings.push(right_id);
let mut right = zoo.catch(right_id);
right.siblings.push(self.pid);
} else if right_id == self.pid {
self.siblings.push(left_id);
let mut left = zoo.catch(left_id);
left.siblings.push(self.pid);
} else {
let mut left = zoo.catch(left_id);
let mut right = zoo.catch(right_id);
left.siblings.push(right_id);
right.siblings.push(left_id);
} }
self.push(Value::Keyword("ok"));
} }
fn handle_msg(&mut self, args: Vec<Value>) { fn handle_msg(&mut self, args: Vec<Value>) {
println!("message received by {}: {}", self.pid, args[0]); // console_log!("message received by {}: {}", self.pid, args[0]);
let Value::Keyword(msg) = args.first().unwrap() else { let Value::Keyword(msg) = args.first().unwrap() else {
return self.panic_with("malformed message to Process".to_string()); return self.panic_with("malformed message to Process".to_string());
}; };
@ -310,12 +336,12 @@ impl Creature {
let f = args[1].clone(); let f = args[1].clone();
let proc = Creature::spawn(f, self.zoo.clone(), self.debug); let proc = Creature::spawn(f, self.zoo.clone(), self.debug);
let id = self.zoo.as_ref().borrow_mut().put(proc); let id = self.zoo.as_ref().borrow_mut().put(proc);
console_log!("spawning new process {id}!"); // console_log!("spawning new process {id}!");
self.push(Value::Keyword(id)); self.push(Value::Keyword(id));
} }
"yield" => { "yield" => {
self.r#yield = true; self.r#yield = true;
println!("yielding from {}", self.pid); console_log!("yielding from {}", self.pid);
self.push(Value::Keyword("ok")); self.push(Value::Keyword("ok"));
} }
"alive" => { "alive" => {
@ -329,12 +355,12 @@ impl Creature {
self.push(Value::False) self.push(Value::False)
} }
} }
"link_panic" => self.link_panic(args[1].clone(), args[2].clone()), "link" => self.link(args[1].clone()),
"link_report" => self.link_report(args[1].clone(), args[2].clone()), "monitor" => self.monitor(args[1].clone()),
"flush" => { "flush" => {
let msgs = self.mbx.iter().cloned().collect::<Vec<_>>(); let msgs = self.mbx.iter().cloned().collect::<Vec<_>>();
let msgs = Vector::from(msgs); let msgs = Vector::from(msgs);
println!( console_log!(
"delivering messages: {}", "delivering messages: {}",
msgs.iter() msgs.iter()
.map(|x| x.show()) .map(|x| x.show())
@ -342,11 +368,11 @@ impl Creature {
.join(" | ") .join(" | ")
); );
self.mbx = VecDeque::new(); self.mbx = VecDeque::new();
println!("flushing messages in {}", self.pid); console_log!("flushing messages in {}", self.pid);
self.push(Value::List(Box::new(msgs))); self.push(Value::List(Box::new(msgs)));
} }
"sleep" => { "sleep" => {
println!("sleeping {} for {}", self.pid, args[1]); console_log!("sleeping {} for {}", self.pid, args[1]);
let Value::Number(ms) = args[1] else { let Value::Number(ms) = args[1] else {
unreachable!() unreachable!()
}; };
@ -359,28 +385,29 @@ impl Creature {
} }
pub fn interpret(&mut self) { pub fn interpret(&mut self) {
console_log!("starting process {}", self.pid); // console_log!("starting process {}", self.pid);
console_log!( // console_log!(
"mbx: {}", // "mbx: {}",
self.mbx // self.mbx
.iter() // .iter()
.map(|x| x.show()) // .map(|x| x.show())
.collect::<Vec<_>>() // .collect::<Vec<_>>()
.join(" | ") // .join(" | ")
); // );
loop { loop {
if self.at_end() { if self.at_end() {
let result = self.stack.pop().unwrap(); let result = self.stack.pop().unwrap();
// println!("process {} has returned {result}", self.pid); self.broadcast_return();
// console_log!("process :{} has returned {result}", self.pid);
self.result = Some(Ok(result)); self.result = Some(Ok(result));
return; return;
} }
if self.r#yield { if self.r#yield {
console_log!("yielding from {}", self.pid); // console_log!("yielding from {}", self.pid);
return; return;
} }
if self.reductions >= MAX_REDUCTIONS { if self.reductions >= MAX_REDUCTIONS {
// println!( // console_log!(
// "process {} is yielding after {MAX_REDUCTIONS} reductions", // "process {} is yielding after {MAX_REDUCTIONS} reductions",
// self.pid // self.pid
// ); // );
@ -730,7 +757,7 @@ impl Creature {
let dict = match self.get_value_at(dict_idx) { let dict = match self.get_value_at(dict_idx) {
Value::Dict(dict) => dict, Value::Dict(dict) => dict,
value => { value => {
println!( console_log!(
"internal Ludus error in function {}", "internal Ludus error in function {}",
self.frame.function.as_fn().name() self.frame.function.as_fn().name()
); );
@ -1003,7 +1030,7 @@ impl Creature {
let called = self.pop(); let called = self.pop();
if self.debug { if self.debug {
println!( console_log!(
"=== tail call into {called}/{arity} from {} ===", "=== tail call into {called}/{arity} from {} ===",
self.frame.function.as_fn().name() self.frame.function.as_fn().name()
); );
@ -1142,7 +1169,7 @@ impl Creature {
let called = self.pop(); let called = self.pop();
if self.debug { if self.debug {
println!("=== calling into {called}/{arity} ==="); console_log!("=== calling into {called}/{arity} ===");
} }
match called { match called {
@ -1263,7 +1290,7 @@ impl Creature {
} }
Return => { Return => {
if self.debug { if self.debug {
println!("== returning from {} ==", self.frame.function.show()) console_log!("== returning from {} ==", self.frame.function.show())
} }
let mut value = Value::Nothing; let mut value = Value::Nothing;
swap(&mut self.register[0], &mut value); swap(&mut self.register[0], &mut value);
@ -1274,14 +1301,15 @@ impl Creature {
self.push(value); self.push(value);
} }
None => { None => {
println!("process {} has returned with {}", self.pid, value); // console_log!("process {} has returned with {}", self.pid, value);
self.result = Some(Ok(value)); self.result = Some(Ok(value));
self.broadcast_return();
return; return;
} }
} }
} }
Print => { Print => {
println!("{}", self.pop().show()); console_log!("{}", self.pop().show());
self.push(Value::Keyword("ok")); self.push(Value::Keyword("ok"));
} }
SetUpvalue => { SetUpvalue => {

View File

@ -1,9 +1,11 @@
use chumsky::primitive::NoneOf;
use crate::chunk::Chunk; use crate::chunk::Chunk;
use crate::value::{Value, Key}; use crate::value::{Value, Key};
use crate::vm::Creature; use crate::vm::Creature;
use crate::panic::Panic; use crate::panic::Panic;
use crate::errors::panic; use crate::errors::panic;
use crate::js::{random, now}; use crate::js::*;
use crate::io::{MsgOut, MsgIn, do_io}; use crate::io::{MsgOut, MsgIn, do_io};
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
@ -82,6 +84,7 @@ pub struct Zoo {
sleeping: HashMap<&'static str, f64>, sleeping: HashMap<&'static str, f64>,
active_idx: usize, active_idx: usize,
active_id: &'static str, active_id: &'static str,
msgs: Vec<String>,
} }
impl Zoo { impl Zoo {
@ -95,6 +98,7 @@ impl Zoo {
sleeping: HashMap::new(), sleeping: HashMap::new(),
active_idx: 0, active_idx: 0,
active_id: "", active_id: "",
msgs: vec![],
} }
} }
@ -131,6 +135,11 @@ impl Zoo {
} }
} }
pub fn kill_linked(&mut self, id: &'static str) {
self.msgs.push(format!("process :{id} terminated by linked process"));
self.kill_list.push(id);
}
pub fn kill(&mut self, id: &'static str) { pub fn kill(&mut self, id: &'static str) {
self.kill_list.push(id); self.kill_list.push(id);
} }
@ -161,7 +170,7 @@ impl Zoo {
pub fn clean_up(&mut self) { pub fn clean_up(&mut self) {
while let Some(id) = self.kill_list.pop() { while let Some(id) = self.kill_list.pop() {
if let Some(idx) = self.ids.get(id) { if let Some(idx) = self.ids.get(id) {
println!("buried process {id}"); console_log!("process :{id} terminated");
self.procs[*idx] = Status::Empty; self.procs[*idx] = Status::Empty;
self.empty.push(*idx); self.empty.push(*idx);
self.ids.remove(id); self.ids.remove(id);
@ -172,14 +181,14 @@ impl Zoo {
self.sleeping self.sleeping
.retain(|_, wakeup_time| now() < *wakeup_time); .retain(|_, wakeup_time| now() < *wakeup_time);
println!( // console_log!(
"currently sleeping processes: {}", // "currently sleeping processes: {}",
self.sleeping // self.sleeping
.keys() // .keys()
.map(|id| id.to_string()) // .map(|id| id.to_string())
.collect::<Vec<_>>() // .collect::<Vec<_>>()
.join(" | ") // .join(" | ")
); // );
} }
pub fn catch(&mut self, id: &'static str) -> Creature { pub fn catch(&mut self, id: &'static str) -> Creature {
@ -362,9 +371,24 @@ impl World {
if let Some(fetch) = self.make_fetch_happen() { if let Some(fetch) = self.make_fetch_happen() {
outbox.push(fetch); outbox.push(fetch);
} }
if let Some(msgs) = self.flush_zoo() {
outbox.push(msgs);
}
outbox outbox
} }
fn flush_zoo(&mut self) -> Option<MsgOut> {
let mut zoo_msgs = vec![];
let mut zoo = self.zoo.borrow_mut();
swap(&mut zoo_msgs, &mut zoo.msgs);
if zoo_msgs.is_empty() {
None
} else {
let inner = zoo_msgs.into_iter().map(Value::string).collect::<imbl::Vector<_>>();
Some(MsgOut::Console(Value::list(inner)))
}
}
fn make_fetch_happen(&self) -> Option<MsgOut> { fn make_fetch_happen(&self) -> Option<MsgOut> {
let out = self.buffers.fetch_out(); let out = self.buffers.fetch_out();
let working = RefCell::new(Value::Interned("")); let working = RefCell::new(Value::Interned(""));
@ -406,11 +430,13 @@ impl World {
// TODO: if we have a panic, actually add the panic message to the console // TODO: if we have a panic, actually add the panic message to the console
let result = self.active_result().clone().unwrap(); let result = self.active_result().clone().unwrap();
self.result = Some(result.clone()); self.result = Some(result.clone());
let result_msg = match result { match result {
Ok(value) => MsgOut::Complete(Value::string(value.show())), Ok(value) => outbox.push(MsgOut::Complete(Value::string(value.show()))),
Err(p) => MsgOut::Error(panic(p)) Err(p) => {
}; outbox.push(MsgOut::Console(Value::list(imbl::vector!(Value::string("Ludus panicked!".to_string())))));
outbox.push(result_msg); outbox.push(MsgOut::Error(panic(p)))
}
}
outbox outbox
} }
@ -466,8 +492,8 @@ impl World {
fn report_process_end(&mut self) { fn report_process_end(&mut self) {
let result = self.active_result().clone().unwrap(); let result = self.active_result().clone().unwrap();
let msg = match result { let msg = match result {
Ok(value) => format!("process {} returned with {}", self.active_id().unwrap(), value.show()), Ok(value) => format!("Process {} returned with {}", self.active_id().unwrap(), value.show()),
Err(panic) => format!("process {} panicked with {}", self.active_id().unwrap(), crate::errors::panic(panic)) Err(panic) => format!("Process {} panicked with {}", self.active_id().unwrap(), crate::errors::panic(panic))
}; };
self.send_ludus_msg(msg); self.send_ludus_msg(msg);
} }