diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 23ca68f..77e7479 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure303_externref_shim: (a: number, b: number, c: any) => void; - readonly closure327_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure352_externref_shim: (a: number, b: number, c: any) => void; + readonly closure375_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 77903a2..6b47dbe 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -240,13 +240,13 @@ function _assertNum(n) { function __wbg_adapter_20(arg0, arg1, arg2) { _assertNum(arg0); _assertNum(arg1); - wasm.closure303_externref_shim(arg0, arg1, arg2); + wasm.closure352_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_50(arg0, arg1, arg2, arg3) { _assertNum(arg0); _assertNum(arg1); - wasm.closure327_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure375_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -425,8 +425,8 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper7980 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 304, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper8059 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 353, __wbg_adapter_20); return ret; }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 391ce7f..922cfaa 100644 Binary files a/pkg/rudus_bg.wasm and b/pkg/rudus_bg.wasm differ diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 2755b90..c106157 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -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_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure303_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 closure352_externref_shim: (a: number, b: number, c: any) => void; +export const closure375_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/chunk.rs b/src/chunk.rs index 445260e..b451207 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -1,5 +1,6 @@ use crate::op::Op; use crate::value::{Key, Value}; +use chumsky::prelude::SimpleSpan; use imbl::HashMap; use num_traits::FromPrimitive; use regex::Regex; @@ -18,6 +19,9 @@ pub struct Chunk { pub string_patterns: Vec, pub env: HashMap, pub msgs: Vec, + pub spans: Vec, + pub src: &'static str, + pub input: &'static str, } impl std::fmt::Display for Chunk { @@ -32,13 +36,13 @@ impl Chunk { use Op::*; match op { Pop | Store | Stash | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse - | PanicIfNoMatch | ResetMatch | GetKey | PanicNoWhen | PanicNoMatch | TypeOf - | Duplicate | Decrement | ToInt | Noop | LoadTuple | LoadList | Eq | Add | Sub - | Mult | Div | Unbox | BoxStore | Assert | Get | At | Not | Panic | EmptyString - | ConcatStrings | Stringify | MatchType | Return | UnconditionalMatch | Print - | AppendList | ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing - | PushGlobal | SetUpvalue | LoadMessage | NextMessage | MatchMessage | ClearMessage - | SendMethod => { + | ResetMatch | GetKey | PanicWhenFallthrough | PanicNoMatch | PanicNoFnMatch + | PanicNoLetMatch | TypeOf | Duplicate | Decrement | ToInt | Noop | LoadTuple + | LoadList | Eq | Add | Sub | Mult | Div | Unbox | BoxStore | Assert | Get | At + | Not | Panic | EmptyString | ConcatStrings | Stringify | MatchType | Return + | UnconditionalMatch | Print | AppendList | ConcatList | PushList | PushDict + | AppendDict | ConcatDict | Nothing | PushGlobal | SetUpvalue | LoadMessage + | NextMessage | MatchMessage | ClearMessage | SendMethod | LoadScrutinee => { println!("{i:04}: {op}") } Constant | MatchConstant => { diff --git a/src/compiler.rs b/src/compiler.rs index 83a4803..71b5db7 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -40,7 +40,7 @@ impl LoopInfo { } } -fn get_builtin(name: &str, arity: usize) -> Option { +fn get_builtin(_name: &str, _arity: usize) -> Option { // match (name, arity) { // ("type", 1) => Some(Op::TypeOf), // ("eq?", 2) => Some(Op::Eq), @@ -453,13 +453,13 @@ impl Compiler { // return the evaluated rhs instead of whatever is last on the stack // we do this by pretending it's a binding (Let(patt, expr), _) => { - // self.match_depth = 0; self.visit(expr); let expr_pos = self.stack_depth - 1; self.report_ast("let binding: matching".to_string(), patt); self.reset_match(); + self.emit_op(Op::LoadScrutinee); self.visit(patt); - self.emit_op(Op::PanicIfNoMatch); + self.emit_op(Op::PanicNoLetMatch); self.emit_op(Op::PushBinding); self.emit_byte(expr_pos); self.stack_depth += 1; @@ -509,14 +509,13 @@ impl Compiler { } Let(patt, expr) => { self.report_depth("before let binding"); - // self.match_depth = 0; - // self.emit_op(Op::ResetMatch); self.visit(expr); self.report_depth("after let expr"); self.report_ast("let binding: matching".to_string(), patt); self.reset_match(); + self.emit_op(Op::LoadScrutinee); self.visit(patt); - self.emit_op(Op::PanicIfNoMatch); + self.emit_op(Op::PanicNoLetMatch); self.report_depth("after let binding"); } WordPattern(name) => { @@ -760,7 +759,7 @@ impl Compiler { match part { StringPart::Word(word) => { // println!("wordpart: {word}"); - words.push(word.clone()); + words.push(*word); pattern.push_str("(.*)"); } StringPart::Data(data) => { @@ -1012,7 +1011,7 @@ impl Compiler { jump_idxes.push(self.stub_jump(Op::Jump)); 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 { self.patch_jump(idx, self.len() - idx - 3); } @@ -1023,6 +1022,7 @@ impl Compiler { let tail_pos = self.tail_pos; self.tail_pos = false; self.visit(scrutinee.as_ref()); + self.emit_op(Op::LoadScrutinee); let stack_depth = self.stack_depth; let mut jump_idxes = vec![]; let mut clauses = clauses.iter(); diff --git a/src/errors.rs b/src/errors.rs index 2d2b4f3..81313db 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,9 +1,12 @@ // use crate::process::{LErr, Trace}; use crate::lexer::Token; use crate::validator::VErr; +use crate::panic::{Panic, PanicMsg}; +use crate::vm::CallFrame; use chumsky::error::RichPattern; use chumsky::prelude::*; + const SEPARATOR: &str = "\n\n"; 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(" | "); 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 + diff --git a/src/io.rs b/src/io.rs index d278d62..ec9d96d 100644 --- a/src/io.rs +++ b/src/io.rs @@ -1,10 +1,11 @@ use wasm_bindgen::prelude::*; use serde::{Serialize, Deserialize}; use crate::value::Value; -use crate::vm::Panic; use imbl::Vector; use std::rc::Rc; +const OK: Value = Value::Keyword("ok"); +const ERR: Value = Value::Keyword("err"); #[wasm_bindgen(module = "/pkg/worker.js")] extern "C" { @@ -32,41 +33,6 @@ pub enum MsgOut { 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::>().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::>().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)] #[serde(tag = "verb", content = "data")] pub enum MsgIn { @@ -88,7 +54,7 @@ impl std::fmt::Display for MsgIn { } impl MsgIn { - pub fn to_value(self) -> Value { + pub fn into_value(self) -> Value { match self { MsgIn::Input(str) => Value::string(str), MsgIn::Fetch(url, status_f64, string) => { @@ -96,9 +62,9 @@ impl MsgIn { let status = Value::Number(status_f64); let text = Value::string(string); let result_tuple = if status_f64 == 200.0 { - Value::tuple(vec![Value::keyword("ok".to_string()), text]) + Value::tuple(vec![OK, text]) } else { - Value::tuple(vec![Value::keyword("err".to_string()), status]) + Value::tuple(vec![ERR, status]) }; 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) -> Vec { let json = serde_json::to_string(&msgs).unwrap(); let inbox = io (json).await; - // if our request dies, make sure we return back to the event loop let inbox = match inbox { Ok(msgs) => msgs, Err(_) => return vec![] }; let inbox = inbox.as_string().expect("response should be a string"); - // log(format!("got a message: {inbox}")); let inbox: Vec = serde_json::from_str(inbox.as_str()).expect("response from js should be valid"); if !inbox.is_empty() { log("ludus received messages".to_string()); diff --git a/src/lib.rs b/src/lib.rs index 875aacd..d3b1944 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,8 @@ use crate::validator::Validator; mod errors; use crate::errors::{lexing, parsing, validation}; +mod panic; + mod chunk; mod op; @@ -179,16 +181,16 @@ pub async fn ludus(src: String) { let mut world = World::new(vm_chunk, prelude.clone(), DEBUG_SCRIPT_RUN); world.run().await; - let result = world.result.clone(); + // let result = world.result.clone(); // TODO: actually do something useful on a panic - match result { - Some(Ok(val)) => val.show(), - Some(Err(panic)) => format!("Ludus panicked! {panic}"), - None => "Ludus run terminated by user".to_string() - }; - if DEBUG_SCRIPT_RUN { - // vm.print_stack(); - } + // match result { + // Some(Ok(val)) => val.show(), + // Some(Err(panic)) => format!("Ludus panicked! {panic}"), + // None => "Ludus run terminated by user".to_string() + // }; + // if DEBUG_SCRIPT_RUN { + // // vm.print_stack(); + // } } diff --git a/src/main.rs b/src/main.rs index f4be844..7886be9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ -use rudus::value::Value; use std::env; pub fn main() { env::set_var("RUST_BACKTRACE", "1"); + println!("Hello, world.") } diff --git a/src/op.rs b/src/op.rs index 7dbae4f..d2e37ff 100644 --- a/src/op.rs +++ b/src/op.rs @@ -25,7 +25,6 @@ pub enum Op { MatchNil, MatchTrue, MatchFalse, - PanicIfNoMatch, MatchConstant, MatchString, PushStringMatches, @@ -51,10 +50,12 @@ pub enum Op { DropDictEntry, PushBox, GetKey, - PanicNoWhen, + PanicWhenFallthrough, JumpIfNoMatch, JumpIfMatch, PanicNoMatch, + PanicNoLetMatch, + PanicNoFnMatch, TypeOf, JumpBack, JumpIfZero, @@ -82,7 +83,17 @@ pub enum Op { Assert, Get, At, - + // Inc, + // Dec, + // Gt, + // Gte, + // Lt, + // Lte, + // Mod, + // First, + // Rest + // Sqrt, + // Append, Not, Print, SetUpvalue, @@ -95,44 +106,8 @@ pub enum Op { MatchMessage, ClearMessage, SendMethod, - // Inc, - // Dec, - // Gt, - // Gte, - // Lt, - // Lte, - // Mod, - // Round, - // Ceil, - // Floor, - // Random, - // Sqrt, - // Assoc, - // 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 + LoadScrutinee, } impl std::fmt::Display for Op { @@ -163,7 +138,6 @@ impl std::fmt::Display for Op { MatchTrue => "match_true", MatchFalse => "match_false", ResetMatch => "reset_match", - PanicIfNoMatch => "panic_if_no_match", MatchConstant => "match_constant", MatchString => "match_string", PushStringMatches => "push_string_matches", @@ -189,10 +163,12 @@ impl std::fmt::Display for Op { DropDictEntry => "drop_dict_entry", PushBox => "push_box", GetKey => "get_key", - PanicNoWhen => "panic_no_when", + PanicWhenFallthrough => "panic_no_when", JumpIfNoMatch => "jump_if_no_match", JumpIfMatch => "jump_if_match", PanicNoMatch => "panic_no_match", + PanicNoFnMatch => "panic_no_fn_match", + PanicNoLetMatch => "panic_no_let_match", TypeOf => "type_of", JumpBack => "jump_back", JumpIfZero => "jump_if_zero", @@ -232,6 +208,8 @@ impl std::fmt::Display for Op { MatchMessage => "match_message", ClearMessage => "clear_message", SendMethod => "send_method", + + LoadScrutinee => "load_scrutinee", }; write!(f, "{rep}") } diff --git a/src/value.rs b/src/value.rs index 2cdb658..5e7824c 100644 --- a/src/value.rs +++ b/src/value.rs @@ -4,7 +4,6 @@ use imbl::{HashMap, Vector}; use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}; use std::cell::RefCell; use std::rc::Rc; -use wasm_bindgen::JsValue; #[derive(Clone, Debug)] pub enum LFn { @@ -525,10 +524,6 @@ impl Value { Value::String(Rc::new(str)) } - pub fn keyword(str: String) -> Value { - Value::Keyword(str.leak()) - } - pub fn list(list: Vector) -> Value { Value::List(Box::new(list)) } diff --git a/src/vm.rs b/src/vm.rs index 0c5cfb3..7bf7aac 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,8 +1,7 @@ -use crate::ast::Ast; use crate::base::BaseFn; use crate::chunk::Chunk; use crate::op::Op; -use crate::spans::Spanned; +use crate::panic::{Panic, PanicMsg}; use crate::value::{Key, LFn, Value}; use crate::world::Zoo; use imbl::{HashMap, Vector}; @@ -15,31 +14,6 @@ use std::rc::Rc; 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, - pub caller: Spanned, - pub function: Value, - pub arguments: Value, - pub input: &'static str, - pub src: &'static str, -} - #[derive(Debug, Clone, PartialEq)] pub struct CallFrame { pub function: Value, @@ -81,22 +55,23 @@ 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 register: [Value; REGISTER_SIZE], - pub matches: bool, - pub match_depth: u8, + stack: Vec, + call_stack: Vec, + frame: CallFrame, + ip: usize, + register: [Value; REGISTER_SIZE], + matches: bool, + match_depth: u8, pub result: Option>, debug: bool, last_code: usize, pub pid: &'static str, pub mbx: VecDeque, msg_idx: usize, - pub reductions: usize, - pub zoo: Rc>, - pub r#yield: bool, + reductions: usize, + zoo: Rc>, + r#yield: bool, + scrutinee: Option, } impl std::fmt::Display for Creature { @@ -143,6 +118,7 @@ impl Creature { reductions: 0, r#yield: false, msg_idx: 0, + scrutinee: None, } } @@ -209,39 +185,48 @@ impl Creature { self.chunk().dissasemble_instr(&mut ip); } - // pub fn run(&mut self) -> &Result { - // while self.result.is_none() { - // self.interpret(); + // pub fn call_stack(&mut self) -> String { + // let mut stack = format!(" calling {}", self.frame.function.show()); + // 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 { - let mut stack = format!(" calling {}", self.frame.function.show()); - 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}"); - } - stack - } + // pub fn panic(&mut self, msg: &'static str) { + // let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack()); + // println!("process {} panicked!\n{msg}", self.pid); + // self.result = Some(Err(Panic::String(msg))); + // self.r#yield = true; + // } - pub fn panic(&mut self, msg: &'static str) { - let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack()); - println!("process {} panicked!\n{msg}", self.pid); - self.result = Some(Err(Panic::String(msg))); + // pub fn panic_with(&mut self, msg: String) { + // let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack()); + // println!("process {} panicked!\n{msg}", self.pid); + // 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; } - pub fn panic_with(&mut self, msg: String) { - let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack()); - println!("process {} panicked!\n{msg}", self.pid); - self.result = Some(Err(Panic::String(msg))); - self.r#yield = true; + fn panic_with(&mut self, msg: String) { + self.panic(PanicMsg::Generic(msg)); } fn get_value_at(&mut self, idx: u8) -> Value { @@ -286,31 +271,11 @@ impl Creature { fn handle_msg(&mut self, args: Vec) { println!("message received by {}: {}", self.pid, args[0]); 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 { "self" => self.push(Value::Keyword(self.pid)), - "send" => { - 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")); - } + "send" => self.send_msg(args[1].clone(), args[2].clone()), "spawn" => { let f = args[1].clone(); let proc = Creature::spawn(f, self.zoo.clone(), self.debug); @@ -358,22 +323,6 @@ impl Creature { self.r#yield = true; 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::>() - // .join(" | ") - // ); - // self.push(Value::Keyword("ok")); - // } msg => panic!("Process does not understand message: {msg}"), } } @@ -457,7 +406,10 @@ impl Creature { match cond { Value::Number(x) if x <= 0.0 => self.ip += jump_len, Value::Number(..) => (), - _ => return self.panic("repeat requires a number"), + _ => { + return self + .panic_with(format!("repeat requires a number, but got {cond}")) + } } } Pop => { @@ -533,9 +485,19 @@ impl Creature { let value = self.get_scrutinee(); self.matches = value == Value::False; } - PanicIfNoMatch => { + PanicNoMatch => { 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 => { @@ -611,14 +573,18 @@ impl Creature { self.push(member.clone()); } } - _ => return self.panic("internal error: expected tuple"), + _ => { + return self + .panic_with(format!("internal error: expected tuple, got {tuple}")) + } }; } LoadSplattedTuple => { let load_len = self.read() as usize; let tuple = self.get_scrutinee(); 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 { self.push(members[i].clone()); @@ -635,20 +601,24 @@ impl Creature { AppendList => { let value = self.pop(); let list = self.pop(); - let Value::List(mut list) = list else { - return self.panic("only lists may be splatted into lists"); + let Value::List(mut members) = list else { + return self.panic_with(format!( + "only lists may be splatted into lists, but got {list}" + )); }; - list.push_back(value); - self.push(Value::List(list)); + members.push_back(value); + self.push(Value::List(members)); } ConcatList => { - let splatted = self.pop(); + let list = self.pop(); let target = self.pop(); let Value::List(mut target) = target else { unreachable!() }; - let Value::List(splatted) = splatted else { - return self.panic("only lists may be splatted into lists"); + let Value::List(splatted) = list else { + return self.panic_with(format!( + "only lists may be splatted into lists, but got {list}" + )); }; target.append(*splatted); self.push(Value::List(target)); @@ -680,14 +650,18 @@ impl Creature { self.push(member.clone()); } } - _ => return self.panic("internal error: expected list"), + _ => { + return self + .panic_with(format!("internal error: expected list, got {list}")) + } }; } LoadSplattedList => { let loaded_len = self.read() as usize; let list = self.get_scrutinee(); 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 { self.push(members[i].clone()); @@ -708,8 +682,11 @@ impl Creature { self.push(Value::Dict(dict)); } ConcatDict => { - let Value::Dict(splatted) = self.pop() else { - return self.panic("only dicts may be splatted into dicts"); + let prolly_dict = self.pop(); + 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 { unreachable!() @@ -795,7 +772,7 @@ impl Creature { if let Value::Number(x) = val { self.push(Value::Number(x as usize as f64)); } else { - return self.panic("repeat requires a number"); + return self.panic_with(format!("repeat requires a number, but got {val}")); } } Decrement => { @@ -803,7 +780,8 @@ impl Creature { if let Value::Number(x) = val { self.push(Value::Number(x - 1.0)); } 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 => { @@ -812,8 +790,10 @@ impl Creature { MatchDepth => { self.match_depth = self.read(); } - PanicNoWhen | PanicNoMatch => { - return self.panic("no match"); + PanicWhenFallthrough => { + return self.panic_with( + "when form fallthrough: expected one clause to be truthy".to_string(), + ); } Eq => { let first = self.pop(); @@ -827,40 +807,48 @@ impl Creature { Add => { let first = 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)) } else { - return self.panic("`add` requires two numbers"); + return self.panic_with(format!( + "`add` requires two numbers, but got {second}, {first}" + )); } } Sub => { let first = 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)) } else { - return self.panic("`sub` requires two numbers"); + return self.panic_with(format!( + "`sub` requires two numbers, but got {second}, {first}" + )); } } Mult => { let first = 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)) } else { - return self.panic("`mult` requires two numbers"); + return self.panic_with(format!( + "`mult` requires two numbers, but got {second}, {first}" + )); } } Div => { let first = 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 { - return self.panic("division by 0"); + return self.panic_with("division by 0".to_string()); } self.push(Value::Number(y / x)) } else { - return self.panic("`div` requires two numbers"); + return self.panic_with(format!( + "`div` requires two numbers, but got {second}, {first}" + )); } } Unbox => { @@ -868,7 +856,8 @@ impl Creature { let inner = if let Value::Box(b) = the_box { b.borrow().clone() } else { - return self.panic("`unbox` requires a box"); + return self + .panic_with(format!("`unbox` requires a box, but got {the_box}")); }; self.push(inner); } @@ -878,14 +867,15 @@ impl Creature { if let Value::Box(b) = the_box { b.replace(new_value.clone()); } 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); } Assert => { let value = self.stack.last().unwrap(); if let Value::Nil | Value::False = value { - return self.panic("asserted falsy value"); + return self.panic_with("asserted falsy value".to_string()); } } Get => { @@ -894,7 +884,9 @@ impl Creature { key, 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 dict = self.pop(); @@ -907,7 +899,7 @@ impl Creature { At => { let idx = self.pop(); let ordered = self.pop(); - let value = match (ordered, idx) { + let value = match (ordered, idx.clone()) { (Value::List(l), Value::Number(i)) => { 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() } (_, 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); } @@ -958,7 +953,9 @@ impl Creature { let arity = self.read(); let the_fn = self.pop(); 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 partial = crate::value::Partial { @@ -997,7 +994,13 @@ impl Creature { for i in 0..arity as usize { 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 self.stack.truncate(self.frame.stack_base); @@ -1051,20 +1054,13 @@ impl Creature { let x = &self.pop(); 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); - // println!( - // "=== returning to {} ===", - // self.frame.function.as_fn().name() - // ); } Value::Partial(partial) => { let last_arg = self.pop(); @@ -1117,6 +1113,7 @@ impl Creature { called.show() )); } + let splat_arity = called.as_fn().splat_arity(); if splat_arity > 0 && arity >= splat_arity { let splatted_args = self.stack.split_off( @@ -1125,11 +1122,22 @@ impl Creature { let gathered_args = Vector::from(splatted_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 { splat_arity.min(arity) } else { arity }; + let mut frame = CallFrame { function: called, arity, @@ -1158,7 +1166,11 @@ impl Creature { let x = &self.pop(); 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); } @@ -1258,6 +1270,9 @@ impl Creature { } self.send_msg(target, Value::tuple(msg)); } + LoadScrutinee => { + self.scrutinee = Some(self.peek().clone()); + } } } } diff --git a/src/world.rs b/src/world.rs index ec868b2..55ec5b2 100644 --- a/src/world.rs +++ b/src/world.rs @@ -1,6 +1,7 @@ use crate::chunk::Chunk; 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 std::cell::RefCell; use std::collections::{HashMap, HashSet}; @@ -455,7 +456,7 @@ impl World { match msg { MsgIn::Input(str) => self.fill_input(str), MsgIn::Kill => self.kill_signal = true, - MsgIn::Fetch(..) => self.fetch_reply(msg.to_value()), + MsgIn::Fetch(..) => self.fetch_reply(msg.into_value()), _ => todo!() } }