diff --git a/pkg/ludus.js b/pkg/ludus.js index 1d6a133..c913a7e 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -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 = [] diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 77e7479..74c01f0 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 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; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 6b47dbe..59d6c57 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.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) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 922cfaa..788cc43 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 c106157..867b626 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 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; diff --git a/src/base.rs b/src/base.rs index 65718b4..34b61f9 100644 --- a/src/base.rs +++ b/src/base.rs @@ -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::>() .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()) } diff --git a/src/chunk.rs b/src/chunk.rs index b451207..c2b2159 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -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 { - // self.kw_index_from(kw).map(Value::Keyword) - // } - - // pub fn kw_index_from(&self, kw: &str) -> Option { - // self.keywords.iter().position(|s| *s == kw) - // } } diff --git a/src/compiler.rs b/src/compiler.rs index 71b5db7..624a0a8 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -68,12 +68,10 @@ pub struct Compiler { pub scope_depth: isize, pub match_depth: usize, pub stack_depth: usize, - pub spans: Vec, - 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, @@ -98,7 +96,7 @@ fn has_placeholder(args: &[Spanned]) -> bool { impl Compiler { pub fn new( ast: &'static Spanned, - name: &'static str, + input: &'static str, src: &'static str, depth: usize, env: imbl::HashMap, @@ -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(); } } diff --git a/src/errors.rs b/src/errors.rs index 81313db..496fab4 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -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 - diff --git a/src/io.rs b/src/io.rs index ec9d96d..261f7e4 100644 --- a/src/io.rs +++ b/src/io.rs @@ -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; } -#[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) -> Vec { let inbox = inbox.as_string().expect("response should be a string"); 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()); + console_log!("ludus received messages"); for msg in inbox.iter() { - log(format!("{}", msg)); + console_log!("{}", msg); } } inbox diff --git a/src/lib.rs b/src/lib.rs index d3b1944..c854fe8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 { .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 { 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 { } } -#[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 { diff --git a/src/panic.rs b/src/panic.rs index 1ffd944..4ed1f3b 100644 --- a/src/panic.rs +++ b/src/panic.rs @@ -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, - pub ip: usize, pub call_stack: Vec, } + +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::>() + .join("\n"); + write!(f, "Panic: {}\n{stub_trace}", self.msg) + } +} diff --git a/src/vm.rs b/src/vm.rs index 7bf7aac..4048277 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -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>, 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::>() + // .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; } diff --git a/src/world.rs b/src/world.rs index 55ec5b2..077109a 100644 --- a/src/world.rs +++ b/src/world.rs @@ -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