keep grinding on loop/recur/jif stack mismatch; add ast->code printer

This commit is contained in:
Scott Richmond 2025-06-22 14:04:43 -04:00
parent 398b140d79
commit 2f60de79a2
8 changed files with 431 additions and 80 deletions

View File

@ -1,4 +1,4 @@
& & the very base: know something's type & the very base: know something's type
& fn type { & fn type {
& "Returns a keyword representing the type of the value passed in." & "Returns a keyword representing the type of the value passed in."
& (x) -> base :type (x) & (x) -> base :type (x)
@ -48,20 +48,20 @@
& (value, _) -> value & (value, _) -> value
& } & }
& & ...and if two things are the same & ...and if two things are the same
& fn eq? { fn eq? {
& "Returns true if all arguments have the same value." "Returns true if all arguments have the same value."
& (x) -> true & (x) -> true
& (x, y) -> base :eq? (x, y) (x, y) -> base :eq? (x, y)
& (x, y, ...zs) -> if eq? (x, y) & (x, y, ...zs) -> if eq? (x, y)
& then loop (y, zs) with { & then loop (y, zs) with {
& (a, []) -> eq? (a, x) & (a, []) -> eq? (a, x)
& (a, [b, ...cs]) -> if eq? (a, x) & (a, [b, ...cs]) -> if eq? (a, x)
& then recur (b, cs) & then recur (b, cs)
& else false & else false
& } & }
& else false & else false
& } }
& &&& true & false: boolean logic (part the first) & &&& true & false: boolean logic (part the first)
& fn bool? { & fn bool? {
@ -115,20 +115,20 @@
fn first { fn first {
"Retrieves the first element of an ordered collection--a tuple or a list. If the collection is empty, returns nil." "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 & ("") -> nil
(xs as :list) -> base :first (xs) (xs as :list) -> base :first (xs)
(xs as :tuple) -> base :first (xs) & (xs as :tuple) -> base :first (xs)
(str as :string) -> base :slice (str, 0, 1) & (str as :string) -> base :slice (str, 0, 1)
} }
fn rest { fn rest {
"Returns all but the first element of a list or tuple, as a list." "Returns all but the first element of a list or tuple, as a list."
([]) -> [] ([]) -> []
(()) -> () & (()) -> ()
(xs as :list) -> base :rest (xs) (xs as :list) -> base :rest (xs)
(xs as :tuple) -> base :rest (xs) & (xs as :tuple) -> base :rest (xs)
(str as :string) -> base :rest (str) & (str as :string) -> base :rest (str)
} }
fn inc { fn inc {
@ -203,18 +203,53 @@ fn fold {
} }
} }
fn map { & 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." & "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, _) & & (f as :fn) -> map (f, _)
& (kw as :keyword) -> map (kw, _) & & (kw as :keyword) -> map (kw, _)
(f as :fn, xs) -> { & (f as :fn, xs) -> {
fn mapper (prev, curr) -> append (prev, f (curr)) & fn mapper (prev, curr) -> append (prev, f (curr))
fold (mapper, xs, []) & 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 { fn add {
@ -231,7 +266,7 @@ fn add {
& nil? & nil?
& some? & some?
& some & some
& eq? eq?
& bool? & bool?
& true? & true?
& false? & false?
@ -241,7 +276,7 @@ fn add {
& fn? & fn?
first first
rest rest
inc & inc
& dec & dec
& count & count
& empty? & empty?
@ -249,8 +284,12 @@ fn add {
& list? & list?
& list & list
& first & first
fold & fold
append & append
map & map
& filter
& keep
& concat
& contains?
add add
} }

View File

@ -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. 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. So now we'll do all bindings relative to the stack base.
UGH. 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.

View File

@ -22,7 +22,7 @@ pub fn eq(x: &Value, y: &Value) -> Value {
pub fn add(x: &Value, y: &Value) -> Value { pub fn add(x: &Value, y: &Value) -> Value {
match (x, y) { match (x, y) {
(Value::Number(x), Value::Number(y)) => Value::Number(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}"),
} }
} }

View File

@ -259,6 +259,7 @@ pub struct Chunk {
pub keywords: Vec<&'static str>, pub keywords: Vec<&'static str>,
pub string_patterns: Vec<StrPattern>, pub string_patterns: Vec<StrPattern>,
pub env: imbl::HashMap<&'static str, Value>, pub env: imbl::HashMap<&'static str, Value>,
pub msgs: Vec<String>,
} }
impl Chunk { impl Chunk {
@ -272,7 +273,7 @@ impl Chunk {
| Mult | Div | Unbox | BoxStore | Assert | Get | At | Not | Panic | EmptyString | Mult | Div | Unbox | BoxStore | Assert | Get | At | Not | Panic | EmptyString
| ConcatStrings | Stringify | MatchType | Return | Match | Print | AppendList | ConcatStrings | Stringify | MatchType | Return | Match | Print | AppendList
| ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing | PushGlobal | ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing | PushGlobal
| SetUpvalue | Msg => { | SetUpvalue => {
println!("{i:04}: {op}") println!("{i:04}: {op}")
} }
Constant | MatchConstant => { Constant | MatchConstant => {
@ -283,6 +284,12 @@ impl Chunk {
println!("{i:04}: {:16} {idx:05}: {value}", op.to_string()); println!("{i:04}: {:16} {idx:05}: {value}", op.to_string());
*i += 2; *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 PushBinding | MatchTuple | MatchSplattedTuple | LoadSplattedTuple | MatchList
| MatchSplattedList | LoadSplattedList | MatchDict | MatchSplattedDict | MatchSplattedList | LoadSplattedList | MatchDict | MatchSplattedDict
| DropDictEntry | LoadDictValue | PushTuple | PushBox | MatchDepth | PopN | StoreN | DropDictEntry | LoadDictValue | PushTuple | PushBox | MatchDepth | PopN | StoreN
@ -370,6 +377,7 @@ pub struct Compiler<'a> {
pub upvalues: Vec<&'static str>, pub upvalues: Vec<&'static str>,
loop_info: Vec<LoopInfo>, loop_info: Vec<LoopInfo>,
tail_pos: bool, tail_pos: bool,
debug: bool,
} }
fn is_binding(expr: &Spanned<Ast>) -> bool { fn is_binding(expr: &Spanned<Ast>) -> bool {
@ -393,6 +401,7 @@ impl<'a> Compiler<'a> {
src: &'static str, src: &'static str,
enclosing: Option<&'a Compiler>, enclosing: Option<&'a Compiler>,
env: imbl::HashMap<&'static str, Value>, env: imbl::HashMap<&'static str, Value>,
debug: bool,
) -> Compiler<'a> { ) -> Compiler<'a> {
let chunk = Chunk { let chunk = Chunk {
constants: vec![], constants: vec![],
@ -400,6 +409,7 @@ impl<'a> Compiler<'a> {
keywords: vec![], keywords: vec![],
string_patterns: vec![], string_patterns: vec![],
env, env,
msgs: vec![],
}; };
Compiler { Compiler {
chunk, chunk,
@ -417,6 +427,7 @@ impl<'a> Compiler<'a> {
src, src,
name, name,
tail_pos: false, tail_pos: false,
debug,
} }
} }
@ -512,13 +523,14 @@ impl<'a> Compiler<'a> {
} }
pub fn bind(&mut self, name: &'static str) { 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 { let binding = Binding {
name, name,
depth: self.scope_depth, depth: self.scope_depth,
stack_pos: self.stack_depth - self.match_depth - 1, stack_pos: self.stack_depth - self.match_depth - 1,
}; };
println!("{:?}", binding); println!("{:?}", binding);
println!("stack: {}; match: {}", self.stack_depth, self.match_depth);
self.bindings.push(binding); self.bindings.push(binding);
} }
@ -536,6 +548,7 @@ impl<'a> Compiler<'a> {
} }
fn resolve_binding(&mut self, name: &'static str) { 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) { if let Some(pos) = self.resolve_local(name) {
self.emit_op(Op::PushBinding); self.emit_op(Op::PushBinding);
self.emit_byte(pos); self.emit_byte(pos);
@ -598,10 +611,17 @@ impl<'a> Compiler<'a> {
} }
fn msg(&mut self, str: String) { 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.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) { pub fn compile(&mut self) {
@ -686,13 +706,17 @@ impl<'a> Compiler<'a> {
let tail_pos = self.tail_pos; let tail_pos = self.tail_pos;
self.tail_pos = false; self.tail_pos = false;
self.visit(cond); self.visit(cond);
self.report_depth("after condition");
let jif_idx = self.stub_jump(Op::JumpIfFalse); let jif_idx = self.stub_jump(Op::JumpIfFalse);
self.stack_depth -= 1; self.stack_depth -= 1;
self.tail_pos = tail_pos; self.tail_pos = tail_pos;
self.visit(then); self.visit(then);
self.report_depth("after consequent");
let jump_idx = self.stub_jump(Op::Jump); let jump_idx = self.stub_jump(Op::Jump);
self.visit(r#else);
self.stack_depth -= 1; self.stack_depth -= 1;
self.visit(r#else);
self.report_depth("after alternative");
// self.stack_depth += 1;
let end_idx = self.len(); let end_idx = self.len();
let jif_offset = jump_idx - jif_idx; let jif_offset = jump_idx - jif_idx;
let jump_offset = end_idx - jump_idx - 3; let jump_offset = end_idx - jump_idx - 3;
@ -1031,11 +1055,13 @@ impl<'a> Compiler<'a> {
let tail_pos = self.tail_pos; let tail_pos = self.tail_pos;
self.tail_pos = false; self.tail_pos = false;
match (&first.0, &second.0) { 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(first);
self.visit(second); self.visit(second);
self.emit_op(Op::GetKey); self.emit_op(Op::GetKey);
self.stack_depth -= 1; self.stack_depth -= 1;
self.report_depth("after keyword access");
} }
(Keyword(_), Arguments(args)) => { (Keyword(_), Arguments(args)) => {
self.visit(&args[0]); self.visit(&args[0]);
@ -1080,6 +1106,7 @@ impl<'a> Compiler<'a> {
self.stack_depth = stack_depth + 1; self.stack_depth = stack_depth + 1;
} }
(Word(fn_name), Arguments(args)) => { (Word(fn_name), Arguments(args)) => {
self.report_depth_str(format!("calling function {fn_name}"));
if has_placeholder(args) { if has_placeholder(args) {
let arity = args.len(); let arity = args.len();
for arg in args { for arg in args {
@ -1105,6 +1132,7 @@ impl<'a> Compiler<'a> {
} }
self.resolve_binding(fn_name); 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) // 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 { if rest.is_empty() && tail_pos {
self.emit_op(Op::TailCall); self.emit_op(Op::TailCall);
} else { } else {
@ -1275,6 +1303,7 @@ impl<'a> Compiler<'a> {
self.src, self.src,
Some(self), Some(self),
self.chunk.env.clone(), self.chunk.env.clone(),
self.debug,
); );
compiler.emit_op(Op::ResetMatch); compiler.emit_op(Op::ResetMatch);
compilers.insert(arity, compiler); compilers.insert(arity, compiler);
@ -1347,7 +1376,7 @@ impl<'a> Compiler<'a> {
for (arity, mut compiler) in compilers { for (arity, mut compiler) in compilers {
compiler.emit_op(Op::PanicNoMatch); compiler.emit_op(Op::PanicNoMatch);
let chunk = compiler.chunk; let chunk = compiler.chunk;
if crate::DEBUG_COMPILE { if self.debug {
println!("=== function chuncktion: {name}/{arity} ==="); println!("=== function chuncktion: {name}/{arity} ===");
chunk.dissasemble(); chunk.dissasemble();
} }
@ -1444,18 +1473,27 @@ impl<'a> Compiler<'a> {
self.visit(member); self.visit(member);
} }
self.msg(format!( self.msg(format!(
"entering loop with stack depth of {}", "***entering loop with stack depth of {}",
self.stack_depth self.stack_depth
)); ));
self.emit_op(Op::StoreN); self.emit_op(Op::StoreN);
self.emit_byte(members.len()); self.emit_byte(members.len());
let arity = members.len(); let arity = members.len();
// self.pop();
let stack_depth = self.stack_depth; 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 //then, save the beginning of the loop
self.emit_op(Op::Load); self.emit_op(Op::Load);
self.enter_loop(); self.enter_loop();
// self.stack_depth += arity; // self.stack_depth += arity;
//next, compile each clause: //next, compile each clause:
self.msg(format!(
"***after load, stack depth is now {}",
self.stack_depth
));
let mut clauses = clauses.iter(); let mut clauses = clauses.iter();
let mut jump_idxes = vec![]; let mut jump_idxes = vec![];
while let Some((Ast::MatchClause(pattern, guard, body), _)) = clauses.next() { while let Some((Ast::MatchClause(pattern, guard, body), _)) = clauses.next() {
@ -1482,9 +1520,18 @@ impl<'a> Compiler<'a> {
self.stack_depth -= 1; self.stack_depth -= 1;
} }
self.tail_pos = tail_pos; self.tail_pos = tail_pos;
self.msg(format!(
"***before visiting body, the stack depth is {}",
self.stack_depth
));
self.visit(body); self.visit(body);
self.msg(format!(
"***after visiting loop body, the stack depth is {}",
self.stack_depth
));
self.emit_op(Op::Store); 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() { while let Some(binding) = self.bindings.last() {
if binding.depth > self.scope_depth { if binding.depth > self.scope_depth {
self.bindings.pop(); self.bindings.pop();
@ -1492,6 +1539,9 @@ impl<'a> Compiler<'a> {
break; 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 { while self.stack_depth > stack_depth {
self.pop(); self.pop();
} }
@ -1511,6 +1561,11 @@ impl<'a> Compiler<'a> {
self.leave_loop(); self.leave_loop();
} }
Recur(args) => { 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; let tail_pos = self.tail_pos;
self.tail_pos = false; self.tail_pos = false;
let mut argnum = 0; let mut argnum = 0;
@ -1519,6 +1574,10 @@ impl<'a> Compiler<'a> {
argnum += 1; argnum += 1;
self.visit(arg); 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_op(Op::StoreN);
self.emit_byte(args.len()); self.emit_byte(args.len());
self.emit_op(Op::PopN); self.emit_op(Op::PopN);

View File

@ -1,9 +1,12 @@
use chumsky::{input::Stream, prelude::*}; use chumsky::{input::Stream, prelude::*};
use imbl::HashMap; use imbl::HashMap;
use std::env; use std::env;
use std::fs;
const DEBUG_COMPILE: bool = true; const DEBUG_SCRIPT_COMPILE: bool = true;
const DEBUG_RUN: bool = true; const DEBUG_SCRIPT_RUN: bool = true;
const DEBUG_PRELUDE_COMPILE: bool = false;
const DEBUG_PRELUDE_RUN: bool = false;
mod base; mod base;
@ -63,13 +66,20 @@ pub fn prelude() -> HashMap<&'static str, Value> {
} }
let parsed: &'static Spanned<Ast> = Box::leak(Box::new(parsed)); let parsed: &'static Spanned<Ast> = 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.emit_constant(base);
compiler.bind("base"); compiler.bind("base");
compiler.compile(); compiler.compile();
let chunk = compiler.chunk; 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(); let prelude = vm.run().clone().unwrap();
match prelude { match prelude {
Value::Dict(hashmap) => *hashmap, Value::Dict(hashmap) => *hashmap,
@ -111,26 +121,26 @@ pub fn run(src: &'static str) {
return; 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(); // let base = base::make_base();
// compiler.emit_constant(base); // compiler.emit_constant(base);
// compiler.bind("base"); // compiler.bind("base");
compiler.compile(); compiler.compile();
if DEBUG_COMPILE { if DEBUG_SCRIPT_COMPILE {
println!("=== source code ==="); println!("=== source code ===");
println!("{src}"); println!("{src}");
compiler.disassemble(); compiler.disassemble();
println!("\n\n") println!("\n\n")
} }
if DEBUG_RUN { if DEBUG_SCRIPT_RUN {
println!("=== vm run: test ==="); println!("=== vm run: test ===");
} }
let vm_chunk = compiler.chunk; 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 result = vm.run();
let output = match result { let output = match result {
Ok(val) => val.show(), Ok(val) => val.show(),
@ -140,10 +150,35 @@ pub fn run(src: &'static str) {
println!("{output}"); println!("{output}");
} }
pub fn ld_fmt(src: &'static str) -> Result<String, String> {
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<Ast> = Box::leak(Box::new(parse_result.unwrap()));
Ok(parsed.0.show())
}
pub fn main() { pub fn main() {
env::set_var("RUST_BACKTRACE", "1"); env::set_var("RUST_BACKTRACE", "1");
let src = r#" let src: &'static str = fs::read_to_string("sandbox.ld").unwrap().leak();
map(inc, [1, 2, 3]) match ld_fmt(src) {
"#; Ok(src) => println!("{}", src),
run(src); Err(msg) => println!("Could not format source with errors:\n{}", msg),
}
} }

View File

@ -120,6 +120,164 @@ pub enum Ast {
DictPattern(Vec<Spanned<Self>>), DictPattern(Vec<Spanned<Self>>),
} }
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::<Vec<_>>()
.join(", ")
)
}
List(members) | ListPattern(members) => format!(
"[{}]",
members
.iter()
.map(|(member, _)| member.show())
.collect::<Vec<_>>()
.join(", ")
),
Arguments(members) => format!(
"({})",
members
.iter()
.map(|(member, _)| member.show())
.collect::<Vec<_>>()
.join(", ")
),
Tuple(members) | TuplePattern(members) => format!(
"({})",
members
.iter()
.map(|(member, _)| member.show())
.collect::<Vec<_>>()
.join(", ")
),
Synthetic(root, first, rest) => format!(
"{} {} {}",
root.0.show(),
first.0.show(),
rest.iter()
.map(|(term, _)| term.show())
.collect::<Vec<_>>()
.join(" ")
),
When(clauses) => format!(
"when {{\n {}\n}}",
clauses
.iter()
.map(|(clause, _)| clause.show())
.collect::<Vec<_>>()
.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::<Vec<_>>()
.join("\n ")
),
FnBody(clauses) => format!(
"{}",
clauses
.iter()
.map(|(clause, _)| clause.show())
.collect::<Vec<_>>()
.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::<Vec<_>>()
.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::<Vec<_>>()
.join("\n ")
),
Recur(args) => format!(
"recur ({})",
args.iter()
.map(|(arg, _)| arg.show())
.collect::<Vec<_>>()
.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 { impl fmt::Display for Ast {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Ast::*; use Ast::*;

View File

@ -26,7 +26,7 @@ impl LFn {
match self { match self {
LFn::Declared { .. } => unreachable!(), LFn::Declared { .. } => unreachable!(),
LFn::Defined { closed, .. } => { LFn::Defined { closed, .. } => {
println!("closing over in {}: {value}", self.name()); println!("closing over in {}: {}", self.name(), value.show());
closed.borrow_mut().push(value); closed.borrow_mut().push(value);
} }
} }
@ -209,7 +209,7 @@ impl std::fmt::Display for Value {
impl Value { impl Value {
pub fn show(&self) -> String { pub fn show(&self) -> String {
use Value::*; use Value::*;
match &self { let mut out = match &self {
Nil => "nil".to_string(), Nil => "nil".to_string(),
True => "true".to_string(), True => "true".to_string(),
False => "false".to_string(), False => "false".to_string(),
@ -244,7 +244,13 @@ impl Value {
Fn(lfn) => format!("fn {}", lfn.name()), Fn(lfn) => format!("fn {}", lfn.name()),
Partial(partial) => format!("fn {}/partial", partial.name), Partial(partial) => format!("fn {}/partial", partial.name),
BaseFn(_) => format!("{self}"), BaseFn(_) => format!("{self}"),
Nothing => unreachable!(), Nothing => "_".to_string(),
};
if out.len() > 20 {
out.truncate(20);
format!("{out}...")
} else {
out
} }
} }

View File

@ -88,10 +88,11 @@ pub struct Vm {
pub matches: bool, pub matches: bool,
pub match_depth: u8, pub match_depth: u8,
pub result: Option<Result<Value, Panic>>, pub result: Option<Result<Value, Panic>>,
debug: bool,
} }
impl Vm { impl Vm {
pub fn new(chunk: Chunk) -> Vm { pub fn new(chunk: Chunk, debug: bool) -> Vm {
let lfn = LFn::Defined { let lfn = LFn::Defined {
name: "user script", name: "user script",
doc: None, doc: None,
@ -116,6 +117,7 @@ impl Vm {
matches: false, matches: false,
match_depth: 0, match_depth: 0,
result: None, result: None,
debug,
} }
} }
@ -136,12 +138,21 @@ impl Vm {
} }
pub fn print_stack(&self) { pub fn print_stack(&self) {
let inner = self let mut inner = vec![];
.stack for (i, value) in self.stack.iter().enumerate() {
.iter() if i == self.frame.stack_base {
.map(|val| val.to_string()) inner.push(format!("->{}<-", value.show()))
.collect::<Vec<_>>() } else {
.join("|"); inner.push(value.show())
}
}
let inner = inner.join("|");
// let inner = self
// .stack
// .iter()
// .map(|val| val.show())
// .collect::<Vec<_>>()
// .join("|");
let register = self let register = self
.return_register .return_register
.iter() .iter()
@ -194,7 +205,7 @@ impl Vm {
self.result = Some(Ok(self.stack.pop().unwrap())); self.result = Some(Ok(self.stack.pop().unwrap()));
return; return;
}; };
if crate::DEBUG_RUN { if self.debug {
self.print_debug(); self.print_debug();
} }
let op = Op::from_u8(*byte).unwrap(); let op = Op::from_u8(*byte).unwrap();
@ -875,8 +886,11 @@ impl Vm {
let val = self.pop(); let val = self.pop();
if crate::DEBUG_RUN { if self.debug {
println!("=== tail call into {val}/{arity} ==="); println!(
"=== tail call into {val}/{arity} from {} ===",
self.frame.function.as_fn().name()
);
} }
match val { match val {
@ -930,7 +944,7 @@ impl Vm {
self.ip = 0; self.ip = 0;
if crate::DEBUG_RUN {} if crate::DEBUG_SCRIPT_RUN {}
} }
Value::BaseFn(base_fn) => { Value::BaseFn(base_fn) => {
let value = match (arity, base_fn) { let value = match (arity, base_fn) {
@ -991,7 +1005,7 @@ impl Vm {
let val = self.pop(); let val = self.pop();
if crate::DEBUG_RUN { if crate::DEBUG_SCRIPT_RUN {
println!("=== calling into {val}/{arity} ==="); println!("=== calling into {val}/{arity} ===");
} }
@ -1076,7 +1090,7 @@ impl Vm {
} }
} }
Return => { Return => {
if crate::DEBUG_RUN { if crate::DEBUG_SCRIPT_RUN {
println!("== returning from {} ==", self.frame.function.show()) println!("== returning from {} ==", self.frame.function.show())
} }
self.frame = self.call_stack.pop().unwrap(); self.frame = self.call_stack.pop().unwrap();
@ -1108,9 +1122,7 @@ impl Vm {
} }
} }
Msg => { Msg => {
let msg = self.pop(); self.ip += 2;
println!("{msg}");
self.ip += 1;
} }
} }
} }