keep working on panics: tracebacks sort of work?

This commit is contained in:
Scott Richmond 2025-07-04 14:10:03 -04:00
parent 050a0f987d
commit 9228e060bb
14 changed files with 139 additions and 173 deletions

View File

@ -34,6 +34,7 @@ async function handle_messages (e) {
}
case "Error": {
console.log("Main: ludus errored with => ", msg.data)
ludus_result = msg.data
running = false
ready = false
outbox = []

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 closure352_externref_shim: (a: number, b: number, c: any) => void;
readonly closure375_externref_shim: (a: number, b: number, c: any, d: any) => void;
readonly closure353_externref_shim: (a: number, b: number, c: any) => void;
readonly closure376_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.closure352_externref_shim(arg0, arg1, arg2);
wasm.closure353_externref_shim(arg0, arg1, arg2);
}
function __wbg_adapter_50(arg0, arg1, arg2, arg3) {
function __wbg_adapter_46(arg0, arg1, arg2, arg3) {
_assertNum(arg0);
_assertNum(arg1);
wasm.closure375_externref_shim(arg0, arg1, arg2, arg3);
wasm.closure376_externref_shim(arg0, arg1, arg2, arg3);
}
async function __wbg_load(module, imports) {
@ -314,31 +314,9 @@ function __wbg_get_imports() {
wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
}
}, arguments) };
imports.wbg.__wbg_log_86d603e98cc11395 = function() { return logError(function (arg0, arg1) {
let deferred0_0;
let deferred0_1;
try {
deferred0_0 = arg0;
deferred0_1 = arg1;
console.log(getStringFromWasm0(arg0, arg1));
} finally {
wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
}
}, arguments) };
imports.wbg.__wbg_log_9e426f8e841e42d3 = function() { return logError(function (arg0, arg1) {
imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) {
console.log(getStringFromWasm0(arg0, arg1));
}, arguments) };
imports.wbg.__wbg_log_edeb598b620f1ba2 = function() { return logError(function (arg0, arg1) {
let deferred0_0;
let deferred0_1;
try {
deferred0_0 = arg0;
deferred0_1 = arg1;
console.log(getStringFromWasm0(arg0, arg1));
} finally {
wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
}
}, arguments) };
imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) {
try {
var state0 = {a: arg0, b: arg1};
@ -346,7 +324,7 @@ function __wbg_get_imports() {
const a = state0.a;
state0.a = 0;
try {
return __wbg_adapter_50(a, state0.b, arg0, arg1);
return __wbg_adapter_46(a, state0.b, arg0, arg1);
} finally {
state0.a = a;
}
@ -425,8 +403,8 @@ function __wbg_get_imports() {
_assertBoolean(ret);
return ret;
};
imports.wbg.__wbindgen_closure_wrapper8059 = function() { return logError(function (arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 353, __wbg_adapter_20);
imports.wbg.__wbindgen_closure_wrapper8065 = function() { return logError(function (arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 354, __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 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 closure353_externref_shim: (a: number, b: number, c: any) => void;
export const closure376_externref_shim: (a: number, b: number, c: any, d: any) => void;
export const __wbindgen_start: () => void;

View File

@ -1,7 +1,7 @@
use crate::js::*;
use crate::value::*;
use imbl::*;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
#[derive(Clone, Debug)]
pub enum BaseFn {
@ -268,12 +268,6 @@ pub fn last(ordered: &Value) -> Value {
}
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(msg: String);
}
pub fn print(x: &Value) -> Value {
let Value::List(args) = x else {
unreachable!("internal Ludus error")
@ -284,7 +278,7 @@ pub fn print(x: &Value) -> Value {
.collect::<Vec<_>>()
.join(" ");
// println!("{out}");
log(out);
console_log!("{out}");
Value::Keyword("ok")
}
@ -513,12 +507,6 @@ pub fn floor(x: &Value) -> Value {
}
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = Math)]
fn random() -> f64;
}
pub fn base_random() -> Value {
Value::Number(random())
}

View File

@ -1,3 +1,4 @@
use crate::js::*;
use crate::op::Op;
use crate::value::{Key, Value};
use chumsky::prelude::SimpleSpan;
@ -43,20 +44,20 @@ impl Chunk {
| UnconditionalMatch | Print | AppendList | ConcatList | PushList | PushDict
| AppendDict | ConcatDict | Nothing | PushGlobal | SetUpvalue | LoadMessage
| NextMessage | MatchMessage | ClearMessage | SendMethod | LoadScrutinee => {
println!("{i:04}: {op}")
console_log!("{i:04}: {op}")
}
Constant | MatchConstant => {
let high = self.bytecode[*i + 1];
let low = self.bytecode[*i + 2];
let idx = ((high as usize) << 8) + low as usize;
let value = &self.constants[idx].show();
println!("{i:04}: {:16} {idx:05}: {value}", op.to_string());
console_log!("{i:04}: {:16} {idx:05}: {value}", op.to_string());
*i += 2;
}
Msg => {
let msg_idx = self.bytecode[*i + 1];
let msg = &self.msgs[msg_idx as usize];
println!("{i:04}: {msg}");
console_log!("{i:04}: {msg}");
*i += 1;
}
PushBinding | MatchTuple | MatchSplattedTuple | LoadSplattedTuple | MatchList
@ -64,7 +65,7 @@ impl Chunk {
| DropDictEntry | LoadDictValue | PushTuple | PushBox | MatchDepth | PopN | StoreN
| Call | GetUpvalue | Partial | MatchString | PushStringMatches | TailCall | LoadN => {
let next = self.bytecode[*i + 1];
println!("{i:04}: {:16} {next:03}", op.to_string());
console_log!("{i:04}: {:16} {next:03}", op.to_string());
*i += 1;
}
Jump | JumpIfFalse | JumpIfTrue | JumpIfNoMatch | JumpIfMatch | JumpBack
@ -72,26 +73,18 @@ impl Chunk {
let high = self.bytecode[*i + 1];
let low = self.bytecode[*i + 2];
let len = ((high as u16) << 8) + low as u16;
println!("{i:04}: {:16} {len:05}", op.to_string());
console_log!("{i:04}: {:16} {len:05}", op.to_string());
*i += 2;
}
}
}
pub fn dissasemble(&self) {
println!("IDX | CODE | INFO");
console_log!("IDX | CODE | INFO");
let mut i = 0;
while i < self.bytecode.len() {
self.dissasemble_instr(&mut i);
i += 1;
}
}
// pub fn kw_from(&self, kw: &str) -> Option<Value> {
// self.kw_index_from(kw).map(Value::Keyword)
// }
// pub fn kw_index_from(&self, kw: &str) -> Option<usize> {
// self.keywords.iter().position(|s| *s == kw)
// }
}

View File

@ -68,12 +68,10 @@ pub struct Compiler {
pub scope_depth: isize,
pub match_depth: usize,
pub stack_depth: usize,
pub spans: Vec<SimpleSpan>,
pub nodes: Vec<&'static Ast>,
pub ast: &'static Ast,
pub span: SimpleSpan,
pub src: &'static str,
pub name: &'static str,
pub input: &'static str,
pub depth: usize,
pub upvalues: Vec<&'static str>,
loop_info: Vec<LoopInfo>,
@ -98,7 +96,7 @@ fn has_placeholder(args: &[Spanned<Ast>]) -> bool {
impl Compiler {
pub fn new(
ast: &'static Spanned<Ast>,
name: &'static str,
input: &'static str,
src: &'static str,
depth: usize,
env: imbl::HashMap<Key, Value>,
@ -111,6 +109,9 @@ impl Compiler {
string_patterns: vec![],
env,
msgs: vec![],
src,
input,
spans: vec![],
};
Compiler {
chunk,
@ -119,14 +120,12 @@ impl Compiler {
scope_depth: -1,
match_depth: 0,
stack_depth: 0,
spans: vec![],
nodes: vec![],
ast: &ast.0,
span: ast.1,
loop_info: vec![],
upvalues: vec![],
src,
name,
input,
tail_pos: false,
debug,
}
@ -147,8 +146,8 @@ impl Compiler {
let low = len as u8;
let high = (len >> 8) as u8;
self.emit_op(op);
self.chunk.bytecode.push(high);
self.chunk.bytecode.push(low);
self.emit_byte(high as usize);
self.emit_byte(low as usize);
}
fn stub_jump(&mut self, op: Op) -> usize {
@ -188,8 +187,8 @@ impl Compiler {
self.emit_op(Op::Constant);
let low = const_idx as u8;
let high = (const_idx >> 8) as u8;
self.chunk.bytecode.push(high);
self.chunk.bytecode.push(low);
self.emit_byte(high as usize);
self.emit_byte(low as usize);
self.stack_depth += 1;
}
@ -215,18 +214,18 @@ impl Compiler {
self.emit_op(Op::MatchConstant);
let low = const_idx as u8;
let high = (const_idx >> 8) as u8;
self.chunk.bytecode.push(high);
self.chunk.bytecode.push(low);
self.emit_byte(high as usize);
self.emit_byte(low as usize);
}
fn emit_op(&mut self, op: Op) {
self.chunk.bytecode.push(op as u8);
self.spans.push(self.span);
self.chunk.spans.push(self.span);
}
fn emit_byte(&mut self, byte: usize) {
self.chunk.bytecode.push(byte as u8);
self.spans.push(self.span);
self.chunk.spans.push(self.span);
}
fn len(&self) -> usize {
@ -234,7 +233,7 @@ impl Compiler {
}
pub fn bind(&mut self, name: &'static str) {
self.msg(format!("binding `{name}` in {}", self.name));
self.msg(format!("binding `{name}` in {}", self.input));
self.msg(format!(
"stack depth: {}; match depth: {}",
self.stack_depth, self.match_depth
@ -275,7 +274,7 @@ impl Compiler {
fn resolve_binding(&mut self, name: &'static str) {
self.msg(format!(
"resolving binding `{name}` in {}\nlocals: {}",
self.name,
self.input,
self.bindings
.iter()
.map(|binding| format!("{binding}"))
@ -1161,7 +1160,7 @@ impl Compiler {
None => {
let mut compiler = Compiler::new(
clause,
name,
self.input,
self.src,
self.depth + 1,
self.chunk.env.clone(),
@ -1502,7 +1501,7 @@ impl Compiler {
}
pub fn disassemble(&self) {
println!("=== chunk: {} ===", self.name);
println!("=== chunk: {} ===", self.input);
self.chunk.dissasemble();
}
}

View File

@ -1,12 +1,11 @@
// use crate::process::{LErr, Trace};
use crate::js::*;
use crate::lexer::Token;
use crate::validator::VErr;
use crate::panic::{Panic, PanicMsg};
use crate::validator::VErr;
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 {
@ -94,30 +93,46 @@ fn parsing_message(err: Rich<'static, Token>) -> String {
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()];
pub fn panic(panic: Panic) -> String {
// console_log!("Ludus panicked!: {panic}");
// panic.call_stack.last().unwrap().chunk().dissasemble();
// console_log!("{:?}", panic.call_stack.last().unwrap().chunk().spans);
let mut msgs = vec!["Ludus panicked!".to_string()];
let msg = match panic.msg {
PanicMsg::Generic(s) => s,
_ => "no match".to_string(),
PanicMsg::Generic(ref s) => s,
_ => &"no match".to_string(),
};
msgs.push(msg.clone());
msgs.push(traceback(&panic));
msgs.join("\n")
}
fn traceback(panic: &Panic) -> String {
let mut traceback = vec![];
for frame in panic.call_stack.iter().rev() {
traceback.push(frame_info(frame));
}
todo!()
traceback.join("\n")
}
fn traceback(_panic: Panic) -> String {
todo!()
fn frame_info(frame: &CallFrame) -> String {
let span = frame.chunk().spans[if frame.ip == 0 {
frame.ip
} else {
frame.ip - 1
}];
let line_number = line_number(frame.chunk().src, span);
let line = get_line(frame.chunk().src, line_number);
let line = line.trim_start();
let name = frame.function.as_fn().name();
let input = frame.chunk().input;
format!(
" in {name} on line {} in {input}\n >>> {line}",
line_number + 1
)
}
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
@ -126,4 +141,3 @@ fn frame_info(frame: CallFrame) -> String {
// 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,6 +1,7 @@
use wasm_bindgen::prelude::*;
use serde::{Serialize, Deserialize};
use crate::value::Value;
use crate::js::*;
use imbl::Vector;
use std::rc::Rc;
@ -13,12 +14,6 @@ extern "C" {
async fn io (output: String) -> Result<JsValue, JsValue>;
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: String);
}
type Url = Value; // expect a string
type Commands = Value; // expect a list of command tuples
@ -81,7 +76,7 @@ impl MsgIn {
}
pub async fn send_err_to_ludus_console(msg: String) {
log(msg.clone());
console_log!("{msg}");
do_io(vec![MsgOut::Ready, MsgOut::Error(msg)]).await;
}
@ -95,9 +90,9 @@ pub async fn do_io (msgs: Vec<MsgOut>) -> Vec<MsgIn> {
let inbox = inbox.as_string().expect("response should be a string");
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());
console_log!("ludus received messages");
for msg in inbox.iter() {
log(format!("{}", msg));
console_log!("{}", msg);
}
}
inbox

View File

@ -42,6 +42,9 @@ use crate::errors::{lexing, parsing, validation};
mod panic;
mod js;
use crate::js::*;
mod chunk;
mod op;
@ -63,8 +66,8 @@ fn prelude() -> HashMap<Key, Value> {
.into_output_errors();
if !parse_errors.is_empty() {
log("ERROR PARSING PRELUDE:");
log(format!("{:?}", parse_errors).as_str());
console_log!("ERROR PARSING PRELUDE:");
console_log!("{:?}", parse_errors);
panic!("parsing errors in prelude");
}
@ -80,9 +83,9 @@ fn prelude() -> HashMap<Key, Value> {
validator.validate();
if !validator.errors.is_empty() {
log("VALIDATION ERRORS IN PRLUDE:");
console_log!("VALIDATION ERRORS IN PRLUDE:");
// report_invalidation(validator.errors);
log(format!("{:?}", validator.errors).as_str());
console_log!("{:?}", validator.errors);
panic!("validator errors in prelude");
}
@ -110,13 +113,6 @@ fn prelude() -> HashMap<Key, Value> {
}
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[wasm_bindgen]
pub async fn ludus(src: String) {
// instrument wasm to report rust panics
@ -157,7 +153,7 @@ pub async fn ludus(src: String) {
let mut compiler = Compiler::new(
parsed,
"ludus script",
"user script",
src,
0,
prelude.clone(),
@ -181,7 +177,6 @@ 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();
// TODO: actually do something useful on a panic
// match result {

View File

@ -1,3 +1,4 @@
use crate::errors::panic;
use crate::value::Value;
use crate::vm::CallFrame;
@ -9,11 +10,38 @@ pub enum PanicMsg {
Generic(String),
}
impl std::fmt::Display for PanicMsg {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use PanicMsg::*;
match self {
NoLetMatch => write!(f, "no match in `let`"),
NoFnMatch => write!(f, "no match calling fn"),
NoMatch => write!(f, "no match in `match` form"),
Generic(s) => write!(f, "{s}"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Panic {
pub msg: PanicMsg,
pub frame: CallFrame,
pub scrutinee: Option<Value>,
pub ip: usize,
pub call_stack: Vec<CallFrame>,
}
fn frame_dump(frame: &CallFrame) -> String {
let dump = format!("stack name: {}\nspans: {:?}", frame, frame.chunk().spans);
dump
}
impl std::fmt::Display for Panic {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let stub_trace = self
.call_stack
.iter()
.map(frame_dump)
.collect::<Vec<_>>()
.join("\n");
write!(f, "Panic: {}\n{stub_trace}", self.msg)
}
}

View File

@ -1,5 +1,6 @@
use crate::base::BaseFn;
use crate::chunk::Chunk;
use crate::js::*;
use crate::op::Op;
use crate::panic::{Panic, PanicMsg};
use crate::value::{Key, LFn, Value};
@ -83,7 +84,7 @@ impl std::fmt::Display for Creature {
impl Creature {
pub fn new(chunk: Chunk, zoo: Rc<RefCell<Zoo>>, debug: bool) -> Creature {
let lfn = LFn::Defined {
name: "user script",
name: "toplevel",
doc: None,
chunks: vec![chunk],
arities: vec![0],
@ -185,42 +186,28 @@ impl Creature {
self.chunk().dissasemble_instr(&mut ip);
}
// 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_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) {
// first prep the current frame for parsing
let mut frame = self.frame.clone();
frame.ip = self.last_code;
// add it to our cloned stack
let mut call_stack = self.call_stack.clone();
call_stack.push(frame);
// console_log!(
// "{}",
// call_stack
// .iter()
// .map(|s| s.to_string())
// .collect::<Vec<_>>()
// .join("\n")
// );
//make a panic
let panic = Panic {
msg,
frame: self.frame.clone(),
scrutinee: self.scrutinee.clone(),
ip: self.ip,
call_stack: self.call_stack.clone(),
call_stack,
};
// and gtfo
self.result = Some(Err(panic));
self.r#yield = true;
}

View File

@ -2,25 +2,13 @@ use crate::chunk::Chunk;
use crate::value::{Value, Key};
use crate::vm::Creature;
use crate::panic::Panic;
use crate::errors::panic;
use crate::js::{random, now};
use crate::io::{MsgOut, MsgIn, do_io};
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::mem::swap;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
// Grab some JS stuff
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
#[wasm_bindgen(js_namespace = Math)]
fn random() -> f64;
#[wasm_bindgen(js_namespace = Date)]
fn now() -> f64;
}
const ANIMALS: [&str; 32] = [
"tortoise",
@ -420,7 +408,7 @@ impl World {
self.result = Some(result.clone());
let result_msg = match result {
Ok(value) => MsgOut::Complete(Value::string(value.show())),
Err(_msg) => MsgOut::Error("Ludus panicked!".to_string())
Err(p) => MsgOut::Error(panic(p))
};
outbox.push(result_msg);
outbox