From 2f60de79a2bdbd07d5ab3b5984d42bb8f3354ae9 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 22 Jun 2025 14:04:43 -0400 Subject: [PATCH] keep grinding on loop/recur/jif stack mismatch; add ast->code printer --- assets/test_prelude.ld | 115 ++++++++++++++++++++---------- may_2025_thoughts.md | 42 +++++++++++ src/base.rs | 2 +- src/compiler.rs | 79 ++++++++++++++++++--- src/main.rs | 59 +++++++++++---- src/parser.rs | 158 +++++++++++++++++++++++++++++++++++++++++ src/value.rs | 12 +++- src/vm.rs | 44 +++++++----- 8 files changed, 431 insertions(+), 80 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index fb87731..51e4236 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1,4 +1,4 @@ -& & the very base: know something's type +& the very base: know something's type & fn type { & "Returns a keyword representing the type of the value passed in." & (x) -> base :type (x) @@ -48,20 +48,20 @@ & (value, _) -> value & } -& & ...and if two things are the same -& fn eq? { -& "Returns true if all arguments have the same value." -& (x) -> true -& (x, y) -> base :eq? (x, y) -& (x, y, ...zs) -> if eq? (x, y) -& then loop (y, zs) with { -& (a, []) -> eq? (a, x) -& (a, [b, ...cs]) -> if eq? (a, x) -& then recur (b, cs) -& else false -& } -& else false -& } +& ...and if two things are the same +fn eq? { + "Returns true if all arguments have the same value." + & (x) -> true + (x, y) -> base :eq? (x, y) + & (x, y, ...zs) -> if eq? (x, y) + & then loop (y, zs) with { + & (a, []) -> eq? (a, x) + & (a, [b, ...cs]) -> if eq? (a, x) + & then recur (b, cs) + & else false + & } + & else false +} & &&& true & false: boolean logic (part the first) & fn bool? { @@ -115,20 +115,20 @@ fn first { "Retrieves the first element of an ordered collection--a tuple or a list. If the collection is empty, returns nil." ([]) -> nil - (()) -> nil - ("") -> nil + & (()) -> nil + & ("") -> nil (xs as :list) -> base :first (xs) - (xs as :tuple) -> base :first (xs) - (str as :string) -> base :slice (str, 0, 1) + & (xs as :tuple) -> base :first (xs) + & (str as :string) -> base :slice (str, 0, 1) } fn rest { "Returns all but the first element of a list or tuple, as a list." ([]) -> [] - (()) -> () + & (()) -> () (xs as :list) -> base :rest (xs) - (xs as :tuple) -> base :rest (xs) - (str as :string) -> base :rest (str) + & (xs as :tuple) -> base :rest (xs) + & (str as :string) -> base :rest (str) } fn inc { @@ -203,18 +203,53 @@ fn fold { } } -fn map { - "Maps a function over a list: returns a new list with elements that are the result of applying the function to each element in the original list. E.g., `map ([1, 2, 3], inc) &=> [2, 3, 4]`. With one argument, returns a function that is a mapper over lists; with two, it executes the mapping function right away." - & (f as :fn) -> map (f, _) - & (kw as :keyword) -> map (kw, _) - (f as :fn, xs) -> { - fn mapper (prev, curr) -> append (prev, f (curr)) - fold (mapper, xs, []) +& fn map { +& "Maps a function over a list: returns a new list with elements that are the result of applying the function to each element in the original list. E.g., `map ([1, 2, 3], inc) &=> [2, 3, 4]`. With one argument, returns a function that is a mapper over lists; with two, it executes the mapping function right away." +& & (f as :fn) -> map (f, _) +& & (kw as :keyword) -> map (kw, _) +& (f as :fn, xs) -> { +& fn mapper (prev, curr) -> append (prev, f (curr)) +& fold (mapper, xs, []) +& } +& & (kw as :keyword, xs) -> { +& & fn mapper (prev, curr) -> append (prev, kw (curr)) +& & fold (mapper, xs, []) +& & } +& } + +& fn filter { +& "Takes a list and a predicate function, and returns a new list with only the items that produce truthy values when the function is called on them. E.g., `filter ([1, 2, 3, 4], odd?) &=> [1, 3]`." +& (p? as :fn) -> filter (p?, _) +& (p? as :fn, xs) -> { +& fn filterer (filtered, x) -> if p? (x) +& then append (filtered, x) +& else filtered +& fold (filterer, xs, []) +& } +& } + +& fn keep { +& "Takes a list and returns a new list with any `nil` values omitted." +& (xs) -> filter (some?, xs) +& } + +& fn concat { +& "Combines two lists, strings, or sets." +& (x as :string, y as :string) -> "{x}{y}" +& (xs as :list, ys as :list) -> base :concat (xs, ys) +& & (xs as :set, ys as :set) -> base :concat (xs, ys) +& (xs, ys, ...zs) -> fold (concat, zs, concat (xs, ys)) +& } + +fn contains? { + "Returns true if a set or list contains a value." + & (value, s as :set) -> bool (base :get (s, value)) + (value, l as :list) -> loop (l) with { + ([]) -> false + ([...xs]) -> if eq? (first(xs), value) + then true + else recur (rest (xs)) } - & (kw as :keyword, xs) -> { - & fn mapper (prev, curr) -> append (prev, kw (curr)) - & fold (mapper, xs, []) - & } } fn add { @@ -231,7 +266,7 @@ fn add { & nil? & some? & some - & eq? + eq? & bool? & true? & false? @@ -241,7 +276,7 @@ fn add { & fn? first rest - inc + & inc & dec & count & empty? @@ -249,8 +284,12 @@ fn add { & list? & list & first - fold - append - map + & fold + & append + & map + & filter + & keep + & concat + & contains? add } diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index fd5e2fc..5f5ab17 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -434,3 +434,45 @@ And the stack positions on stuff are also totally weird. One thing: the way we are now resolving upvalues means that nothing should ever reach back further in the stack than the stack base in the vm. So now we'll do all bindings relative to the stack base. UGH. + +*** + +So now I have a bug that I'm dying to figure out. +But it's time to go to bed. + +When `recur` is in the alternatve branch of an if, it thinks there's one more value on the stack than there really is. +To the best of my ability to tell, `if` has proper stack behaviour. +So the question is what's happening in the interaction between the `jump_if_false` instruction and `recur`. + +To wit, the following code works just fine: +``` +fn not { + (false) -> true + (nil) -> true + (_) -> false +} + +let test = 2 +loop ([1, 2, 3]) with { + ([]) -> false + ([x]) -> eq? (x, test) + ([x, ...xs]) -> if not(eq? (x, test)) + then recur (xs) + else true +} +``` + +But the following code does not: +``` +let test = 2 +loop ([1, 2, 3]) with { + ([]) -> false + ([x]) -> eq? (x, test) + ([x, ...xs]) -> if eq? (x, test) + then true + else recur (xs) +} +``` + +Meanwhile, other `loop`/`recur` forms seem to work okay for me. +So: ugh. diff --git a/src/base.rs b/src/base.rs index 68c1fb9..7498a44 100644 --- a/src/base.rs +++ b/src/base.rs @@ -22,7 +22,7 @@ pub fn eq(x: &Value, y: &Value) -> Value { pub fn add(x: &Value, y: &Value) -> Value { match (x, y) { (Value::Number(x), Value::Number(y)) => Value::Number(x + y), - _ => unreachable!("internal Ludus error"), + _ => unreachable!("internal Ludus error: wrong arguments to base add: {x}, {y}"), } } diff --git a/src/compiler.rs b/src/compiler.rs index 8e3640c..b77d62a 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -259,6 +259,7 @@ pub struct Chunk { pub keywords: Vec<&'static str>, pub string_patterns: Vec, pub env: imbl::HashMap<&'static str, Value>, + pub msgs: Vec, } impl Chunk { @@ -272,7 +273,7 @@ impl Chunk { | Mult | Div | Unbox | BoxStore | Assert | Get | At | Not | Panic | EmptyString | ConcatStrings | Stringify | MatchType | Return | Match | Print | AppendList | ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing | PushGlobal - | SetUpvalue | Msg => { + | SetUpvalue => { println!("{i:04}: {op}") } Constant | MatchConstant => { @@ -283,6 +284,12 @@ impl Chunk { println!("{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}"); + *i += 1; + } PushBinding | MatchTuple | MatchSplattedTuple | LoadSplattedTuple | MatchList | MatchSplattedList | LoadSplattedList | MatchDict | MatchSplattedDict | DropDictEntry | LoadDictValue | PushTuple | PushBox | MatchDepth | PopN | StoreN @@ -370,6 +377,7 @@ pub struct Compiler<'a> { pub upvalues: Vec<&'static str>, loop_info: Vec, tail_pos: bool, + debug: bool, } fn is_binding(expr: &Spanned) -> bool { @@ -393,6 +401,7 @@ impl<'a> Compiler<'a> { src: &'static str, enclosing: Option<&'a Compiler>, env: imbl::HashMap<&'static str, Value>, + debug: bool, ) -> Compiler<'a> { let chunk = Chunk { constants: vec![], @@ -400,6 +409,7 @@ impl<'a> Compiler<'a> { keywords: vec![], string_patterns: vec![], env, + msgs: vec![], }; Compiler { chunk, @@ -417,6 +427,7 @@ impl<'a> Compiler<'a> { src, name, tail_pos: false, + debug, } } @@ -512,13 +523,14 @@ impl<'a> Compiler<'a> { } pub fn bind(&mut self, name: &'static str) { + self.msg(format!("binding `{name}` in {}", self.name)); + println!("stack: {}; match: {}", self.stack_depth, self.match_depth); let binding = Binding { name, depth: self.scope_depth, stack_pos: self.stack_depth - self.match_depth - 1, }; println!("{:?}", binding); - println!("stack: {}; match: {}", self.stack_depth, self.match_depth); self.bindings.push(binding); } @@ -536,6 +548,7 @@ impl<'a> Compiler<'a> { } fn resolve_binding(&mut self, name: &'static str) { + self.msg(format!("resolving binding `{name}` in {}", self.name)); if let Some(pos) = self.resolve_local(name) { self.emit_op(Op::PushBinding); self.emit_byte(pos); @@ -598,10 +611,17 @@ impl<'a> Compiler<'a> { } fn msg(&mut self, str: String) { - let leaked = Box::leak(str.into_boxed_str()); - self.emit_constant(Value::Interned(leaked)); self.emit_op(Op::Msg); - self.stack_depth -= 1; + self.emit_byte(self.chunk.msgs.len()); + self.chunk.msgs.push(str); + } + + fn report_depth(&mut self, label: &'static str) { + self.msg(format!("***{label} stack depth: {}", self.stack_depth)); + } + + fn report_depth_str(&mut self, label: String) { + self.msg(format!("***{label} stack depth: {}", self.stack_depth)); } pub fn compile(&mut self) { @@ -686,13 +706,17 @@ impl<'a> Compiler<'a> { let tail_pos = self.tail_pos; self.tail_pos = false; self.visit(cond); + self.report_depth("after condition"); let jif_idx = self.stub_jump(Op::JumpIfFalse); self.stack_depth -= 1; self.tail_pos = tail_pos; self.visit(then); + self.report_depth("after consequent"); let jump_idx = self.stub_jump(Op::Jump); - self.visit(r#else); self.stack_depth -= 1; + self.visit(r#else); + self.report_depth("after alternative"); + // self.stack_depth += 1; let end_idx = self.len(); let jif_offset = jump_idx - jif_idx; let jump_offset = end_idx - jump_idx - 3; @@ -1031,11 +1055,13 @@ impl<'a> Compiler<'a> { let tail_pos = self.tail_pos; self.tail_pos = false; match (&first.0, &second.0) { - (Word(_), Keyword(_)) => { + (Word(name), Keyword(key)) => { + self.report_depth_str(format!("accessing keyword: {name} :{key}")); self.visit(first); self.visit(second); self.emit_op(Op::GetKey); self.stack_depth -= 1; + self.report_depth("after keyword access"); } (Keyword(_), Arguments(args)) => { self.visit(&args[0]); @@ -1080,6 +1106,7 @@ impl<'a> Compiler<'a> { self.stack_depth = stack_depth + 1; } (Word(fn_name), Arguments(args)) => { + self.report_depth_str(format!("calling function {fn_name}")); if has_placeholder(args) { let arity = args.len(); for arg in args { @@ -1105,6 +1132,7 @@ impl<'a> Compiler<'a> { } self.resolve_binding(fn_name); // if we're in tail position AND there aren't any rest args, this should be a tail call (I think) + self.report_depth_str(format!("after {arity} args")); if rest.is_empty() && tail_pos { self.emit_op(Op::TailCall); } else { @@ -1275,6 +1303,7 @@ impl<'a> Compiler<'a> { self.src, Some(self), self.chunk.env.clone(), + self.debug, ); compiler.emit_op(Op::ResetMatch); compilers.insert(arity, compiler); @@ -1347,7 +1376,7 @@ impl<'a> Compiler<'a> { for (arity, mut compiler) in compilers { compiler.emit_op(Op::PanicNoMatch); let chunk = compiler.chunk; - if crate::DEBUG_COMPILE { + if self.debug { println!("=== function chuncktion: {name}/{arity} ==="); chunk.dissasemble(); } @@ -1444,18 +1473,27 @@ impl<'a> Compiler<'a> { self.visit(member); } self.msg(format!( - "entering loop with stack depth of {}", + "***entering loop with stack depth of {}", self.stack_depth )); self.emit_op(Op::StoreN); self.emit_byte(members.len()); let arity = members.len(); + // self.pop(); let stack_depth = self.stack_depth; + self.msg(format!( + "***after store, stack depth is now {}", + self.stack_depth + )); //then, save the beginning of the loop self.emit_op(Op::Load); self.enter_loop(); // self.stack_depth += arity; //next, compile each clause: + self.msg(format!( + "***after load, stack depth is now {}", + self.stack_depth + )); let mut clauses = clauses.iter(); let mut jump_idxes = vec![]; while let Some((Ast::MatchClause(pattern, guard, body), _)) = clauses.next() { @@ -1482,9 +1520,18 @@ impl<'a> Compiler<'a> { self.stack_depth -= 1; } self.tail_pos = tail_pos; + self.msg(format!( + "***before visiting body, the stack depth is {}", + self.stack_depth + )); self.visit(body); + self.msg(format!( + "***after visiting loop body, the stack depth is {}", + self.stack_depth + )); self.emit_op(Op::Store); - // self.scope_depth -= 1; + self.scope_depth -= 1; + self.msg("releasing loop bindings".to_string()); while let Some(binding) = self.bindings.last() { if binding.depth > self.scope_depth { self.bindings.pop(); @@ -1492,6 +1539,9 @@ impl<'a> Compiler<'a> { break; } } + self.msg("resetting the stack after loop".to_string()); + // self.emit_op(Op::PopN); + // self.emit_byte(self.stack_depth - stack_depth); while self.stack_depth > stack_depth { self.pop(); } @@ -1511,6 +1561,11 @@ impl<'a> Compiler<'a> { self.leave_loop(); } Recur(args) => { + // self.emit_op(Op::Nothing); + self.msg(format!( + "before visiting recur args the compiler thinks the stack depth is {}", + self.stack_depth + )); let tail_pos = self.tail_pos; self.tail_pos = false; let mut argnum = 0; @@ -1519,6 +1574,10 @@ impl<'a> Compiler<'a> { argnum += 1; self.visit(arg); } + self.msg(format!( + "after visiting recur args the compiler thinks the stack depth is {}", + self.stack_depth, + )); self.emit_op(Op::StoreN); self.emit_byte(args.len()); self.emit_op(Op::PopN); diff --git a/src/main.rs b/src/main.rs index 6cfdff1..60bfbf4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,12 @@ use chumsky::{input::Stream, prelude::*}; use imbl::HashMap; use std::env; +use std::fs; -const DEBUG_COMPILE: bool = true; -const DEBUG_RUN: bool = true; +const DEBUG_SCRIPT_COMPILE: bool = true; +const DEBUG_SCRIPT_RUN: bool = true; +const DEBUG_PRELUDE_COMPILE: bool = false; +const DEBUG_PRELUDE_RUN: bool = false; mod base; @@ -63,13 +66,20 @@ pub fn prelude() -> HashMap<&'static str, Value> { } let parsed: &'static Spanned = Box::leak(Box::new(parsed)); - let mut compiler = Compiler::new(parsed, "prelude", PRELUDE, None, HashMap::new()); + let mut compiler = Compiler::new( + parsed, + "prelude", + PRELUDE, + None, + HashMap::new(), + DEBUG_PRELUDE_COMPILE, + ); compiler.emit_constant(base); compiler.bind("base"); compiler.compile(); let chunk = compiler.chunk; - let mut vm = Vm::new(chunk); + let mut vm = Vm::new(chunk, DEBUG_PRELUDE_RUN); let prelude = vm.run().clone().unwrap(); match prelude { Value::Dict(hashmap) => *hashmap, @@ -111,26 +121,26 @@ pub fn run(src: &'static str) { return; } - let mut compiler = Compiler::new(parsed, "test", src, None, prelude); + let mut compiler = Compiler::new(parsed, "test", src, None, prelude, DEBUG_SCRIPT_COMPILE); // let base = base::make_base(); // compiler.emit_constant(base); // compiler.bind("base"); compiler.compile(); - if DEBUG_COMPILE { + if DEBUG_SCRIPT_COMPILE { println!("=== source code ==="); println!("{src}"); compiler.disassemble(); println!("\n\n") } - if DEBUG_RUN { + if DEBUG_SCRIPT_RUN { println!("=== vm run: test ==="); } let vm_chunk = compiler.chunk; - let mut vm = Vm::new(vm_chunk); + let mut vm = Vm::new(vm_chunk, DEBUG_SCRIPT_RUN); let result = vm.run(); let output = match result { Ok(val) => val.show(), @@ -140,10 +150,35 @@ pub fn run(src: &'static str) { println!("{output}"); } +pub fn ld_fmt(src: &'static str) -> Result { + let (tokens, lex_errs) = lexer().parse(src).into_output_errors(); + if !lex_errs.is_empty() { + println!("{:?}", lex_errs); + return Err(format!("{:?}", lex_errs)); + } + + let tokens = tokens.unwrap(); + + let (parse_result, parse_errors) = parser() + .parse(Stream::from_iter(tokens).map((0..src.len()).into(), |(t, s)| (t, s))) + .into_output_errors(); + if !parse_errors.is_empty() { + return Err(format!("{:?}", parse_errors)); + } + + // ::sigh:: The AST should be 'static + // This simplifies lifetimes, and + // in any event, the AST should live forever + let parsed: &'static Spanned = Box::leak(Box::new(parse_result.unwrap())); + + Ok(parsed.0.show()) +} + pub fn main() { env::set_var("RUST_BACKTRACE", "1"); - let src = r#" -map(inc, [1, 2, 3]) -"#; - run(src); + let src: &'static str = fs::read_to_string("sandbox.ld").unwrap().leak(); + match ld_fmt(src) { + Ok(src) => println!("{}", src), + Err(msg) => println!("Could not format source with errors:\n{}", msg), + } } diff --git a/src/parser.rs b/src/parser.rs index 9978a95..f882ff2 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -120,6 +120,164 @@ pub enum Ast { DictPattern(Vec>), } +impl Ast { + pub fn show(&self) -> String { + use Ast::*; + match self { + And => "and".to_string(), + Or => "or".to_string(), + Error => unreachable!(), + Nil | NilPattern => "nil".to_string(), + String(s) | StringPattern(s) => format!("\"{s}\""), + Interpolated(strs) | InterpolatedPattern(strs, _) => { + let mut out = "".to_string(); + out = format!("\"{out}"); + for (part, _) in strs { + out = format!("{out}{part}"); + } + format!("{out}\"") + } + Boolean(b) | BooleanPattern(b) => b.to_string(), + Number(n) | NumberPattern(n) => n.to_string(), + Keyword(k) | KeywordPattern(k) => format!(":{k}"), + Word(w) | WordPattern(w) => w.to_string(), + Block(lines) => { + let mut out = "{\n".to_string(); + for (line, _) in lines { + out = format!("{out}\n {}", line.show()); + } + format!("{out}\n}}") + } + If(cond, then, r#else) => format!( + "if {}\n then {}\n else {}", + cond.0.show(), + then.0.show(), + r#else.0.show() + ), + Let(pattern, expression) => { + format!("let {} = {}", pattern.0.show(), expression.0.show()) + } + Dict(entries) | DictPattern(entries) => { + format!( + "#{{{}}}", + entries + .iter() + .map(|(pair, _)| pair.show()) + .collect::>() + .join(", ") + ) + } + List(members) | ListPattern(members) => format!( + "[{}]", + members + .iter() + .map(|(member, _)| member.show()) + .collect::>() + .join(", ") + ), + Arguments(members) => format!( + "({})", + members + .iter() + .map(|(member, _)| member.show()) + .collect::>() + .join(", ") + ), + Tuple(members) | TuplePattern(members) => format!( + "({})", + members + .iter() + .map(|(member, _)| member.show()) + .collect::>() + .join(", ") + ), + Synthetic(root, first, rest) => format!( + "{} {} {}", + root.0.show(), + first.0.show(), + rest.iter() + .map(|(term, _)| term.show()) + .collect::>() + .join(" ") + ), + When(clauses) => format!( + "when {{\n {}\n}}", + clauses + .iter() + .map(|(clause, _)| clause.show()) + .collect::>() + .join("\n ") + ), + Placeholder | PlaceholderPattern => "_".to_string(), + LBox(name, rhs) => format!("box {name} = {}", rhs.0.show()), + Match(scrutinee, clauses) => format!( + "match {} with {{\n {}\n}}", + scrutinee.0.show(), + clauses + .iter() + .map(|(clause, _)| clause.show()) + .collect::>() + .join("\n ") + ), + FnBody(clauses) => format!( + "{}", + clauses + .iter() + .map(|(clause, _)| clause.show()) + .collect::>() + .join("\n ") + ), + Fn(name, body, doc) => { + let mut out = format!("fn {name} {{\n"); + if let Some(doc) = doc { + out = format!("{out} {doc}\n"); + } + format!("{out} {}\n}}", body.0.show()) + } + FnDeclaration(name) => format!("fn {name}"), + Panic(expr) => format!("panic! {}", expr.0.show()), + Do(terms) => { + format!( + "do {}", + terms + .iter() + .map(|(term, _)| term.show()) + .collect::>() + .join(" > ") + ) + } + Repeat(times, body) => format!("repeat {} {{\n{}\n}}", times.0.show(), body.0.show()), + Splat(word) => format!("...{}", word), + Splattern(pattern) => format!("...{}", pattern.0.show()), + AsPattern(word, type_keyword) => format!("{word} as :{type_keyword}"), + Pair(key, value) | PairPattern(key, value) => format!(":{key} {}", value.0.show()), + Loop(init, body) => format!( + "loop {} with {{\n {}\n}}", + init.0.show(), + body.iter() + .map(|(clause, _)| clause.show()) + .collect::>() + .join("\n ") + ), + Recur(args) => format!( + "recur ({})", + args.iter() + .map(|(arg, _)| arg.show()) + .collect::>() + .join(", ") + ), + MatchClause(pattern, guard, body) => { + let mut out = format!("{}", pattern.0.show()); + if let Some(guard) = guard.as_ref() { + out = format!("{out} if {}", guard.0.show()); + } + format!("{out} -> {}", body.0.show()) + } + WhenClause(cond, body) => format!("{} -> {}", cond.0.show(), body.0.show()), + } + } +} + impl fmt::Display for Ast { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use Ast::*; diff --git a/src/value.rs b/src/value.rs index 41b5b72..ed85546 100644 --- a/src/value.rs +++ b/src/value.rs @@ -26,7 +26,7 @@ impl LFn { match self { LFn::Declared { .. } => unreachable!(), LFn::Defined { closed, .. } => { - println!("closing over in {}: {value}", self.name()); + println!("closing over in {}: {}", self.name(), value.show()); closed.borrow_mut().push(value); } } @@ -209,7 +209,7 @@ impl std::fmt::Display for Value { impl Value { pub fn show(&self) -> String { use Value::*; - match &self { + let mut out = match &self { Nil => "nil".to_string(), True => "true".to_string(), False => "false".to_string(), @@ -244,7 +244,13 @@ impl Value { Fn(lfn) => format!("fn {}", lfn.name()), Partial(partial) => format!("fn {}/partial", partial.name), BaseFn(_) => format!("{self}"), - Nothing => unreachable!(), + Nothing => "_".to_string(), + }; + if out.len() > 20 { + out.truncate(20); + format!("{out}...") + } else { + out } } diff --git a/src/vm.rs b/src/vm.rs index 9de605c..1d4bb99 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -88,10 +88,11 @@ pub struct Vm { pub matches: bool, pub match_depth: u8, pub result: Option>, + debug: bool, } impl Vm { - pub fn new(chunk: Chunk) -> Vm { + pub fn new(chunk: Chunk, debug: bool) -> Vm { let lfn = LFn::Defined { name: "user script", doc: None, @@ -116,6 +117,7 @@ impl Vm { matches: false, match_depth: 0, result: None, + debug, } } @@ -136,12 +138,21 @@ impl Vm { } pub fn print_stack(&self) { - let inner = self - .stack - .iter() - .map(|val| val.to_string()) - .collect::>() - .join("|"); + let mut inner = vec![]; + for (i, value) in self.stack.iter().enumerate() { + if i == self.frame.stack_base { + inner.push(format!("->{}<-", value.show())) + } else { + inner.push(value.show()) + } + } + let inner = inner.join("|"); + // let inner = self + // .stack + // .iter() + // .map(|val| val.show()) + // .collect::>() + // .join("|"); let register = self .return_register .iter() @@ -194,7 +205,7 @@ impl Vm { self.result = Some(Ok(self.stack.pop().unwrap())); return; }; - if crate::DEBUG_RUN { + if self.debug { self.print_debug(); } let op = Op::from_u8(*byte).unwrap(); @@ -875,8 +886,11 @@ impl Vm { let val = self.pop(); - if crate::DEBUG_RUN { - println!("=== tail call into {val}/{arity} ==="); + if self.debug { + println!( + "=== tail call into {val}/{arity} from {} ===", + self.frame.function.as_fn().name() + ); } match val { @@ -930,7 +944,7 @@ impl Vm { self.ip = 0; - if crate::DEBUG_RUN {} + if crate::DEBUG_SCRIPT_RUN {} } Value::BaseFn(base_fn) => { let value = match (arity, base_fn) { @@ -991,7 +1005,7 @@ impl Vm { let val = self.pop(); - if crate::DEBUG_RUN { + if crate::DEBUG_SCRIPT_RUN { println!("=== calling into {val}/{arity} ==="); } @@ -1076,7 +1090,7 @@ impl Vm { } } Return => { - if crate::DEBUG_RUN { + if crate::DEBUG_SCRIPT_RUN { println!("== returning from {} ==", self.frame.function.show()) } self.frame = self.call_stack.pop().unwrap(); @@ -1108,9 +1122,7 @@ impl Vm { } } Msg => { - let msg = self.pop(); - println!("{msg}"); - self.ip += 1; + self.ip += 2; } } }