improve panic message, slightly

Former-commit-id: e4e32bb308
This commit is contained in:
Scott Richmond 2025-07-05 22:40:11 -04:00
parent 5ac87d97b6
commit 795a50a69c
8 changed files with 182 additions and 101 deletions

View File

@ -1035,7 +1035,7 @@ fn spawn! {
}
fn yield! {
"Forces a process to yield."
"Forces a process to yield, allowing other processes to execute."
() -> base :process (:yield)
}
@ -1045,10 +1045,13 @@ fn alive? {
}
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`."
(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)
"Links this process to another process. When either one dies--panics or returns--both are shut down."
(pid as :keyword) -> base :process (:link, pid)
}
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! {
@ -1061,6 +1064,25 @@ fn sleep! {
(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
fn request_fetch! {
(pid as :keyword, url as :string) -> {
@ -1427,7 +1449,11 @@ fn llist {
flush!
& wip actor functions
& link!
link!
monitor!
await!
heed!
spawn_turtle!
& shared memory w/ rust

View File

@ -1249,5 +1249,6 @@ And now, we build capacity for building projects.
* [ ] allow for multiple 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

View File

@ -403,7 +403,7 @@ function __wbg_get_imports() {
_assertBoolean(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);
return ret;
}, 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}");
// panic.call_stack.last().unwrap().chunk().dissasemble();
// 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 {
PanicMsg::Generic(ref s) => s,
_ => &"no match".to_string(),

View File

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

166
src/vm.rs
View File

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

View File

@ -1,9 +1,11 @@
use chumsky::primitive::NoneOf;
use crate::chunk::Chunk;
use crate::value::{Value, Key};
use crate::vm::Creature;
use crate::panic::Panic;
use crate::errors::panic;
use crate::js::{random, now};
use crate::js::*;
use crate::io::{MsgOut, MsgIn, do_io};
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
@ -82,6 +84,7 @@ pub struct Zoo {
sleeping: HashMap<&'static str, f64>,
active_idx: usize,
active_id: &'static str,
msgs: Vec<String>,
}
impl Zoo {
@ -95,6 +98,7 @@ impl Zoo {
sleeping: HashMap::new(),
active_idx: 0,
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) {
self.kill_list.push(id);
}
@ -161,7 +170,7 @@ impl Zoo {
pub fn clean_up(&mut self) {
while let Some(id) = self.kill_list.pop() {
if let Some(idx) = self.ids.get(id) {
println!("buried process {id}");
console_log!("process :{id} terminated");
self.procs[*idx] = Status::Empty;
self.empty.push(*idx);
self.ids.remove(id);
@ -172,14 +181,14 @@ impl Zoo {
self.sleeping
.retain(|_, wakeup_time| now() < *wakeup_time);
println!(
"currently sleeping processes: {}",
self.sleeping
.keys()
.map(|id| id.to_string())
.collect::<Vec<_>>()
.join(" | ")
);
// console_log!(
// "currently sleeping processes: {}",
// self.sleeping
// .keys()
// .map(|id| id.to_string())
// .collect::<Vec<_>>()
// .join(" | ")
// );
}
pub fn catch(&mut self, id: &'static str) -> Creature {
@ -362,9 +371,24 @@ impl World {
if let Some(fetch) = self.make_fetch_happen() {
outbox.push(fetch);
}
if let Some(msgs) = self.flush_zoo() {
outbox.push(msgs);
}
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> {
let out = self.buffers.fetch_out();
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
let result = self.active_result().clone().unwrap();
self.result = Some(result.clone());
let result_msg = match result {
Ok(value) => MsgOut::Complete(Value::string(value.show())),
Err(p) => MsgOut::Error(panic(p))
};
outbox.push(result_msg);
match result {
Ok(value) => outbox.push(MsgOut::Complete(Value::string(value.show()))),
Err(p) => {
outbox.push(MsgOut::Console(Value::list(imbl::vector!(Value::string("Ludus panicked!".to_string())))));
outbox.push(MsgOut::Error(panic(p)))
}
}
outbox
}
@ -466,8 +492,8 @@ impl World {
fn report_process_end(&mut self) {
let result = self.active_result().clone().unwrap();
let msg = match result {
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))
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))
};
self.send_ludus_msg(msg);
}