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_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;
}

View File

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

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_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;

View File

@ -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<StrPattern>,
pub env: HashMap<Key, Value>,
pub msgs: Vec<String>,
pub spans: Vec<SimpleSpan>,
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 => {

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) {
// ("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();

View File

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

View File

@ -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::<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)]
#[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<MsgOut>) -> Vec<MsgIn> {
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<MsgIn> = serde_json::from_str(inbox.as_str()).expect("response from js should be valid");
if !inbox.is_empty() {
log("ludus received messages".to_string());

View File

@ -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();
// }
}

View File

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

View File

@ -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}")
}

View File

@ -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 {
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::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<Ast>,
pub caller: Spanned<Ast>,
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<Value>,
pub call_stack: Vec<CallFrame>,
pub frame: CallFrame,
pub ip: usize,
pub register: [Value; REGISTER_SIZE],
pub matches: bool,
pub match_depth: u8,
stack: Vec<Value>,
call_stack: Vec<CallFrame>,
frame: CallFrame,
ip: usize,
register: [Value; REGISTER_SIZE],
matches: bool,
match_depth: u8,
pub result: Option<Result<Value, Panic>>,
debug: bool,
last_code: usize,
pub pid: &'static str,
pub mbx: VecDeque<Value>,
msg_idx: usize,
pub reductions: usize,
pub zoo: Rc<RefCell<Zoo>>,
pub r#yield: bool,
reductions: usize,
zoo: Rc<RefCell<Zoo>>,
r#yield: bool,
scrutinee: Option<Value>,
}
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<Value, Panic> {
// 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
// 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_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(),
};
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.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<Value>) {
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::<Vec<_>>()
// .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());
}
}
}
}

View File

@ -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!()
}
}