lots of bugs fixed--upvalues, bindings, stack manipulations, tail calls, etc.

This commit is contained in:
Scott Richmond 2025-06-21 22:25:08 -04:00
parent 583262f9e8
commit 25a0c62dcf
7 changed files with 165 additions and 177 deletions

View File

@ -217,6 +217,12 @@ fn map {
& }
}
fn add {
() -> 0
(x) -> x
(x, y) -> base :add (x, y)
}
#{
& type
& coll?
@ -233,7 +239,8 @@ fn map {
& not
& tuple?
& fn?
& rest
first
rest
inc
& dec
& count
@ -242,7 +249,8 @@ fn map {
& list?
& list
& first
& fold
& append
fold
append
map
add
}

View File

@ -422,3 +422,15 @@ https://craftinginterpreters.com/closures.html#flattening-upvalues.
I need to study and adapt this exact set of problems.
I believe I need to take the strategy he uses with closures being different from functions, etc.
So: rework the closures strategy here.
***
Closures strategy mostly unfucked.
Now I'm having some difficulty with bindings, again?
Current situation: still trying to get `map` and `fold` to work properly.
The bindings inside of non-trivial functions goes weird.
The scope depths are all out of whack.
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.

View File

@ -5,10 +5,10 @@ use std::rc::Rc;
#[derive(Clone, Debug)]
pub enum BaseFn {
Nullary(fn() -> Value),
Unary(fn(&Value) -> Value),
Binary(fn(&Value, &Value) -> Value),
Ternary(fn(&Value, &Value, &Value) -> Value),
Nullary(&'static str, fn() -> Value),
Unary(&'static str, fn(&Value) -> Value),
Binary(&'static str, fn(&Value, &Value) -> Value),
Ternary(&'static str, fn(&Value, &Value, &Value) -> Value),
}
pub fn eq(x: &Value, y: &Value) -> Value {
@ -585,57 +585,60 @@ pub fn r#mod(x: &Value, y: &Value) -> Value {
pub fn make_base() -> Value {
let members = vec![
("add", Value::BaseFn(BaseFn::Binary(add))),
("append", Value::BaseFn(BaseFn::Binary(append))),
("assoc", Value::BaseFn(BaseFn::Ternary(assoc))),
("at", Value::BaseFn(BaseFn::Binary(at))),
("atan_2", Value::BaseFn(BaseFn::Binary(atan_2))),
("bool", Value::BaseFn(BaseFn::Unary(r#bool))),
("ceil", Value::BaseFn(BaseFn::Unary(ceil))),
("chars", Value::BaseFn(BaseFn::Unary(chars))),
("concat", Value::BaseFn(BaseFn::Binary(concat))),
("cos", Value::BaseFn(BaseFn::Unary(cos))),
("count", Value::BaseFn(BaseFn::Unary(count))),
("dec", Value::BaseFn(BaseFn::Unary(dec))),
("dissoc", Value::BaseFn(BaseFn::Binary(dissoc))),
("div", Value::BaseFn(BaseFn::Binary(div))),
("doc!", Value::BaseFn(BaseFn::Unary(doc))),
("downcase", Value::BaseFn(BaseFn::Unary(downcase))),
("eq?", Value::BaseFn(BaseFn::Binary(eq))),
("first", Value::BaseFn(BaseFn::Unary(first))),
("floor", Value::BaseFn(BaseFn::Unary(floor))),
("get", Value::BaseFn(BaseFn::Binary(get))),
("gt?", Value::BaseFn(BaseFn::Binary(gt))),
("gte?", Value::BaseFn(BaseFn::Binary(gte))),
("inc", Value::BaseFn(BaseFn::Unary(inc))),
("last", Value::BaseFn(BaseFn::Unary(last))),
("list", Value::BaseFn(BaseFn::Unary(list))),
("lt?", Value::BaseFn(BaseFn::Binary(lt))),
("lte?", Value::BaseFn(BaseFn::Binary(lte))),
("mod", Value::BaseFn(BaseFn::Binary(r#mod))),
("mult", Value::BaseFn(BaseFn::Binary(mult))),
("number", Value::BaseFn(BaseFn::Unary(number))),
("add", Value::BaseFn(BaseFn::Binary("add", add))),
("append", Value::BaseFn(BaseFn::Binary("append", append))),
("assoc", Value::BaseFn(BaseFn::Ternary("assoc", assoc))),
("at", Value::BaseFn(BaseFn::Binary("at", at))),
("atan_2", Value::BaseFn(BaseFn::Binary("atan_2", atan_2))),
("bool", Value::BaseFn(BaseFn::Unary("bool", r#bool))),
("ceil", Value::BaseFn(BaseFn::Unary("ceil", ceil))),
("chars", Value::BaseFn(BaseFn::Unary("chars", chars))),
("concat", Value::BaseFn(BaseFn::Binary("concat", concat))),
("cos", Value::BaseFn(BaseFn::Unary("cos", cos))),
("count", Value::BaseFn(BaseFn::Unary("count", count))),
("dec", Value::BaseFn(BaseFn::Unary("dec", dec))),
("dissoc", Value::BaseFn(BaseFn::Binary("dissoc", dissoc))),
("div", Value::BaseFn(BaseFn::Binary("div", div))),
("doc!", Value::BaseFn(BaseFn::Unary("doc!", doc))),
(
"downcase",
Value::BaseFn(BaseFn::Unary("downcase", downcase)),
),
("eq?", Value::BaseFn(BaseFn::Binary("eq?", eq))),
("first", Value::BaseFn(BaseFn::Unary("first", first))),
("floor", Value::BaseFn(BaseFn::Unary("floor", floor))),
("get", Value::BaseFn(BaseFn::Binary("get", get))),
("gt?", Value::BaseFn(BaseFn::Binary("gt?", gt))),
("gte?", Value::BaseFn(BaseFn::Binary("gte?", gte))),
("inc", Value::BaseFn(BaseFn::Unary("inc", inc))),
("last", Value::BaseFn(BaseFn::Unary("last", last))),
("list", Value::BaseFn(BaseFn::Unary("list", list))),
("lt?", Value::BaseFn(BaseFn::Binary("lt?", lt))),
("lte?", Value::BaseFn(BaseFn::Binary("lte?", lte))),
("mod", Value::BaseFn(BaseFn::Binary("mod", r#mod))),
("mult", Value::BaseFn(BaseFn::Binary("mult", mult))),
("number", Value::BaseFn(BaseFn::Unary("number", number))),
("pi", Value::Number(std::f64::consts::PI)),
("print!", Value::BaseFn(BaseFn::Unary(print))),
("random", Value::BaseFn(BaseFn::Nullary(random))),
("range", Value::BaseFn(BaseFn::Binary(range))),
("rest", Value::BaseFn(BaseFn::Unary(rest))),
("round", Value::BaseFn(BaseFn::Unary(round))),
("show", Value::BaseFn(BaseFn::Unary(show))),
("sin", Value::BaseFn(BaseFn::Unary(sin))),
("slice", Value::BaseFn(BaseFn::Ternary(slice))),
("split", Value::BaseFn(BaseFn::Binary(split))),
("sqrt", Value::BaseFn(BaseFn::Unary(sqrt))),
("print!", Value::BaseFn(BaseFn::Unary("print!", print))),
("random", Value::BaseFn(BaseFn::Nullary("random", random))),
("range", Value::BaseFn(BaseFn::Binary("range", range))),
("rest", Value::BaseFn(BaseFn::Unary("rest", rest))),
("round", Value::BaseFn(BaseFn::Unary("round", round))),
("show", Value::BaseFn(BaseFn::Unary("show", show))),
("sin", Value::BaseFn(BaseFn::Unary("sin", sin))),
("slice", Value::BaseFn(BaseFn::Ternary("slice", slice))),
("split", Value::BaseFn(BaseFn::Binary("split", split))),
("sqrt", Value::BaseFn(BaseFn::Unary("sqrt", sqrt))),
("sqrt_2", Value::Number(std::f64::consts::SQRT_2)),
("store!", Value::BaseFn(BaseFn::Binary(store))),
("sub", Value::BaseFn(BaseFn::Binary(sub))),
("tan", Value::BaseFn(BaseFn::Unary(tan))),
("trim", Value::BaseFn(BaseFn::Unary(trim))),
("triml", Value::BaseFn(BaseFn::Unary(triml))),
("trimr", Value::BaseFn(BaseFn::Unary(trimr))),
("type", Value::BaseFn(BaseFn::Unary(r#type))),
("unbox", Value::BaseFn(BaseFn::Unary(unbox))),
("upcase", Value::BaseFn(BaseFn::Unary(upcase))),
("store!", Value::BaseFn(BaseFn::Binary("store!", store))),
("sub", Value::BaseFn(BaseFn::Binary("sub", sub))),
("tan", Value::BaseFn(BaseFn::Unary("tan", tan))),
("trim", Value::BaseFn(BaseFn::Unary("trim", trim))),
("triml", Value::BaseFn(BaseFn::Unary("triml", triml))),
("trimr", Value::BaseFn(BaseFn::Unary("trimr", trimr))),
("type", Value::BaseFn(BaseFn::Unary("type", r#type))),
("unbox", Value::BaseFn(BaseFn::Unary("unbox", unbox))),
("upcase", Value::BaseFn(BaseFn::Unary("upcase", upcase))),
];
Value::Dict(Box::new(HashMap::from(members)))
}

View File

@ -26,7 +26,7 @@ pub enum Op {
PushBinding,
PushGlobal,
Store,
StoreAt,
StoreN,
Stash,
Load,
ResetMatch,
@ -96,6 +96,8 @@ pub enum Op {
Print,
SetUpvalue,
GetUpvalue,
Msg,
// Inc,
// Dec,
// Gt,
@ -140,6 +142,7 @@ impl std::fmt::Display for Op {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use Op::*;
let rep = match self {
Msg => "msg",
Noop => "noop",
Nothing => "nothing",
Nil => "nil",
@ -154,7 +157,7 @@ impl std::fmt::Display for Op {
PushBinding => "push_binding",
PushGlobal => "push_global",
Store => "store",
StoreAt => "store_at",
StoreN => "store_n",
Stash => "stash",
Load => "load",
Match => "match",
@ -268,7 +271,8 @@ impl Chunk {
| Duplicate | Decrement | Truncate | Noop | LoadTuple | LoadList | Eq | Add | Sub
| Mult | Div | Unbox | BoxStore | Assert | Get | At | Not | Panic | EmptyString
| ConcatStrings | Stringify | MatchType | Return | Match | Print | AppendList
| ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing | PushGlobal => {
| ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing | PushGlobal
| SetUpvalue | Msg => {
println!("{i:04}: {op}")
}
Constant | MatchConstant => {
@ -281,9 +285,8 @@ impl Chunk {
}
PushBinding | MatchTuple | MatchSplattedTuple | LoadSplattedTuple | MatchList
| MatchSplattedList | LoadSplattedList | MatchDict | MatchSplattedDict
| DropDictEntry | LoadDictValue | PushTuple | PushBox | MatchDepth | PopN | StoreAt
| Call | SetUpvalue | GetUpvalue | Partial | MatchString | PushStringMatches
| TailCall => {
| DropDictEntry | LoadDictValue | PushTuple | PushBox | MatchDepth | PopN | StoreN
| Call | GetUpvalue | Partial | MatchString | PushStringMatches | TailCall => {
let next = self.bytecode[*i + 1];
println!("{i:04}: {:16} {next:03}", op.to_string());
*i += 1;
@ -364,7 +367,7 @@ pub struct Compiler<'a> {
pub src: &'static str,
pub name: &'static str,
pub enclosing: Option<&'a Compiler<'a>>,
pub upvalues: Vec<Upvalue>,
pub upvalues: Vec<&'static str>,
loop_info: Vec<LoopInfo>,
tail_pos: bool,
}
@ -383,12 +386,6 @@ fn has_placeholder(args: &[Spanned<Ast>]) -> bool {
args.iter().any(|arg| matches!(arg, (Ast::Placeholder, _)))
}
fn as_two_bytes(x: usize) -> (u8, u8) {
let low = x as u8;
let high = (x >> 8) as u8;
(high, low)
}
impl<'a> Compiler<'a> {
pub fn new(
ast: &'static Spanned<Ast>,
@ -535,27 +532,7 @@ impl<'a> Compiler<'a> {
}
fn resolve_upvalue(&self, name: &'static str) -> Option<usize> {
self.upvalues.iter().position(|uv| uv.name == name)
}
fn get_upvalue(&self, name: &'static str) -> Upvalue {
let local = self.bindings.iter().find(|b| b.name == name);
match local {
Some(binding) => {
let upvalue = Upvalue {
name,
stack_pos: binding.stack_pos,
};
println!("found upvalue {name} in {}", self.name);
upvalue
}
None => {
println!("Getting upvalue {name}");
let upvalue = self.enclosing.unwrap().get_upvalue(name);
println!("upvalue: {:?}", upvalue);
upvalue
}
}
self.upvalues.iter().position(|uv| *uv == name)
}
fn resolve_binding(&mut self, name: &'static str) {
@ -576,40 +553,12 @@ impl<'a> Compiler<'a> {
self.emit_op(Op::PushGlobal);
return;
}
let upvalue = self.get_upvalue(name);
self.emit_op(Op::GetUpvalue);
self.emit_byte(self.upvalues.len());
self.upvalues.push(upvalue);
self.upvalues.push(name);
self.stack_depth += 1;
}
fn resolve_binding_old(&mut self, name: &'static str) {
match self.resolve_local(name) {
Some(position) => {
self.emit_op(Op::PushBinding);
self.emit_byte(position);
self.stack_depth += 1;
}
None => match self.resolve_upvalue(name) {
Some(position) => {
println!("resolved upvalue: {name} at {position}");
self.emit_op(Op::GetUpvalue);
self.emit_byte(position);
self.stack_depth += 1;
}
None => {
println!("setting upvalue: {name}");
let upvalue = self.get_upvalue(name);
self.emit_op(Op::GetUpvalue);
self.emit_byte(self.upvalues.len());
self.upvalues.push(upvalue);
dbg!(&self.upvalues);
self.stack_depth += 1;
}
},
}
}
fn pop(&mut self) {
self.emit_op(Op::Pop);
self.stack_depth -= 1;
@ -648,6 +597,13 @@ impl<'a> Compiler<'a> {
self.loop_info.last().unwrap().stack_root
}
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;
}
pub fn compile(&mut self) {
use Ast::*;
match self.ast {
@ -1377,7 +1333,7 @@ impl<'a> Compiler<'a> {
for idx in no_match_jumps {
compiler.patch_jump(idx, compiler.len() - idx - 3);
}
compiler.scope_depth -= 1;
// compiler.scope_depth -= 1;
std::mem::swap(&mut compiler.upvalues, &mut upvalues);
}
@ -1442,8 +1398,9 @@ impl<'a> Compiler<'a> {
}
for upvalue in upvalues {
self.resolve_binding(upvalue);
self.emit_op(Op::SetUpvalue);
self.emit_byte(upvalue.stack_pos);
self.stack_depth -= 1;
}
}
FnDeclaration(name) => {
@ -1483,17 +1440,21 @@ impl<'a> Compiler<'a> {
let (Ast::Tuple(members), _) = value.as_ref() else {
unreachable!()
};
for (i, member) in members.iter().enumerate() {
for member in members {
self.visit(member);
self.emit_op(Op::StoreAt);
self.emit_byte(i);
}
self.msg(format!(
"entering loop with stack depth of {}",
self.stack_depth
));
self.emit_op(Op::StoreN);
self.emit_byte(members.len());
let arity = members.len();
let stack_depth = self.stack_depth;
//then, save the beginning of the loop
self.emit_op(Op::Load);
self.enter_loop();
self.stack_depth += arity;
// self.stack_depth += arity;
//next, compile each clause:
let mut clauses = clauses.iter();
let mut jump_idxes = vec![];
@ -1550,15 +1511,21 @@ impl<'a> Compiler<'a> {
self.leave_loop();
}
Recur(args) => {
for (i, arg) in args.iter().enumerate() {
let tail_pos = self.tail_pos;
self.tail_pos = false;
let mut argnum = 0;
for arg in args {
self.msg(format!("recur arg: {argnum}"));
argnum += 1;
self.visit(arg);
self.emit_op(Op::StoreAt);
self.emit_byte(i);
}
self.emit_op(Op::StoreN);
self.emit_byte(args.len());
self.emit_op(Op::PopN);
self.emit_byte(self.loop_root());
self.emit_byte(self.stack_depth - self.loop_root());
self.emit_op(Op::Load);
self.jump(Op::JumpBack, self.len() - self.loop_idx());
self.tail_pos = tail_pos;
}
Panic(msg) => {
self.visit(msg);

View File

@ -99,8 +99,8 @@ pub fn run(src: &'static str) {
// in any event, the AST should live forever
let parsed: &'static Spanned<Ast> = Box::leak(Box::new(parse_result.unwrap()));
// let prelude = prelude();
let prelude = imbl::HashMap::new();
let prelude = prelude();
// let prelude = imbl::HashMap::new();
let mut validator = Validator::new(&parsed.0, &parsed.1, "user input", src, prelude.clone());
validator.validate();
@ -111,8 +111,6 @@ pub fn run(src: &'static str) {
return;
}
// let prelude = imbl::HashMap::new();
let mut compiler = Compiler::new(parsed, "test", src, None, prelude);
// let base = base::make_base();
// compiler.emit_constant(base);
@ -145,15 +143,7 @@ pub fn run(src: &'static str) {
pub fn main() {
env::set_var("RUST_BACKTRACE", "1");
let src = r#"
let foo = {
fn quux () -> {
let bar = :bar
let baz = :baz
fn frobulate () -> (bar, baz, baz)
}
}
foo () ()
map(inc, [1, 2, 3])
"#;
run(src);
}

View File

@ -192,7 +192,15 @@ impl std::fmt::Display for Value {
),
Box(value) => write!(f, "box {{ {} }}", value.as_ref().borrow()),
Fn(lfn) => write!(f, "fn {}", lfn.name()),
BaseFn(_) => write!(f, "base fn"),
BaseFn(inner) => {
let name = match inner {
crate::base::BaseFn::Nullary(name, _)
| crate::base::BaseFn::Unary(name, _)
| crate::base::BaseFn::Binary(name, _)
| crate::base::BaseFn::Ternary(name, _) => name,
};
write!(f, "fn {name}/base")
}
Partial(partial) => write!(f, "fn {}/partial", partial.name),
}
}
@ -235,7 +243,7 @@ impl Value {
Box(x) => format!("box {{ {} }}", x.as_ref().borrow().show()),
Fn(lfn) => format!("fn {}", lfn.name()),
Partial(partial) => format!("fn {}/partial", partial.name),
BaseFn(_) => "base fn".to_string(),
BaseFn(_) => format!("{self}"),
Nothing => unreachable!(),
}
}

View File

@ -317,9 +317,11 @@ impl Vm {
self.push(Value::Nothing);
self.ip += 1;
}
StoreAt => {
let i = self.chunk().bytecode[self.ip + 1] as usize;
self.return_register[i] = self.pop();
StoreN => {
let n = self.chunk().bytecode[self.ip + 1] as usize;
for i in (0..n).rev() {
self.return_register[i] = self.pop();
}
self.ip += 2;
}
Stash => {
@ -873,6 +875,10 @@ impl Vm {
let val = self.pop();
if crate::DEBUG_RUN {
println!("=== tail call into {val}/{arity} ===");
}
match val {
Value::Fn(_) => {
if !val.as_fn().accepts(arity) {
@ -924,20 +930,18 @@ impl Vm {
self.ip = 0;
if crate::DEBUG_RUN {
println!("== tail call into {} ==", self.frame.function.show());
}
if crate::DEBUG_RUN {}
}
Value::BaseFn(base_fn) => {
let value = match (arity, base_fn) {
(0, BaseFn::Nullary(f)) => f(),
(1, BaseFn::Unary(f)) => f(&self.pop()),
(2, BaseFn::Binary(f)) => {
(0, BaseFn::Nullary(_, f)) => f(),
(1, BaseFn::Unary(_, f)) => f(&self.pop()),
(2, BaseFn::Binary(_, f)) => {
let y = &self.pop();
let x = &self.pop();
f(x, y)
}
(3, BaseFn::Ternary(f)) => {
(3, BaseFn::Ternary(_, f)) => {
let z = &self.pop();
let y = &self.pop();
let x = &self.pop();
@ -977,10 +981,6 @@ impl Vm {
self.call_stack.push(frame);
self.ip = 0;
if crate::DEBUG_RUN {
println!("== calling into {} ==", self.frame.function.show());
}
}
_ => return self.panic_with(format!("{} is not a function", val.show())),
}
@ -991,6 +991,10 @@ impl Vm {
let val = self.pop();
if crate::DEBUG_RUN {
println!("=== calling into {val}/{arity} ===");
}
match val {
Value::Fn(_) => {
if !val.as_fn().accepts(arity) {
@ -1024,21 +1028,17 @@ impl Vm {
self.call_stack.push(frame);
self.ip = 0;
if crate::DEBUG_RUN {
println!("== calling into {} ==", self.frame.function.show());
}
}
Value::BaseFn(base_fn) => {
let value = match (arity, base_fn) {
(0, BaseFn::Nullary(f)) => f(),
(1, BaseFn::Unary(f)) => f(&self.pop()),
(2, BaseFn::Binary(f)) => {
(0, BaseFn::Nullary(_, f)) => f(),
(1, BaseFn::Unary(_, f)) => f(&self.pop()),
(2, BaseFn::Binary(_, f)) => {
let y = &self.pop();
let x = &self.pop();
f(x, y)
}
(3, BaseFn::Ternary(f)) => {
(3, BaseFn::Ternary(_, f)) => {
let z = &self.pop();
let y = &self.pop();
let x = &self.pop();
@ -1071,10 +1071,6 @@ impl Vm {
self.call_stack.push(frame);
self.ip = 0;
if crate::DEBUG_RUN {
println!("== calling into {} ==", self.frame.function.show());
}
}
_ => return self.panic_with(format!("{} is not a function", val.show())),
}
@ -1095,13 +1091,12 @@ impl Vm {
self.ip += 1;
}
SetUpvalue => {
let idx = self.chunk().bytecode[self.ip + 1];
self.ip += 2;
let closed_idx = idx as usize + self.frame.stack_base;
let closed_over = self.stack[closed_idx].clone();
if let Value::Fn(lfn) = self.peek() {
lfn.close(closed_over);
}
let value = self.pop();
let Value::Fn(lfn) = self.peek() else {
panic!("expected function closing over value, got {}", self.peek());
};
lfn.close(value);
self.ip += 1;
}
GetUpvalue => {
let idx = self.chunk().bytecode[self.ip + 1];
@ -1112,6 +1107,11 @@ impl Vm {
unreachable!();
}
}
Msg => {
let msg = self.pop();
println!("{msg}");
self.ip += 1;
}
}
}
}