Compare commits

...

4 Commits

Author SHA1 Message Date
Scott Richmond
813a79a415 add a sandbox file to replace in-code Rust string 2025-06-22 14:40:27 -04:00
Scott Richmond
0b27944d11 improve bytecode readability by reporting patterns 2025-06-22 14:39:52 -04:00
Scott Richmond
f58a5f14b5 improve bytecode readability by reporting patterns 2025-06-22 14:38:29 -04:00
Scott Richmond
2f60de79a2 keep grinding on loop/recur/jif stack mismatch; add ast->code printer 2025-06-22 14:04:43 -04:00
9 changed files with 470 additions and 79 deletions

View File

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

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

29
sandbox.ld Normal file
View File

@ -0,0 +1,29 @@
let test = 2
& loop ([1, 2, 3]) with {
& ([]) -> false
& ([x]) -> eq? (x, test)
& ([x, ...xs]) -> {
& let foo = :bar
& when {
& eq? (x, test) -> true
& :else -> recur (xs)
& }
& }
& }
fn not {
(false) -> true
(nil) -> true
(_) -> false
}
loop ([1, 2, 3]) with {
([]) -> false
([x]) -> eq? (x, test)
([x, ...xs]) -> if not (eq? (x, test))
then recur (xs)
else true
}

View File

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

View File

@ -259,6 +259,7 @@ pub struct Chunk {
pub keywords: Vec<&'static str>,
pub string_patterns: Vec<StrPattern>,
pub env: imbl::HashMap<&'static str, Value>,
pub msgs: Vec<String>,
}
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<LoopInfo>,
tail_pos: bool,
debug: bool,
}
fn is_binding(expr: &Spanned<Ast>) -> 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,21 @@ 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));
}
fn report_ast(&mut self, label: String, node: &'static Spanned<Ast>) {
self.msg(format!("***{label}: {}", node.0.show()))
}
pub fn compile(&mut self) {
@ -649,6 +673,7 @@ impl<'a> Compiler<'a> {
self.emit_op(Op::ResetMatch);
self.visit(expr);
let expr_pos = self.stack_depth - 1;
self.report_ast("let binding: matching".to_string(), patt);
self.visit(patt);
self.emit_op(Op::PanicIfNoMatch);
self.emit_op(Op::PushBinding);
@ -686,13 +711,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;
@ -703,6 +732,7 @@ impl<'a> Compiler<'a> {
self.match_depth = 0;
self.emit_op(Op::ResetMatch);
self.visit(expr);
self.report_ast("let binding: matching".to_string(), patt);
self.visit(patt);
self.emit_op(Op::PanicIfNoMatch);
}
@ -1031,11 +1061,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 +1112,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 +1138,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 {
@ -1180,6 +1214,7 @@ impl<'a> Compiler<'a> {
while let Some((MatchClause(pattern, guard, body), _)) = clauses.next() {
self.tail_pos = false;
let mut no_match_jumps = vec![];
self.report_ast("match clause: ".to_string(), pattern);
self.scope_depth += 1;
self.match_depth = 0;
self.visit(pattern);
@ -1256,6 +1291,7 @@ impl<'a> Compiler<'a> {
let MatchClause(pattern, guard, clause_body) = &clause.0 else {
unreachable!()
};
let full_pattern = pattern;
let TuplePattern(pattern) = &pattern.0 else {
unreachable!()
};
@ -1275,6 +1311,7 @@ impl<'a> Compiler<'a> {
self.src,
Some(self),
self.chunk.env.clone(),
self.debug,
);
compiler.emit_op(Op::ResetMatch);
compilers.insert(arity, compiler);
@ -1290,6 +1327,7 @@ impl<'a> Compiler<'a> {
std::mem::swap(&mut upvalues, &mut compiler.upvalues);
let mut tup_jump_idxes = vec![];
compiler.report_ast("function clause matching: ".to_string(), full_pattern);
for member in pattern {
compiler.match_depth -= 1;
compiler.emit_op(Op::MatchDepth);
@ -1347,7 +1385,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 +1482,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() {
@ -1467,6 +1514,7 @@ impl<'a> Compiler<'a> {
};
self.match_depth = arity;
let mut jnm_idxes = vec![];
self.report_ast("loop clause matching: ".to_string(), pattern);
for member in members {
self.match_depth -= 1;
self.emit_op(Op::MatchDepth);
@ -1482,9 +1530,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 +1549,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 +1571,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 +1584,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);

View File

@ -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<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.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,36 @@ pub fn run(src: &'static str) {
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() {
env::set_var("RUST_BACKTRACE", "1");
let src = r#"
map(inc, [1, 2, 3])
"#;
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),
}
run(src);
}

View File

@ -120,6 +120,164 @@ pub enum Ast {
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 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Ast::*;

View File

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

View File

@ -88,10 +88,11 @@ pub struct Vm {
pub matches: bool,
pub match_depth: u8,
pub result: Option<Result<Value, Panic>>,
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::<Vec<_>>()
.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::<Vec<_>>()
// .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;
}
}
}