working on panics

This commit is contained in:
Scott Richmond 2025-07-04 01:23:16 -04:00
parent f97f6670bd
commit 0d8b42662b
14 changed files with 270 additions and 274 deletions

4
pkg/rudus.d.ts vendored
View File

@ -14,8 +14,8 @@ export interface InitOutput {
readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_malloc: (a: number, b: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
readonly __wbindgen_export_6: WebAssembly.Table; readonly __wbindgen_export_6: WebAssembly.Table;
readonly closure303_externref_shim: (a: number, b: number, c: any) => void; readonly closure352_externref_shim: (a: number, b: number, c: any) => void;
readonly closure327_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly closure375_externref_shim: (a: number, b: number, c: any, d: any) => void;
readonly __wbindgen_start: () => void; readonly __wbindgen_start: () => void;
} }

View File

@ -240,13 +240,13 @@ function _assertNum(n) {
function __wbg_adapter_20(arg0, arg1, arg2) { function __wbg_adapter_20(arg0, arg1, arg2) {
_assertNum(arg0); _assertNum(arg0);
_assertNum(arg1); _assertNum(arg1);
wasm.closure303_externref_shim(arg0, arg1, arg2); wasm.closure352_externref_shim(arg0, arg1, arg2);
} }
function __wbg_adapter_50(arg0, arg1, arg2, arg3) { function __wbg_adapter_50(arg0, arg1, arg2, arg3) {
_assertNum(arg0); _assertNum(arg0);
_assertNum(arg1); _assertNum(arg1);
wasm.closure327_externref_shim(arg0, arg1, arg2, arg3); wasm.closure375_externref_shim(arg0, arg1, arg2, arg3);
} }
async function __wbg_load(module, imports) { async function __wbg_load(module, imports) {
@ -425,8 +425,8 @@ function __wbg_get_imports() {
_assertBoolean(ret); _assertBoolean(ret);
return ret; return ret;
}; };
imports.wbg.__wbindgen_closure_wrapper7980 = function() { return logError(function (arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper8059 = function() { return logError(function (arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 304, __wbg_adapter_20); const ret = makeMutClosure(arg0, arg1, 353, __wbg_adapter_20);
return ret; return ret;
}, arguments) }; }, arguments) };
imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {

Binary file not shown.

View File

@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void;
export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_malloc: (a: number, b: number) => number;
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
export const __wbindgen_export_6: WebAssembly.Table; export const __wbindgen_export_6: WebAssembly.Table;
export const closure303_externref_shim: (a: number, b: number, c: any) => void; export const closure352_externref_shim: (a: number, b: number, c: any) => void;
export const closure327_externref_shim: (a: number, b: number, c: any, d: any) => void; export const closure375_externref_shim: (a: number, b: number, c: any, d: any) => void;
export const __wbindgen_start: () => void; export const __wbindgen_start: () => void;

View File

@ -1,5 +1,6 @@
use crate::op::Op; use crate::op::Op;
use crate::value::{Key, Value}; use crate::value::{Key, Value};
use chumsky::prelude::SimpleSpan;
use imbl::HashMap; use imbl::HashMap;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use regex::Regex; use regex::Regex;
@ -18,6 +19,9 @@ pub struct Chunk {
pub string_patterns: Vec<StrPattern>, pub string_patterns: Vec<StrPattern>,
pub env: HashMap<Key, Value>, pub env: HashMap<Key, Value>,
pub msgs: Vec<String>, pub msgs: Vec<String>,
pub spans: Vec<SimpleSpan>,
pub src: &'static str,
pub input: &'static str,
} }
impl std::fmt::Display for Chunk { impl std::fmt::Display for Chunk {
@ -32,13 +36,13 @@ impl Chunk {
use Op::*; use Op::*;
match op { match op {
Pop | Store | Stash | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse Pop | Store | Stash | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse
| PanicIfNoMatch | ResetMatch | GetKey | PanicNoWhen | PanicNoMatch | TypeOf | ResetMatch | GetKey | PanicWhenFallthrough | PanicNoMatch | PanicNoFnMatch
| Duplicate | Decrement | ToInt | Noop | LoadTuple | LoadList | Eq | Add | Sub | PanicNoLetMatch | TypeOf | Duplicate | Decrement | ToInt | Noop | LoadTuple
| Mult | Div | Unbox | BoxStore | Assert | Get | At | Not | Panic | EmptyString | LoadList | Eq | Add | Sub | Mult | Div | Unbox | BoxStore | Assert | Get | At
| ConcatStrings | Stringify | MatchType | Return | UnconditionalMatch | Print | Not | Panic | EmptyString | ConcatStrings | Stringify | MatchType | Return
| AppendList | ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing | UnconditionalMatch | Print | AppendList | ConcatList | PushList | PushDict
| PushGlobal | SetUpvalue | LoadMessage | NextMessage | MatchMessage | ClearMessage | AppendDict | ConcatDict | Nothing | PushGlobal | SetUpvalue | LoadMessage
| SendMethod => { | NextMessage | MatchMessage | ClearMessage | SendMethod | LoadScrutinee => {
println!("{i:04}: {op}") println!("{i:04}: {op}")
} }
Constant | MatchConstant => { Constant | MatchConstant => {

View File

@ -40,7 +40,7 @@ impl LoopInfo {
} }
} }
fn get_builtin(name: &str, arity: usize) -> Option<Op> { fn get_builtin(_name: &str, _arity: usize) -> Option<Op> {
// match (name, arity) { // match (name, arity) {
// ("type", 1) => Some(Op::TypeOf), // ("type", 1) => Some(Op::TypeOf),
// ("eq?", 2) => Some(Op::Eq), // ("eq?", 2) => Some(Op::Eq),
@ -453,13 +453,13 @@ impl Compiler {
// return the evaluated rhs instead of whatever is last on the stack // return the evaluated rhs instead of whatever is last on the stack
// we do this by pretending it's a binding // we do this by pretending it's a binding
(Let(patt, expr), _) => { (Let(patt, expr), _) => {
// self.match_depth = 0;
self.visit(expr); self.visit(expr);
let expr_pos = self.stack_depth - 1; let expr_pos = self.stack_depth - 1;
self.report_ast("let binding: matching".to_string(), patt); self.report_ast("let binding: matching".to_string(), patt);
self.reset_match(); self.reset_match();
self.emit_op(Op::LoadScrutinee);
self.visit(patt); self.visit(patt);
self.emit_op(Op::PanicIfNoMatch); self.emit_op(Op::PanicNoLetMatch);
self.emit_op(Op::PushBinding); self.emit_op(Op::PushBinding);
self.emit_byte(expr_pos); self.emit_byte(expr_pos);
self.stack_depth += 1; self.stack_depth += 1;
@ -509,14 +509,13 @@ impl Compiler {
} }
Let(patt, expr) => { Let(patt, expr) => {
self.report_depth("before let binding"); self.report_depth("before let binding");
// self.match_depth = 0;
// self.emit_op(Op::ResetMatch);
self.visit(expr); self.visit(expr);
self.report_depth("after let expr"); self.report_depth("after let expr");
self.report_ast("let binding: matching".to_string(), patt); self.report_ast("let binding: matching".to_string(), patt);
self.reset_match(); self.reset_match();
self.emit_op(Op::LoadScrutinee);
self.visit(patt); self.visit(patt);
self.emit_op(Op::PanicIfNoMatch); self.emit_op(Op::PanicNoLetMatch);
self.report_depth("after let binding"); self.report_depth("after let binding");
} }
WordPattern(name) => { WordPattern(name) => {
@ -760,7 +759,7 @@ impl Compiler {
match part { match part {
StringPart::Word(word) => { StringPart::Word(word) => {
// println!("wordpart: {word}"); // println!("wordpart: {word}");
words.push(word.clone()); words.push(*word);
pattern.push_str("(.*)"); pattern.push_str("(.*)");
} }
StringPart::Data(data) => { StringPart::Data(data) => {
@ -1012,7 +1011,7 @@ impl Compiler {
jump_idxes.push(self.stub_jump(Op::Jump)); jump_idxes.push(self.stub_jump(Op::Jump));
self.patch_jump(jif_jump_idx, self.len() - jif_jump_idx - 3); self.patch_jump(jif_jump_idx, self.len() - jif_jump_idx - 3);
} }
self.emit_op(Op::PanicNoWhen); self.emit_op(Op::PanicWhenFallthrough);
for idx in jump_idxes { for idx in jump_idxes {
self.patch_jump(idx, self.len() - idx - 3); self.patch_jump(idx, self.len() - idx - 3);
} }
@ -1023,6 +1022,7 @@ impl Compiler {
let tail_pos = self.tail_pos; let tail_pos = self.tail_pos;
self.tail_pos = false; self.tail_pos = false;
self.visit(scrutinee.as_ref()); self.visit(scrutinee.as_ref());
self.emit_op(Op::LoadScrutinee);
let stack_depth = self.stack_depth; let stack_depth = self.stack_depth;
let mut jump_idxes = vec![]; let mut jump_idxes = vec![];
let mut clauses = clauses.iter(); let mut clauses = clauses.iter();

View File

@ -1,9 +1,12 @@
// use crate::process::{LErr, Trace}; // use crate::process::{LErr, Trace};
use crate::lexer::Token; use crate::lexer::Token;
use crate::validator::VErr; use crate::validator::VErr;
use crate::panic::{Panic, PanicMsg};
use crate::vm::CallFrame;
use chumsky::error::RichPattern; use chumsky::error::RichPattern;
use chumsky::prelude::*; use chumsky::prelude::*;
const SEPARATOR: &str = "\n\n"; const SEPARATOR: &str = "\n\n";
fn line_number(src: &'static str, span: SimpleSpan) -> usize { fn line_number(src: &'static str, span: SimpleSpan) -> usize {
@ -90,3 +93,37 @@ fn parsing_message(err: Rich<'static, Token>) -> String {
let expecteds = expecteds.join(" | "); let expecteds = expecteds.join(" | ");
format!("Ludus did not expect to see: {found}\n expected: {expecteds}") format!("Ludus did not expect to see: {found}\n expected: {expecteds}")
} }
pub fn panic(panic: Panic, src: &'static str, input: &'static str) -> String {
let msgs = vec!["Ludus panicked!".to_string()];
let msg = match panic.msg {
PanicMsg::Generic(s) => s,
_ => "no match".to_string(),
}
todo!()
}
fn traceback(_panic: Panic) -> String {
todo!()
}
fn frame_info(frame: CallFrame) -> String {
let chunk = frame.chunk();
let CallFrame{function, arity, ip, ..} = frame;
todo!()
}
/////// Some thoughts
// We're putting the information we need on the function and the chunk.
// In the compiler, on functions, build up a vec of strings that are the patterns the function can match against
// The pattern asts have a `show` method.
// And with the additional members on Chunk, we should have everything we need for a pretty fn no match message
// Let no match is no problem, either. We should have no concerns pulling the line with the span start and string
// We don't need to reproduce the pattern, since it will be right there in the code
// As for match forms, we'll just use "no match" and print the value

View File

@ -1,10 +1,11 @@
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use crate::value::Value; use crate::value::Value;
use crate::vm::Panic;
use imbl::Vector; use imbl::Vector;
use std::rc::Rc; use std::rc::Rc;
const OK: Value = Value::Keyword("ok");
const ERR: Value = Value::Keyword("err");
#[wasm_bindgen(module = "/pkg/worker.js")] #[wasm_bindgen(module = "/pkg/worker.js")]
extern "C" { extern "C" {
@ -32,41 +33,6 @@ pub enum MsgOut {
Ready Ready
} }
// impl MsgOut {
// pub fn to_json(&self) -> String {
// match self {
// MsgOut::Complete(value) => match value {
// Ok(value) => {
// make_json_payload("Complete", serde_json::to_string(&value.show()).unwrap())
// },
// Err(_) => make_json_payload("Complete", "\"null\"".to_string())
// },
// MsgOut::Commands(commands) => {
// let commands = commands.as_list();
// let vals_json = commands.iter().map(|v| v.to_json().unwrap()).collect::<Vec<_>>().join(",");
// let vals_json = format!("[{vals_json}]");
// make_json_payload("Commands", vals_json)
// }
// MsgOut::Fetch(value) => {
// // TODO: do parsing here?
// // Right now, defer to fetch
// let url = value.to_json().unwrap();
// make_json_payload("Fetch", url)
// }
// MsgOut::Console(lines) => {
// let lines = lines.as_list();
// let json_lines = lines.iter().map(|line| line.to_json().unwrap()).collect::<Vec<_>>().join("\\n");
// let json_lines = format!("\"{json_lines}\"");
// make_json_payload("Console", json_lines)
// }
// MsgOut::Ready => {
// make_json_payload("Ready", "\"null\"".to_string())
// }
// }
// }
// }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "verb", content = "data")] #[serde(tag = "verb", content = "data")]
pub enum MsgIn { pub enum MsgIn {
@ -88,7 +54,7 @@ impl std::fmt::Display for MsgIn {
} }
impl MsgIn { impl MsgIn {
pub fn to_value(self) -> Value { pub fn into_value(self) -> Value {
match self { match self {
MsgIn::Input(str) => Value::string(str), MsgIn::Input(str) => Value::string(str),
MsgIn::Fetch(url, status_f64, string) => { MsgIn::Fetch(url, status_f64, string) => {
@ -96,9 +62,9 @@ impl MsgIn {
let status = Value::Number(status_f64); let status = Value::Number(status_f64);
let text = Value::string(string); let text = Value::string(string);
let result_tuple = if status_f64 == 200.0 { let result_tuple = if status_f64 == 200.0 {
Value::tuple(vec![Value::keyword("ok".to_string()), text]) Value::tuple(vec![OK, text])
} else { } else {
Value::tuple(vec![Value::keyword("err".to_string()), status]) Value::tuple(vec![ERR, status])
}; };
Value::tuple(vec![url, result_tuple]) Value::tuple(vec![url, result_tuple])
} }
@ -122,13 +88,11 @@ pub async fn send_err_to_ludus_console(msg: String) {
pub async fn do_io (msgs: Vec<MsgOut>) -> Vec<MsgIn> { pub async fn do_io (msgs: Vec<MsgOut>) -> Vec<MsgIn> {
let json = serde_json::to_string(&msgs).unwrap(); let json = serde_json::to_string(&msgs).unwrap();
let inbox = io (json).await; let inbox = io (json).await;
// if our request dies, make sure we return back to the event loop
let inbox = match inbox { let inbox = match inbox {
Ok(msgs) => msgs, Ok(msgs) => msgs,
Err(_) => return vec![] Err(_) => return vec![]
}; };
let inbox = inbox.as_string().expect("response should be a string"); let inbox = inbox.as_string().expect("response should be a string");
// log(format!("got a message: {inbox}"));
let inbox: Vec<MsgIn> = serde_json::from_str(inbox.as_str()).expect("response from js should be valid"); let inbox: Vec<MsgIn> = serde_json::from_str(inbox.as_str()).expect("response from js should be valid");
if !inbox.is_empty() { if !inbox.is_empty() {
log("ludus received messages".to_string()); log("ludus received messages".to_string());

View File

@ -40,6 +40,8 @@ use crate::validator::Validator;
mod errors; mod errors;
use crate::errors::{lexing, parsing, validation}; use crate::errors::{lexing, parsing, validation};
mod panic;
mod chunk; mod chunk;
mod op; mod op;
@ -179,16 +181,16 @@ pub async fn ludus(src: String) {
let mut world = World::new(vm_chunk, prelude.clone(), DEBUG_SCRIPT_RUN); let mut world = World::new(vm_chunk, prelude.clone(), DEBUG_SCRIPT_RUN);
world.run().await; world.run().await;
let result = world.result.clone(); // let result = world.result.clone();
// TODO: actually do something useful on a panic // TODO: actually do something useful on a panic
match result { // match result {
Some(Ok(val)) => val.show(), // Some(Ok(val)) => val.show(),
Some(Err(panic)) => format!("Ludus panicked! {panic}"), // Some(Err(panic)) => format!("Ludus panicked! {panic}"),
None => "Ludus run terminated by user".to_string() // None => "Ludus run terminated by user".to_string()
}; // };
if DEBUG_SCRIPT_RUN { // if DEBUG_SCRIPT_RUN {
// vm.print_stack(); // // vm.print_stack();
} // }
} }

View File

@ -1,6 +1,6 @@
use rudus::value::Value;
use std::env; use std::env;
pub fn main() { pub fn main() {
env::set_var("RUST_BACKTRACE", "1"); env::set_var("RUST_BACKTRACE", "1");
println!("Hello, world.")
} }

View File

@ -25,7 +25,6 @@ pub enum Op {
MatchNil, MatchNil,
MatchTrue, MatchTrue,
MatchFalse, MatchFalse,
PanicIfNoMatch,
MatchConstant, MatchConstant,
MatchString, MatchString,
PushStringMatches, PushStringMatches,
@ -51,10 +50,12 @@ pub enum Op {
DropDictEntry, DropDictEntry,
PushBox, PushBox,
GetKey, GetKey,
PanicNoWhen, PanicWhenFallthrough,
JumpIfNoMatch, JumpIfNoMatch,
JumpIfMatch, JumpIfMatch,
PanicNoMatch, PanicNoMatch,
PanicNoLetMatch,
PanicNoFnMatch,
TypeOf, TypeOf,
JumpBack, JumpBack,
JumpIfZero, JumpIfZero,
@ -82,7 +83,17 @@ pub enum Op {
Assert, Assert,
Get, Get,
At, At,
// Inc,
// Dec,
// Gt,
// Gte,
// Lt,
// Lte,
// Mod,
// First,
// Rest
// Sqrt,
// Append,
Not, Not,
Print, Print,
SetUpvalue, SetUpvalue,
@ -95,44 +106,8 @@ pub enum Op {
MatchMessage, MatchMessage,
ClearMessage, ClearMessage,
SendMethod, SendMethod,
// Inc,
// Dec,
// Gt,
// Gte,
// Lt,
// Lte,
// Mod,
// Round,
// Ceil,
// Floor,
// Random,
// Sqrt,
// Assoc, LoadScrutinee,
// Concat,
// Conj,
// Count,
// Disj,
// Dissoc,
// Range,
// Rest,
// Slice,
// "atan_2" math/atan2
// "chars" chars
// "cos" math/cos
// "doc" doc
// "downcase" string/ascii-lower
// "pi" math/pi
// "show" show
// "sin" math/sin
// "split" string/split
// "str_slice" string/slice
// "tan" math/tan
// "trim" string/trim
// "triml" string/triml
// "trimr" string/trimr
// "upcase" string/ascii-upper
} }
impl std::fmt::Display for Op { impl std::fmt::Display for Op {
@ -163,7 +138,6 @@ impl std::fmt::Display for Op {
MatchTrue => "match_true", MatchTrue => "match_true",
MatchFalse => "match_false", MatchFalse => "match_false",
ResetMatch => "reset_match", ResetMatch => "reset_match",
PanicIfNoMatch => "panic_if_no_match",
MatchConstant => "match_constant", MatchConstant => "match_constant",
MatchString => "match_string", MatchString => "match_string",
PushStringMatches => "push_string_matches", PushStringMatches => "push_string_matches",
@ -189,10 +163,12 @@ impl std::fmt::Display for Op {
DropDictEntry => "drop_dict_entry", DropDictEntry => "drop_dict_entry",
PushBox => "push_box", PushBox => "push_box",
GetKey => "get_key", GetKey => "get_key",
PanicNoWhen => "panic_no_when", PanicWhenFallthrough => "panic_no_when",
JumpIfNoMatch => "jump_if_no_match", JumpIfNoMatch => "jump_if_no_match",
JumpIfMatch => "jump_if_match", JumpIfMatch => "jump_if_match",
PanicNoMatch => "panic_no_match", PanicNoMatch => "panic_no_match",
PanicNoFnMatch => "panic_no_fn_match",
PanicNoLetMatch => "panic_no_let_match",
TypeOf => "type_of", TypeOf => "type_of",
JumpBack => "jump_back", JumpBack => "jump_back",
JumpIfZero => "jump_if_zero", JumpIfZero => "jump_if_zero",
@ -232,6 +208,8 @@ impl std::fmt::Display for Op {
MatchMessage => "match_message", MatchMessage => "match_message",
ClearMessage => "clear_message", ClearMessage => "clear_message",
SendMethod => "send_method", SendMethod => "send_method",
LoadScrutinee => "load_scrutinee",
}; };
write!(f, "{rep}") write!(f, "{rep}")
} }

View File

@ -4,7 +4,6 @@ use imbl::{HashMap, Vector};
use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}; use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer};
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use wasm_bindgen::JsValue;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum LFn { pub enum LFn {
@ -525,10 +524,6 @@ impl Value {
Value::String(Rc::new(str)) Value::String(Rc::new(str))
} }
pub fn keyword(str: String) -> Value {
Value::Keyword(str.leak())
}
pub fn list(list: Vector<Value>) -> Value { pub fn list(list: Vector<Value>) -> Value {
Value::List(Box::new(list)) Value::List(Box::new(list))
} }

317
src/vm.rs
View File

@ -1,8 +1,7 @@
use crate::ast::Ast;
use crate::base::BaseFn; use crate::base::BaseFn;
use crate::chunk::Chunk; use crate::chunk::Chunk;
use crate::op::Op; use crate::op::Op;
use crate::spans::Spanned; use crate::panic::{Panic, PanicMsg};
use crate::value::{Key, LFn, Value}; use crate::value::{Key, LFn, Value};
use crate::world::Zoo; use crate::world::Zoo;
use imbl::{HashMap, Vector}; use imbl::{HashMap, Vector};
@ -15,31 +14,6 @@ use std::rc::Rc;
const MAX_REDUCTIONS: usize = 1000; const MAX_REDUCTIONS: usize = 1000;
#[derive(Debug, Clone, PartialEq)]
pub enum Panic {
Str(&'static str),
String(String),
}
impl fmt::Display for Panic {
fn fmt(self: &Panic, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Panic::Str(msg) => write!(f, "{msg}"),
Panic::String(msg) => write!(f, "{msg}"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Trace {
pub callee: Spanned<Ast>,
pub caller: Spanned<Ast>,
pub function: Value,
pub arguments: Value,
pub input: &'static str,
pub src: &'static str,
}
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct CallFrame { pub struct CallFrame {
pub function: Value, pub function: Value,
@ -81,22 +55,23 @@ const REGISTER_SIZE: usize = 8;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Creature { pub struct Creature {
pub stack: Vec<Value>, stack: Vec<Value>,
pub call_stack: Vec<CallFrame>, call_stack: Vec<CallFrame>,
pub frame: CallFrame, frame: CallFrame,
pub ip: usize, ip: usize,
pub register: [Value; REGISTER_SIZE], register: [Value; REGISTER_SIZE],
pub matches: bool, matches: bool,
pub match_depth: u8, match_depth: u8,
pub result: Option<Result<Value, Panic>>, pub result: Option<Result<Value, Panic>>,
debug: bool, debug: bool,
last_code: usize, last_code: usize,
pub pid: &'static str, pub pid: &'static str,
pub mbx: VecDeque<Value>, pub mbx: VecDeque<Value>,
msg_idx: usize, msg_idx: usize,
pub reductions: usize, reductions: usize,
pub zoo: Rc<RefCell<Zoo>>, zoo: Rc<RefCell<Zoo>>,
pub r#yield: bool, r#yield: bool,
scrutinee: Option<Value>,
} }
impl std::fmt::Display for Creature { impl std::fmt::Display for Creature {
@ -143,6 +118,7 @@ impl Creature {
reductions: 0, reductions: 0,
r#yield: false, r#yield: false,
msg_idx: 0, msg_idx: 0,
scrutinee: None,
} }
} }
@ -209,39 +185,48 @@ impl Creature {
self.chunk().dissasemble_instr(&mut ip); self.chunk().dissasemble_instr(&mut ip);
} }
// pub fn run(&mut self) -> &Result<Value, Panic> { // pub fn call_stack(&mut self) -> String {
// while self.result.is_none() { // let mut stack = format!(" calling {}", self.frame.function.show());
// self.interpret(); // for frame in self.call_stack.iter().rev() {
// let mut name = frame.function.show();
// name = if name == "fn user script" {
// "user script".to_string()
// } else {
// name
// };
// stack = format!("{stack}\n from {name}");
// } // }
// self.result.as_ref().unwrap() // stack
// } // }
pub fn call_stack(&mut self) -> String { // pub fn panic(&mut self, msg: &'static str) {
let mut stack = format!(" calling {}", self.frame.function.show()); // let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack());
for frame in self.call_stack.iter().rev() { // println!("process {} panicked!\n{msg}", self.pid);
let mut name = frame.function.show(); // self.result = Some(Err(Panic::String(msg)));
name = if name == "fn user script" { // self.r#yield = true;
"user script".to_string() // }
} else {
name
};
stack = format!("{stack}\n from {name}");
}
stack
}
pub fn panic(&mut self, msg: &'static str) { // pub fn panic_with(&mut self, msg: String) {
let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack()); // let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack());
println!("process {} panicked!\n{msg}", self.pid); // println!("process {} panicked!\n{msg}", self.pid);
self.result = Some(Err(Panic::String(msg))); // self.result = Some(Err(Panic::String(msg)));
// self.r#yield = true;
// }
fn panic(&mut self, msg: PanicMsg) {
let panic = Panic {
msg,
frame: self.frame.clone(),
scrutinee: self.scrutinee.clone(),
ip: self.ip,
call_stack: self.call_stack.clone(),
};
self.result = Some(Err(panic));
self.r#yield = true; self.r#yield = true;
} }
pub fn panic_with(&mut self, msg: String) { fn panic_with(&mut self, msg: String) {
let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack()); self.panic(PanicMsg::Generic(msg));
println!("process {} panicked!\n{msg}", self.pid);
self.result = Some(Err(Panic::String(msg)));
self.r#yield = true;
} }
fn get_value_at(&mut self, idx: u8) -> Value { fn get_value_at(&mut self, idx: u8) -> Value {
@ -286,31 +271,11 @@ impl Creature {
fn handle_msg(&mut self, args: Vec<Value>) { fn handle_msg(&mut self, args: Vec<Value>) {
println!("message received by {}: {}", self.pid, args[0]); println!("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("malformed message to Process"); return self.panic_with("malformed message to Process".to_string());
}; };
match *msg { match *msg {
"self" => self.push(Value::Keyword(self.pid)), "self" => self.push(Value::Keyword(self.pid)),
"send" => { "send" => self.send_msg(args[1].clone(), args[2].clone()),
self.send_msg(args[1].clone(), args[2].clone())
// let Value::Keyword(pid) = args[1] else {
// return self.panic("malformed pid");
// };
// println!(
// "sending msg from {} to {} of {}",
// self.pid,
// pid,
// args[2].show()
// );
// if self.pid == pid {
// self.mbx.push_back(args[2].clone());
// } else {
// self.zoo
// .as_ref()
// .borrow_mut()
// .send_msg(pid, args[2].clone());
// }
// self.push(Value::Keyword("ok"));
}
"spawn" => { "spawn" => {
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);
@ -358,22 +323,6 @@ impl Creature {
self.r#yield = true; self.r#yield = true;
self.push(Value::Keyword("ok")); self.push(Value::Keyword("ok"));
} }
// "flush_i" => {
// let Value::Number(n) = args[1] else {
// unreachable!()
// };
// println!("flushing message at {n}");
// self.mbx.remove(n as usize);
// println!(
// "mailbox is now: {}",
// self.mbx
// .iter()
// .map(|msg| msg.to_string())
// .collect::<Vec<_>>()
// .join(" | ")
// );
// self.push(Value::Keyword("ok"));
// }
msg => panic!("Process does not understand message: {msg}"), msg => panic!("Process does not understand message: {msg}"),
} }
} }
@ -457,7 +406,10 @@ impl Creature {
match cond { match cond {
Value::Number(x) if x <= 0.0 => self.ip += jump_len, Value::Number(x) if x <= 0.0 => self.ip += jump_len,
Value::Number(..) => (), Value::Number(..) => (),
_ => return self.panic("repeat requires a number"), _ => {
return self
.panic_with(format!("repeat requires a number, but got {cond}"))
}
} }
} }
Pop => { Pop => {
@ -533,9 +485,19 @@ impl Creature {
let value = self.get_scrutinee(); let value = self.get_scrutinee();
self.matches = value == Value::False; self.matches = value == Value::False;
} }
PanicIfNoMatch => { PanicNoMatch => {
if !self.matches { if !self.matches {
return self.panic("no match"); return self.panic(PanicMsg::NoMatch);
}
}
PanicNoLetMatch => {
if !self.matches {
return self.panic(PanicMsg::NoLetMatch);
}
}
PanicNoFnMatch => {
if !self.matches {
return self.panic(PanicMsg::NoFnMatch);
} }
} }
MatchConstant => { MatchConstant => {
@ -611,14 +573,18 @@ impl Creature {
self.push(member.clone()); self.push(member.clone());
} }
} }
_ => return self.panic("internal error: expected tuple"), _ => {
return self
.panic_with(format!("internal error: expected tuple, got {tuple}"))
}
}; };
} }
LoadSplattedTuple => { LoadSplattedTuple => {
let load_len = self.read() as usize; let load_len = self.read() as usize;
let tuple = self.get_scrutinee(); let tuple = self.get_scrutinee();
let Value::Tuple(members) = tuple else { let Value::Tuple(members) = tuple else {
return self.panic("internal error: expected tuple"); return self
.panic_with(format!("internal error: expected tuple, got {tuple}"));
}; };
for i in 0..load_len - 1 { for i in 0..load_len - 1 {
self.push(members[i].clone()); self.push(members[i].clone());
@ -635,20 +601,24 @@ impl Creature {
AppendList => { AppendList => {
let value = self.pop(); let value = self.pop();
let list = self.pop(); let list = self.pop();
let Value::List(mut list) = list else { let Value::List(mut members) = list else {
return self.panic("only lists may be splatted into lists"); return self.panic_with(format!(
"only lists may be splatted into lists, but got {list}"
));
}; };
list.push_back(value); members.push_back(value);
self.push(Value::List(list)); self.push(Value::List(members));
} }
ConcatList => { ConcatList => {
let splatted = self.pop(); let list = self.pop();
let target = self.pop(); let target = self.pop();
let Value::List(mut target) = target else { let Value::List(mut target) = target else {
unreachable!() unreachable!()
}; };
let Value::List(splatted) = splatted else { let Value::List(splatted) = list else {
return self.panic("only lists may be splatted into lists"); return self.panic_with(format!(
"only lists may be splatted into lists, but got {list}"
));
}; };
target.append(*splatted); target.append(*splatted);
self.push(Value::List(target)); self.push(Value::List(target));
@ -680,14 +650,18 @@ impl Creature {
self.push(member.clone()); self.push(member.clone());
} }
} }
_ => return self.panic("internal error: expected list"), _ => {
return self
.panic_with(format!("internal error: expected list, got {list}"))
}
}; };
} }
LoadSplattedList => { LoadSplattedList => {
let loaded_len = self.read() as usize; let loaded_len = self.read() as usize;
let list = self.get_scrutinee(); let list = self.get_scrutinee();
let Value::List(members) = list else { let Value::List(members) = list else {
return self.panic("internal error: expected list"); return self
.panic_with(format!("internal error: expected list, got {list}"));
}; };
for i in 0..loaded_len - 1 { for i in 0..loaded_len - 1 {
self.push(members[i].clone()); self.push(members[i].clone());
@ -708,8 +682,11 @@ impl Creature {
self.push(Value::Dict(dict)); self.push(Value::Dict(dict));
} }
ConcatDict => { ConcatDict => {
let Value::Dict(splatted) = self.pop() else { let prolly_dict = self.pop();
return self.panic("only dicts may be splatted into dicts"); let Value::Dict(splatted) = prolly_dict else {
return self.panic_with(format!(
"only dicts may be splatted into dicts, got {prolly_dict}"
));
}; };
let Value::Dict(target) = self.pop() else { let Value::Dict(target) = self.pop() else {
unreachable!() unreachable!()
@ -795,7 +772,7 @@ impl Creature {
if let Value::Number(x) = val { if let Value::Number(x) = val {
self.push(Value::Number(x as usize as f64)); self.push(Value::Number(x as usize as f64));
} else { } else {
return self.panic("repeat requires a number"); return self.panic_with(format!("repeat requires a number, but got {val}"));
} }
} }
Decrement => { Decrement => {
@ -803,7 +780,8 @@ impl Creature {
if let Value::Number(x) = val { if let Value::Number(x) = val {
self.push(Value::Number(x - 1.0)); self.push(Value::Number(x - 1.0));
} else { } else {
return self.panic("you may only decrement a number"); return self
.panic_with(format!("you may only decrement a number, but got {val}"));
} }
} }
Duplicate => { Duplicate => {
@ -812,8 +790,10 @@ impl Creature {
MatchDepth => { MatchDepth => {
self.match_depth = self.read(); self.match_depth = self.read();
} }
PanicNoWhen | PanicNoMatch => { PanicWhenFallthrough => {
return self.panic("no match"); return self.panic_with(
"when form fallthrough: expected one clause to be truthy".to_string(),
);
} }
Eq => { Eq => {
let first = self.pop(); let first = self.pop();
@ -827,40 +807,48 @@ impl Creature {
Add => { Add => {
let first = self.pop(); let first = self.pop();
let second = self.pop(); let second = self.pop();
if let (Value::Number(x), Value::Number(y)) = (first, second) { if let (Value::Number(x), Value::Number(y)) = (first.clone(), second.clone()) {
self.push(Value::Number(x + y)) self.push(Value::Number(x + y))
} else { } else {
return self.panic("`add` requires two numbers"); return self.panic_with(format!(
"`add` requires two numbers, but got {second}, {first}"
));
} }
} }
Sub => { Sub => {
let first = self.pop(); let first = self.pop();
let second = self.pop(); let second = self.pop();
if let (Value::Number(x), Value::Number(y)) = (first, second) { if let (Value::Number(x), Value::Number(y)) = (first.clone(), second.clone()) {
self.push(Value::Number(y - x)) self.push(Value::Number(y - x))
} else { } else {
return self.panic("`sub` requires two numbers"); return self.panic_with(format!(
"`sub` requires two numbers, but got {second}, {first}"
));
} }
} }
Mult => { Mult => {
let first = self.pop(); let first = self.pop();
let second = self.pop(); let second = self.pop();
if let (Value::Number(x), Value::Number(y)) = (first, second) { if let (Value::Number(x), Value::Number(y)) = (first.clone(), second.clone()) {
self.push(Value::Number(x * y)) self.push(Value::Number(x * y))
} else { } else {
return self.panic("`mult` requires two numbers"); return self.panic_with(format!(
"`mult` requires two numbers, but got {second}, {first}"
));
} }
} }
Div => { Div => {
let first = self.pop(); let first = self.pop();
let second = self.pop(); let second = self.pop();
if let (Value::Number(x), Value::Number(y)) = (first, second) { if let (Value::Number(x), Value::Number(y)) = (first.clone(), second.clone()) {
if x == 0.0 { if x == 0.0 {
return self.panic("division by 0"); return self.panic_with("division by 0".to_string());
} }
self.push(Value::Number(y / x)) self.push(Value::Number(y / x))
} else { } else {
return self.panic("`div` requires two numbers"); return self.panic_with(format!(
"`div` requires two numbers, but got {second}, {first}"
));
} }
} }
Unbox => { Unbox => {
@ -868,7 +856,8 @@ impl Creature {
let inner = if let Value::Box(b) = the_box { let inner = if let Value::Box(b) = the_box {
b.borrow().clone() b.borrow().clone()
} else { } else {
return self.panic("`unbox` requires a box"); return self
.panic_with(format!("`unbox` requires a box, but got {the_box}"));
}; };
self.push(inner); self.push(inner);
} }
@ -878,14 +867,15 @@ impl Creature {
if let Value::Box(b) = the_box { if let Value::Box(b) = the_box {
b.replace(new_value.clone()); b.replace(new_value.clone());
} else { } else {
return self.panic("`store` requires a box"); return self
.panic_with(format!("`store` requires a box, but got {the_box}"));
} }
self.push(new_value); self.push(new_value);
} }
Assert => { Assert => {
let value = self.stack.last().unwrap(); let value = self.stack.last().unwrap();
if let Value::Nil | Value::False = value { if let Value::Nil | Value::False = value {
return self.panic("asserted falsy value"); return self.panic_with("asserted falsy value".to_string());
} }
} }
Get => { Get => {
@ -894,7 +884,9 @@ impl Creature {
key, key,
Value::Keyword(_) | Value::String(_) | Value::Interned(_) Value::Keyword(_) | Value::String(_) | Value::Interned(_)
) { ) {
return self.panic("keys must be keywords"); return self.panic_with(format!(
"dict keys must be keywords or strings, but got {key}"
));
} }
let key = Key::from_value(key); let key = Key::from_value(key);
let dict = self.pop(); let dict = self.pop();
@ -907,7 +899,7 @@ impl Creature {
At => { At => {
let idx = self.pop(); let idx = self.pop();
let ordered = self.pop(); let ordered = self.pop();
let value = match (ordered, idx) { let value = match (ordered, idx.clone()) {
(Value::List(l), Value::Number(i)) => { (Value::List(l), Value::Number(i)) => {
l.get(i as usize).unwrap_or(&Value::Nil).clone() l.get(i as usize).unwrap_or(&Value::Nil).clone()
} }
@ -915,7 +907,10 @@ impl Creature {
t.get(i as usize).unwrap_or(&Value::Nil).clone() t.get(i as usize).unwrap_or(&Value::Nil).clone()
} }
(_, Value::Number(_)) => Value::Nil, (_, Value::Number(_)) => Value::Nil,
_ => return self.panic("indexes must be numbers"), _ => {
return self
.panic_with(format!("indexes must be numbers, but got {idx}"))
}
}; };
self.push(value); self.push(value);
} }
@ -958,7 +953,9 @@ impl Creature {
let arity = self.read(); let arity = self.read();
let the_fn = self.pop(); let the_fn = self.pop();
let Value::Fn(ref inner) = the_fn else { let Value::Fn(ref inner) = the_fn else {
return self.panic("only functions may be partially applied"); return self.panic_with(format!(
"only functions may be partially applied, but got {the_fn}"
));
}; };
let args = self.stack.split_off(self.stack.len() - arity as usize); let args = self.stack.split_off(self.stack.len() - arity as usize);
let partial = crate::value::Partial { let partial = crate::value::Partial {
@ -997,7 +994,13 @@ impl Creature {
for i in 0..arity as usize { for i in 0..arity as usize {
self.register[arity as usize - i - 1] = self.pop(); self.register[arity as usize - i - 1] = self.pop();
} }
// self.print_stack();
// save the arguments as our scrutinee
let mut scrutinee = vec![];
for i in 0..arity as usize {
scrutinee.push(self.register[i].clone())
}
self.scrutinee = Some(Value::tuple(scrutinee));
// then pop everything back to the current stack frame // then pop everything back to the current stack frame
self.stack.truncate(self.frame.stack_base); self.stack.truncate(self.frame.stack_base);
@ -1051,20 +1054,13 @@ impl Creature {
let x = &self.pop(); let x = &self.pop();
f(x, y, z) f(x, y, z)
} }
_ => return self.panic("internal ludus error"), _ => {
return self.panic_with(
"internal ludus error: bad base fn call".to_string(),
)
}
}; };
// // algo:
// // clear the stack
// self.stack.truncate(self.frame.stack_base);
// // then pop back out to the enclosing stack frame
// self.frame = self.call_stack.pop().unwrap();
// self.ip = self.frame.ip;
// // finally, throw the value on the stack
self.push(value); self.push(value);
// println!(
// "=== returning to {} ===",
// self.frame.function.as_fn().name()
// );
} }
Value::Partial(partial) => { Value::Partial(partial) => {
let last_arg = self.pop(); let last_arg = self.pop();
@ -1117,6 +1113,7 @@ impl Creature {
called.show() called.show()
)); ));
} }
let splat_arity = called.as_fn().splat_arity(); let splat_arity = called.as_fn().splat_arity();
if splat_arity > 0 && arity >= splat_arity { if splat_arity > 0 && arity >= splat_arity {
let splatted_args = self.stack.split_off( let splatted_args = self.stack.split_off(
@ -1125,11 +1122,22 @@ impl Creature {
let gathered_args = Vector::from(splatted_args); let gathered_args = Vector::from(splatted_args);
self.push(Value::List(Box::new(gathered_args))); self.push(Value::List(Box::new(gathered_args)));
} }
let mut scrutinee = vec![];
for i in 0..arity {
scrutinee.push(
self.stack[self.stack.len() - arity as usize + i as usize]
.clone(),
)
}
self.scrutinee = Some(Value::tuple(scrutinee));
let arity = if splat_arity > 0 { let arity = if splat_arity > 0 {
splat_arity.min(arity) splat_arity.min(arity)
} else { } else {
arity arity
}; };
let mut frame = CallFrame { let mut frame = CallFrame {
function: called, function: called,
arity, arity,
@ -1158,7 +1166,11 @@ impl Creature {
let x = &self.pop(); let x = &self.pop();
f(x, y, z) f(x, y, z)
} }
_ => return self.panic("internal ludus error"), _ => {
return self.panic_with(
"internal ludus error: bad base fn call".to_string(),
)
}
}; };
self.push(value); self.push(value);
} }
@ -1258,6 +1270,9 @@ impl Creature {
} }
self.send_msg(target, Value::tuple(msg)); self.send_msg(target, Value::tuple(msg));
} }
LoadScrutinee => {
self.scrutinee = Some(self.peek().clone());
}
} }
} }
} }

View File

@ -1,6 +1,7 @@
use crate::chunk::Chunk; use crate::chunk::Chunk;
use crate::value::{Value, Key}; use crate::value::{Value, Key};
use crate::vm::{Creature, Panic}; use crate::vm::Creature;
use crate::panic::Panic;
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};
@ -455,7 +456,7 @@ impl World {
match msg { match msg {
MsgIn::Input(str) => self.fill_input(str), MsgIn::Input(str) => self.fill_input(str),
MsgIn::Kill => self.kill_signal = true, MsgIn::Kill => self.kill_signal = true,
MsgIn::Fetch(..) => self.fetch_reply(msg.to_value()), MsgIn::Fetch(..) => self.fetch_reply(msg.into_value()),
_ => todo!() _ => todo!()
} }
} }