Compare commits

...

7 Commits

Author SHA1 Message Date
Scott Richmond
4f80c500a2 so turtle graphics appears to work? 2025-06-24 15:58:14 -04:00
Scott Richmond
d9f0b44bed so many things: DRY out VM, fix repeat tail calls, etc. 2025-06-24 15:50:02 -04:00
Scott Richmond
0290bb3bf2 update (fix?) partial function application stack discipline 2025-06-23 21:22:28 -04:00
Scott Richmond
772c56a6df fix upvalue resolution for forward-declared functions 2025-06-23 20:26:26 -04:00
Scott Richmond
42a5f599f7 fix upvalue resolution for forward-declared functions 2025-06-23 20:06:40 -04:00
Scott Richmond
f2bae26e1c fix upvalue resolution, hopefully for real this time 2025-06-23 19:17:53 -04:00
Scott Richmond
987cc172c1 moar debugging: find issues with upvalues 2025-06-23 18:59:12 -04:00
10 changed files with 1303 additions and 10999 deletions

View File

@ -968,8 +968,7 @@ fn err? {
fn unwrap! { fn unwrap! {
"Takes a result tuple. If it's :ok, then returns the value. If it's not :ok, then it panics. If it's not a result tuple, it also panics." "Takes a result tuple. If it's :ok, then returns the value. If it's not :ok, then it panics. If it's not a result tuple, it also panics."
((:ok, value)) -> value ((:ok, value)) -> value
((:err, msg)) -> panic! string ("Unwrapped :err! ", msg) ((:err, msg)) -> panic! "Unwrapped :err! {msg}"
(_) -> panic! "Cannot unwrap something that's not an error tuple."
} }
fn unwrap_or { fn unwrap_or {
@ -1147,14 +1146,15 @@ fn loadstate! {
fn apply_command { fn apply_command {
"Takes a turtle state and a command and calculates a new state." "Takes a turtle state and a command and calculates a new state."
(state, command) -> match command with { (state, command) -> {
match command with {
(:goto, (x, y)) -> assoc (state, :position, (x, y)) (:goto, (x, y)) -> assoc (state, :position, (x, y))
(:home) -> do state > (:home) -> do state >
assoc (_, :position, (0, 0)) > assoc (_, :position, (0, 0)) >
assoc (_, :heading, 0) assoc (_, :heading, 0)
(:clear) -> do state > & (:clear) -> do state >
assoc (state, :position, (0, 0)) > & assoc (state, :position, (0, 0)) >
assoc (_, :heading, 0) & assoc (_, :heading, 0)
(:right, turns) -> update (state, :heading, add (_, turns)) (:right, turns) -> update (state, :heading, add (_, turns))
(:left, turns) -> update (state, :heading, sub (_, turns)) (:left, turns) -> update (state, :heading, sub (_, turns))
(:forward, steps) -> { (:forward, steps) -> {
@ -1178,7 +1178,7 @@ fn apply_command {
(:show) -> assoc (state, :visible?, true) (:show) -> assoc (state, :visible?, true)
(:hide) -> assoc (state, :visible?, false) (:hide) -> assoc (state, :visible?, false)
(:background, _) -> state (:background, _) -> state
} }}
} }
& position () -> (x, y) & position () -> (x, y)
@ -1210,168 +1210,171 @@ fn penwidth {
box state = nil box state = nil
#{ #{
type apply_command
coll? add_command!
ordered?
assoc? abs
nil? abs
some? add
some angle
eq?
bool?
true?
false?
bool
not
tuple?
fn?
first
rest
inc
dec
count
empty?
any? any?
list?
list
first
fold
foldr
append append
map assert!
filter assoc
keep assoc?
concat at
contains? atan/2
print! back!
show background!
report! between?
doc! bg!
string bk!
string? bool
join bool?
split box?
trim butlast
upcase ceil
downcase
chars chars
chars/safe chars/safe
ws? clear!
strip coll?
words colors
sentence concat
to_number contains?
box? cos
unbox count
store! dec
update! deg/rad
add deg/turn
sub dict
mult dict?
dissoc
dist
div div
div/0 div/0
div/safe div/safe
doc!
downcase
each!
empty?
eq?
err
err?
even?
false?
fd!
filter
first
first
first
floor
fn?
fold
foldr
forward!
get
goto!
gt?
gte?
has?
heading
heading/vector
hideturtle!
home!
inc
inv inv
inv/0 inv/0
inv/safe inv/safe
abs join
neg keep
zero? keys
gt? keyword?
gte? last
left!
list
list?
loadstate!
lt!
lt? lt?
lte? lte?
between? map
neg?
pos?
abs
pi
tau
turn/deg
deg/turn
turn/rad
rad/turn
deg/rad
rad/deg
sin
cos
tan
rotate
atan/2
angle
mod mod
mod/0 mod/0
mod/safe mod/safe
even? mult
neg
neg?
nil?
not
odd? odd?
square
sqrt
sqrt/safe
dist
heading/vector
floor
ceil
round
range
at
first
second
last
butlast
slice
keyword?
assoc
dissoc
update
keys
values
get
has?
dict
dict?
each!
random
random_int
ok ok
ok? ok?
err ordered?
err? pc!
pd!
pencolor
pencolor!
pendown!
pendown?
penup!
penwidth
penwidth!
pi
pos?
position
print!
pu!
pw!
rad/deg
rad/turn
random
random_int
range
report!
rest
right!
rotate
round
rt!
second
sentence
setheading!
show
showturtle!
sin
slice
some
some?
split
sqrt
sqrt/safe
square
state
store!
string
string?
strip
sub
tan
tau
to_number
trim
true?
tuple?
turn/deg
turn/rad
turtle_commands
turtle_init
turtle_state
type
unbox
unwrap! unwrap!
unwrap_or unwrap_or
assert! upcase
colors update
turtle_init update!
turtle_commands values
turtle_state words
forward! ws?
fd! zero?
back!
bk!
left!
lt!
right!
rt!
penup!
pu!
pendown!
pd!
pencolor!
pc!
penwidth!
pw!
background!
bg!
home!
clear!
goto!
setheading!
showturtle!
hideturtle!
loadstate!
position
heading
pendown?
pencolor
penwidth
state
} }

View File

@ -494,8 +494,32 @@ Here's a list of things that need doing:
- I need this fixed for optimization reasons. - I need this fixed for optimization reasons.
- I _think_ I just fixed this by fixing tail position tracking in collections - I _think_ I just fixed this by fixing tail position tracking in collections
- [ ] test this - [ ] test this
- I did not fix it.
* [x] Dict patterns are giving me stack discipline grief. Why is stack discipline so hard? * [x] Dict patterns are giving me stack discipline grief. Why is stack discipline so hard?
* [ ] This is in the service of getting turtle graphics working * [ ] This is in the service of getting turtle graphics working
* Other forms in the language need help: * Other forms in the language need help:
* [ ] repeat needs its stack discipline updated, it currently crashes the compiler * [ ] repeat needs its stack discipline updated, it currently crashes the compiler
### More closure problems
#### 2025-06-23
My solution to closures wasn't quite right.
I can't use Uncle Bob's strategy of the recursive call, since Rust's ownership semantics make this onerous at best.
My solution: introduce the concept of a "compiler depth," with 0 being the global scope.
If the compiler's at 0 depth, we can pull it out of the environment.
If the compiler's at a depth > 0, then we can ask the enclosing compiler to stash the upvalue.
And thus we get what we need.
But: some functions in prelude aren't properly getting their closures, and I don't know why, since they *are* getting them properly in user scripts.
Take `apply_command`.
Next step: sort out if any other functions aren't getting things closed over properly.
PROBLEM: forward-declared functions weren't at the top of the stack when `Op::SetUpvalue` was called.
So all of `apply_command`'s upvalues were being attached to the function declared before it (which was sitting right there at the top of the stack.)
SOLUTION: test to see if the function has been forward-declared, and if it has, bring it to the top fo the stack.
NEW PROBLEM: a lot of instructions in the VM don't properly offset from the call frame's stack base, which leads to weirdness when doing things inside function calls.
NEW SOLUTION: create a function that does the offset properly, and replace everywhere we directly access the stack.
This is the thing I am about to do

View File

@ -1,46 +1,19 @@
let state = #{:position (0, 0), :heading 0, :pencolor :white} fn circle! () -> repeat 20 {
fd! (2)
rt! (inv (20))
}
let command = (:forward, 10) fn flower! () -> repeat 10 {
circle! ()
rt! (inv (10))
}
& match command with { fn garland! () -> repeat 10 {
& & (:goto, (x, y)) -> assoc (state, :position, (x, y)) flower! ()
& & (:home) -> do state > fd! (30)
& & assoc (_, :position, (0, 0)) > }
& & assoc (_, :heading, 0)
& & (:clear) -> do state >
& & assoc (state, :position, (0, 0)) >
& & assoc (_, :heading, 0)
& & (:right, turns) -> update (state, :heading, add (_, turns))
& & (:left, turns) -> update (state, :heading, sub (_, turns))
& (:forward, steps) -> {
& print! ("matched forward")
& let #{heading, position, ...} = state
& print! ("extracted {heading} and {position} from state")
& let unit = heading/vector (heading)
& print! ("unit vector at {heading}: {unit}")
& let vect = mult (steps, unit)
& print! ("update vector: {vect}")
& let new_state = update (state, :position, add (vect, _))
& print! ("new state: {new_state}")
& new_state
& }
& & (:back, steps) -> {
& & let #{heading, position, ...} = state
& & let unit = heading/vector (heading)
& & let vect = mult (steps, unit)
& & update (state, :position, sub (_, vect))
& & }
& & (:penup) -> assoc (state, :pendown?, false)
& & (:pendown) -> assoc (state, :pendown?, true)
& & (:penwidth, pixels) -> assoc (state, :penwidth, pixels)
& & (:pencolor, color) -> assoc (state, :pencolor, color)
& & (:setheading, heading) -> assoc (state, :heading, heading)
& & (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor) -> #{position, heading, visible?, pendown?, penwidth, pencolor}
& & (:show) -> assoc (state, :visible?, true)
& & (:hide) -> assoc (state, :visible?, false)
& & (:background, _) -> state
& }
let #{heading, position, ...x} = state
let unit = heading/vector (heading)
unit
garland! ()
do turtle_commands > unbox > print!
do turtle_state > unbox > print!

File diff suppressed because it is too large Load Diff

View File

@ -27,11 +27,11 @@ impl Chunk {
match op { match op {
Pop | Store | Stash | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse Pop | Store | Stash | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse
| PanicIfNoMatch | ResetMatch | GetKey | PanicNoWhen | PanicNoMatch | TypeOf | PanicIfNoMatch | ResetMatch | GetKey | PanicNoWhen | PanicNoMatch | TypeOf
| Duplicate | Decrement | Truncate | Noop | LoadTuple | LoadList | Eq | Add | Sub | Duplicate | Decrement | ToInt | Noop | LoadTuple | LoadList | Eq | Add | Sub
| 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 | UnconditionalMatch | Print
| ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing | PushGlobal | AppendList | ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing
| SetUpvalue => { | PushGlobal | SetUpvalue => {
println!("{i:04}: {op}") println!("{i:04}: {op}")
} }
Constant | MatchConstant => { Constant | MatchConstant => {

View File

@ -63,7 +63,7 @@ fn get_builtin(name: &str, arity: usize) -> Option<Op> {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Compiler<'a> { pub struct Compiler {
pub chunk: Chunk, pub chunk: Chunk,
pub bindings: Vec<Binding>, pub bindings: Vec<Binding>,
pub scope_depth: isize, pub scope_depth: isize,
@ -75,7 +75,7 @@ pub struct Compiler<'a> {
pub span: SimpleSpan, pub span: SimpleSpan,
pub src: &'static str, pub src: &'static str,
pub name: &'static str, pub name: &'static str,
pub enclosing: Option<&'a Compiler<'a>>, pub depth: usize,
pub upvalues: Vec<&'static str>, pub upvalues: Vec<&'static str>,
loop_info: Vec<LoopInfo>, loop_info: Vec<LoopInfo>,
tail_pos: bool, tail_pos: bool,
@ -96,15 +96,15 @@ fn has_placeholder(args: &[Spanned<Ast>]) -> bool {
args.iter().any(|arg| matches!(arg, (Ast::Placeholder, _))) args.iter().any(|arg| matches!(arg, (Ast::Placeholder, _)))
} }
impl<'a> Compiler<'a> { impl Compiler {
pub fn new( pub fn new(
ast: &'static Spanned<Ast>, ast: &'static Spanned<Ast>,
name: &'static str, name: &'static str,
src: &'static str, src: &'static str,
enclosing: Option<&'a Compiler>, depth: usize,
env: imbl::HashMap<&'static str, Value>, env: imbl::HashMap<&'static str, Value>,
debug: bool, debug: bool,
) -> Compiler<'a> { ) -> Compiler {
let chunk = Chunk { let chunk = Chunk {
constants: vec![], constants: vec![],
bytecode: vec![], bytecode: vec![],
@ -116,6 +116,7 @@ impl<'a> Compiler<'a> {
Compiler { Compiler {
chunk, chunk,
bindings: vec![], bindings: vec![],
depth,
scope_depth: -1, scope_depth: -1,
match_depth: 0, match_depth: 0,
stack_depth: 0, stack_depth: 0,
@ -124,7 +125,6 @@ impl<'a> Compiler<'a> {
ast: &ast.0, ast: &ast.0,
span: ast.1, span: ast.1,
loop_info: vec![], loop_info: vec![],
enclosing,
upvalues: vec![], upvalues: vec![],
src, src,
name, name,
@ -297,18 +297,18 @@ impl<'a> Compiler<'a> {
self.stack_depth += 1; self.stack_depth += 1;
return; return;
} }
if self.chunk.env.contains_key(name) { if self.depth == 0 {
self.msg("as global".to_string()); self.msg("as global".to_string());
self.emit_constant(Value::Keyword(name)); self.emit_constant(Value::Keyword(name));
self.emit_op(Op::PushGlobal); self.emit_op(Op::PushGlobal);
return; } else {
}
self.msg(format!("as enclosing upvalue {}", self.upvalues.len())); self.msg(format!("as enclosing upvalue {}", self.upvalues.len()));
self.emit_op(Op::GetUpvalue); self.emit_op(Op::GetUpvalue);
self.emit_byte(self.upvalues.len()); self.emit_byte(self.upvalues.len());
self.upvalues.push(name); self.upvalues.push(name);
self.stack_depth += 1; self.stack_depth += 1;
} }
}
fn duplicate(&mut self) { fn duplicate(&mut self) {
self.emit_op(Op::Duplicate); self.emit_op(Op::Duplicate);
@ -393,11 +393,13 @@ impl<'a> Compiler<'a> {
} }
fn msg(&mut self, str: String) { fn msg(&mut self, str: String) {
if self.debug {
self.emit_op(Op::Msg); self.emit_op(Op::Msg);
self.emit_byte(self.chunk.msgs.len()); self.emit_byte(self.chunk.msgs.len());
println!("{str}"); println!("{str}");
self.chunk.msgs.push(str); self.chunk.msgs.push(str);
} }
}
fn report_depth(&mut self, label: &'static str) { fn report_depth(&mut self, label: &'static str) {
self.msg(format!("***{label} stack depth: {}", self.stack_depth)); self.msg(format!("***{label} stack depth: {}", self.stack_depth));
@ -519,12 +521,12 @@ impl<'a> Compiler<'a> {
self.report_depth("after let binding"); self.report_depth("after let binding");
} }
WordPattern(name) => { WordPattern(name) => {
self.emit_op(Op::Match); self.emit_op(Op::UnconditionalMatch);
self.bind(name); self.bind(name);
} }
Word(name) | Splat(name) => self.resolve_binding(name), Word(name) | Splat(name) => self.resolve_binding(name),
PlaceholderPattern => { PlaceholderPattern => {
self.emit_op(Op::Match); self.emit_op(Op::UnconditionalMatch);
} }
NilPattern => { NilPattern => {
self.emit_op(Op::MatchNil); self.emit_op(Op::MatchNil);
@ -750,18 +752,18 @@ impl<'a> Compiler<'a> {
} }
Splattern(patt) => self.visit(patt), Splattern(patt) => self.visit(patt),
InterpolatedPattern(parts, _) => { InterpolatedPattern(parts, _) => {
println!("An interpolated pattern of {} parts", parts.len()); // println!("An interpolated pattern of {} parts", parts.len());
let mut pattern = "".to_string(); let mut pattern = "".to_string();
let mut words = vec![]; let mut words = vec![];
for (part, _) in parts { for (part, _) in parts {
match part { match part {
StringPart::Word(word) => { StringPart::Word(word) => {
println!("wordpart: {word}"); // println!("wordpart: {word}");
words.push(word.clone()); words.push(word.clone());
pattern.push_str("(.*)"); pattern.push_str("(.*)");
} }
StringPart::Data(data) => { StringPart::Data(data) => {
println!("datapart: {data}"); // println!("datapart: {data}");
let data = regex::escape(data); let data = regex::escape(data);
pattern.push_str(data.as_str()); pattern.push_str(data.as_str());
} }
@ -915,7 +917,7 @@ impl<'a> Compiler<'a> {
self.resolve_binding(fn_name); self.resolve_binding(fn_name);
self.emit_op(Op::Partial); self.emit_op(Op::Partial);
self.emit_byte(arity); self.emit_byte(arity);
self.stack_depth -= 1; self.stack_depth -= args.len() - 1;
} else { } else {
match get_builtin(fn_name, args.len()) { match get_builtin(fn_name, args.len()) {
Some(code) => { Some(code) => {
@ -1094,7 +1096,7 @@ impl<'a> Compiler<'a> {
clause, clause,
name, name,
self.src, self.src,
Some(self), self.depth + 1,
self.chunk.env.clone(), self.chunk.env.clone(),
self.debug, self.debug,
); );
@ -1122,7 +1124,7 @@ impl<'a> Compiler<'a> {
tup_jump_idxes.push(compiler.stub_jump(Op::JumpIfNoMatch)); tup_jump_idxes.push(compiler.stub_jump(Op::JumpIfNoMatch));
} }
if pattern.is_empty() { if pattern.is_empty() {
compiler.emit_op(Op::Match); compiler.emit_op(Op::UnconditionalMatch);
} }
let jump_idx = compiler.stub_jump(Op::Jump); let jump_idx = compiler.stub_jump(Op::Jump);
for idx in tup_jump_idxes { for idx in tup_jump_idxes {
@ -1195,7 +1197,6 @@ impl<'a> Compiler<'a> {
closed: RefCell::new(vec![]), closed: RefCell::new(vec![]),
}; };
// TODO: check if the function is already declared, and pull out the relevant OnceCell if need be
let the_fn = Value::Fn(Rc::new(lfn)); let the_fn = Value::Fn(Rc::new(lfn));
// self.emit_constant(the_fn); // self.emit_constant(the_fn);
// self.bind(name); // self.bind(name);
@ -1217,6 +1218,10 @@ impl<'a> Compiler<'a> {
}) })
.unwrap(); .unwrap();
self.chunk.constants[declaration_idx] = the_fn; self.chunk.constants[declaration_idx] = the_fn;
// if the function been forward-declared, bring it to the top of the stack
if declaration_idx < self.chunk.constants.len() - 1 {
self.resolve_binding(name);
}
} else { } else {
self.emit_constant(the_fn) self.emit_constant(the_fn)
} }
@ -1234,8 +1239,10 @@ impl<'a> Compiler<'a> {
} }
FnBody(_) => unreachable!(), FnBody(_) => unreachable!(),
Repeat(times, body) => { Repeat(times, body) => {
let tail_pos = self.tail_pos;
self.tail_pos = false;
self.visit(times); self.visit(times);
self.emit_op(Op::Truncate); self.emit_op(Op::ToInt);
// skip the decrement the first time // skip the decrement the first time
self.emit_op(Op::Jump); self.emit_op(Op::Jump);
self.emit_byte(0); self.emit_byte(0);
@ -1255,6 +1262,7 @@ impl<'a> Compiler<'a> {
self.patch_jump(jiz_idx, self.len() - repeat_begin - 4); self.patch_jump(jiz_idx, self.len() - repeat_begin - 4);
self.pop(); self.pop();
self.emit_constant(Value::Nil); self.emit_constant(Value::Nil);
self.tail_pos = tail_pos;
} }
Loop(value, clauses) => { Loop(value, clauses) => {
self.report_depth("entering loop"); self.report_depth("entering loop");

View File

@ -3,8 +3,8 @@ use imbl::HashMap;
use std::env; use std::env;
use std::fs; use std::fs;
const DEBUG_SCRIPT_COMPILE: bool = true; const DEBUG_SCRIPT_COMPILE: bool = false;
const DEBUG_SCRIPT_RUN: bool = true; const DEBUG_SCRIPT_RUN: bool = false;
const DEBUG_PRELUDE_COMPILE: bool = false; const DEBUG_PRELUDE_COMPILE: bool = false;
const DEBUG_PRELUDE_RUN: bool = false; const DEBUG_PRELUDE_RUN: bool = false;
@ -73,7 +73,7 @@ pub fn prelude() -> HashMap<&'static str, Value> {
parsed, parsed,
"prelude", "prelude",
PRELUDE, PRELUDE,
None, 0,
HashMap::new(), HashMap::new(),
DEBUG_PRELUDE_COMPILE, DEBUG_PRELUDE_COMPILE,
); );
@ -124,7 +124,7 @@ pub fn run(src: &'static str) {
return; return;
} }
let mut compiler = Compiler::new(parsed, "sandbox", src, None, prelude, DEBUG_SCRIPT_COMPILE); let mut compiler = Compiler::new(parsed, "sandbox", src, 0, 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");
@ -146,10 +146,12 @@ pub fn run(src: &'static str) {
let mut vm = Vm::new(vm_chunk, DEBUG_SCRIPT_RUN); 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.to_string(),
Err(panic) => format!("Ludus panicked! {panic}"), Err(panic) => format!("Ludus panicked! {panic}"),
}; };
if DEBUG_SCRIPT_RUN {
vm.print_stack(); vm.print_stack();
}
println!("{output}"); println!("{output}");
} }

View File

@ -21,7 +21,7 @@ pub enum Op {
Load, Load,
LoadN, LoadN,
ResetMatch, ResetMatch,
Match, UnconditionalMatch,
MatchNil, MatchNil,
MatchTrue, MatchTrue,
MatchFalse, MatchFalse,
@ -60,7 +60,7 @@ pub enum Op {
JumpIfZero, JumpIfZero,
Duplicate, Duplicate,
Decrement, Decrement,
Truncate, ToInt,
MatchDepth, MatchDepth,
Panic, Panic,
EmptyString, EmptyString,
@ -152,7 +152,7 @@ impl std::fmt::Display for Op {
Stash => "stash", Stash => "stash",
Load => "load", Load => "load",
LoadN => "load_n", LoadN => "load_n",
Match => "match", UnconditionalMatch => "match",
MatchNil => "match_nil", MatchNil => "match_nil",
MatchTrue => "match_true", MatchTrue => "match_true",
MatchFalse => "match_false", MatchFalse => "match_false",
@ -191,7 +191,7 @@ impl std::fmt::Display for Op {
JumpBack => "jump_back", JumpBack => "jump_back",
JumpIfZero => "jump_if_zero", JumpIfZero => "jump_if_zero",
Decrement => "decrement", Decrement => "decrement",
Truncate => "truncate", ToInt => "truncate",
Duplicate => "duplicate", Duplicate => "duplicate",
MatchDepth => "match_depth", MatchDepth => "match_depth",
Panic => "panic", Panic => "panic",

View File

@ -26,8 +26,12 @@ impl LFn {
match self { match self {
LFn::Declared { .. } => unreachable!(), LFn::Declared { .. } => unreachable!(),
LFn::Defined { closed, .. } => { LFn::Defined { closed, .. } => {
println!("closing over in {}: {}", self.name(), value.show()); let shown = value.show();
closed.borrow_mut().push(value); closed.borrow_mut().push(value);
let pos = closed.borrow().len();
if crate::DEBUG_SCRIPT_RUN {
println!("closing over in {} at {pos}: {shown}", self.name(),);
}
} }
} }
} }

411
src/vm.rs
View File

@ -90,6 +90,7 @@ pub struct Vm {
pub match_depth: u8, pub match_depth: u8,
pub result: Option<Result<Value, Panic>>, pub result: Option<Result<Value, Panic>>,
debug: bool, debug: bool,
last_code: usize,
} }
impl Vm { impl Vm {
@ -119,6 +120,7 @@ impl Vm {
match_depth: 0, match_depth: 0,
result: None, result: None,
debug, debug,
last_code: 0,
} }
} }
@ -148,24 +150,18 @@ impl Vm {
} }
} }
let inner = inner.join("|"); 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()
.map(|val| val.to_string()) .map(|val| val.to_string())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(","); .join(",");
println!("{:04}: [{inner}] ({register})", self.ip); println!("{:04}: [{inner}] ({register})", self.last_code);
} }
fn print_debug(&self) { fn print_debug(&self) {
self.print_stack(); self.print_stack();
let mut ip = self.ip; let mut ip = self.last_code;
self.chunk().dissasemble_instr(&mut ip); self.chunk().dissasemble_instr(&mut ip);
} }
@ -200,120 +196,104 @@ impl Vm {
self.result = Some(Err(Panic::String(msg))); self.result = Some(Err(Panic::String(msg)));
} }
fn get_value_at(&mut self, idx: u8) -> Value {
let idx = idx as usize;
let idx = idx + self.frame.stack_base;
self.stack[idx].clone()
}
fn get_scrutinee(&mut self) -> Value {
let idx = self.stack.len() - self.match_depth as usize - 1;
self.stack[idx].clone()
}
fn read(&mut self) -> u8 {
let code = self.chunk().bytecode[self.ip];
self.ip += 1;
code
}
fn read2(&mut self) -> usize {
let high = self.read();
let low = self.read();
combine_bytes(high, low)
}
fn at_end(&mut self) -> bool {
self.ip >= self.chunk().bytecode.len()
}
pub fn interpret(&mut self) { pub fn interpret(&mut self) {
loop { loop {
let Some(byte) = self.chunk().bytecode.get(self.ip) else { if self.at_end() {
self.result = Some(Ok(self.stack.pop().unwrap())); self.result = Some(Ok(self.stack.pop().unwrap()));
return; return;
}; }
let code = self.read();
if self.debug { if self.debug {
self.last_code = self.ip - 1;
self.print_debug(); self.print_debug();
} }
let op = Op::from_u8(*byte).unwrap(); let op = Op::from_u8(code).unwrap();
use Op::*; use Op::*;
match op { match op {
Noop => { Noop => (),
self.ip += 1; Nil => self.push(Value::Nil),
} Nothing => self.push(Value::Nothing),
Nil => { True => self.push(Value::True),
self.push(Value::Nil); False => self.push(Value::False),
self.ip += 1; Msg => {
} let _ = self.read();
Nothing => {
self.push(Value::Nothing);
self.ip += 1;
}
True => {
self.push(Value::True);
self.ip += 1;
}
False => {
self.push(Value::False);
self.ip += 1;
} }
Constant => { Constant => {
let high = self.chunk().bytecode[self.ip + 1]; let const_idx = self.read2();
let low = self.chunk().bytecode[self.ip + 2];
let const_idx = combine_bytes(high, low);
let value = self.chunk().constants[const_idx].clone(); let value = self.chunk().constants[const_idx].clone();
self.push(value); self.push(value);
self.ip += 3;
} }
Jump => { Jump => {
let high = self.chunk().bytecode[self.ip + 1]; let jump_len = self.read2();
let low = self.chunk().bytecode[self.ip + 2]; self.ip += jump_len;
let jump_len = combine_bytes(high, low);
// let jump_len = self.chunk().bytecode[self.ip + 1];
self.ip += jump_len + 3;
} }
JumpBack => { JumpBack => {
let high = self.chunk().bytecode[self.ip + 1]; let jump_len = self.read2();
let low = self.chunk().bytecode[self.ip + 2]; self.ip -= jump_len + 3;
let jump_len = combine_bytes(high, low);
// let jump_len = self.chunk().bytecode[self.ip + 1];
self.ip -= jump_len;
} }
JumpIfFalse => { JumpIfFalse => {
let high = self.chunk().bytecode[self.ip + 1]; let jump_len = self.read2();
let low = self.chunk().bytecode[self.ip + 2];
let jump_len = combine_bytes(high, low);
// let jump_len = self.chunk().bytecode[self.ip + 1];
let cond = self.pop(); let cond = self.pop();
match cond { match cond {
Value::Nil | Value::False => { Value::Nil | Value::False => self.ip += jump_len,
self.ip += jump_len + 3; _ => (),
}
_ => {
self.ip += 3;
}
} }
} }
JumpIfTrue => { JumpIfTrue => {
let high = self.chunk().bytecode[self.ip + 1]; let jump_len = self.read2();
let low = self.chunk().bytecode[self.ip + 2];
let jump_len = combine_bytes(high, low);
// let jump_len = self.chunk().bytecode[self.ip + 1];
let cond = self.pop(); let cond = self.pop();
match cond { match cond {
Value::Nil | Value::False => { Value::Nil | Value::False => (),
self.ip += 3; _ => self.ip += jump_len,
}
_ => {
self.ip += jump_len + 3;
}
} }
} }
JumpIfZero => { JumpIfZero => {
let high = self.chunk().bytecode[self.ip + 1]; let jump_len = self.read2();
let low = self.chunk().bytecode[self.ip + 2];
let jump_len = combine_bytes(high, low);
// let jump_len = self.chunk().bytecode[self.ip + 1];
let cond = self.pop(); let cond = self.pop();
match cond { match cond {
Value::Number(x) if x <= 0.0 => { Value::Number(x) if x <= 0.0 => self.ip += jump_len,
self.ip += jump_len + 3; Value::Number(..) => (),
}
Value::Number(..) => {
self.ip += 3;
}
_ => return self.panic("repeat requires a number"), _ => return self.panic("repeat requires a number"),
} }
} }
Pop => { Pop => {
self.pop(); self.pop();
self.ip += 1;
} }
PopN => { PopN => {
let n = self.chunk().bytecode[self.ip + 1] as usize; let n = self.read() as usize;
self.stack.truncate(self.stack.len() - n); self.stack.truncate(self.stack.len() - n);
self.ip += 2;
} }
PushBinding => { PushBinding => {
let binding_idx = let idx = self.read();
self.chunk().bytecode[self.ip + 1] as usize + self.frame.stack_base; let value = self.get_value_at(idx);
let binding_value = self.stack[binding_idx].clone(); self.push(value);
self.push(binding_value);
self.ip += 2;
} }
PushGlobal => { PushGlobal => {
let key = self.pop(); let key = self.pop();
@ -322,98 +302,74 @@ impl Vm {
}; };
let value = self.chunk().env.get(name).unwrap(); let value = self.chunk().env.get(name).unwrap();
self.push(value.clone()); self.push(value.clone());
self.ip += 1;
} }
Store => { Store => {
self.return_register[0] = self.pop(); self.return_register[0] = self.pop();
self.ip += 1;
} }
StoreN => { StoreN => {
let n = self.chunk().bytecode[self.ip + 1] as usize; let n = self.read() as usize;
for i in (0..n).rev() { for i in (0..n).rev() {
self.return_register[i] = self.pop(); self.return_register[i] = self.pop();
} }
self.ip += 2;
} }
Stash => { Stash => {
self.return_register[0] = self.peek().clone(); self.return_register[0] = self.peek().clone();
self.ip += 1;
} }
Load => { Load => {
let mut value = Value::Nothing; let mut value = Value::Nothing;
swap(&mut self.return_register[0], &mut value); swap(&mut self.return_register[0], &mut value);
self.push(value); self.push(value);
self.ip += 1;
} }
LoadN => { LoadN => {
let n = self.chunk().bytecode[self.ip + 1] as usize; let n = self.read() as usize;
for i in 0..n { for i in 0..n {
let mut value = Value::Nothing; let mut value = Value::Nothing;
swap(&mut self.return_register[i], &mut value); swap(&mut self.return_register[i], &mut value);
self.push(value); self.push(value);
} }
self.ip += 2;
} }
ResetMatch => { ResetMatch => {
self.matches = false; self.matches = false;
self.match_depth = 0; self.match_depth = 0;
self.ip += 1;
} }
Match => { UnconditionalMatch => {
self.matches = true; self.matches = true;
self.ip += 1;
} }
MatchType => { MatchType => {
let as_type = self.pop(); let as_type = self.pop();
let Value::Keyword(as_type) = as_type else { let Value::Keyword(as_type) = as_type else {
unreachable!() unreachable!()
}; };
let idx = self.stack.len() - self.match_depth as usize - 1; let value = self.get_scrutinee();
let val_type = self.stack[idx].type_of(); let val_type = value.type_of();
self.matches = val_type == as_type; self.matches = val_type == as_type;
self.ip += 1;
} }
MatchNil => { MatchNil => {
let idx = self.stack.len() - self.match_depth as usize - 1; let value = self.get_scrutinee();
if self.stack[idx] == Value::Nil { self.matches = value == Value::Nil;
self.matches = true;
};
self.ip += 1;
} }
MatchTrue => { MatchTrue => {
let idx = self.stack.len() - self.match_depth as usize - 1; let value = self.get_scrutinee();
if self.stack[idx] == Value::True { self.matches = value == Value::True;
self.matches = true;
};
self.ip += 1;
} }
MatchFalse => { MatchFalse => {
let idx = self.stack.len() - self.match_depth as usize - 1; let value = self.get_scrutinee();
if self.stack[idx] == Value::False { self.matches = value == Value::False;
self.matches = true;
}
self.ip += 1;
} }
PanicIfNoMatch => { PanicIfNoMatch => {
if !self.matches { if !self.matches {
return self.panic("no match"); return self.panic("no match");
} else {
self.ip += 1;
} }
} }
MatchConstant => { MatchConstant => {
let high = self.chunk().bytecode[self.ip + 1]; let const_idx = self.read2();
let low = self.chunk().bytecode[self.ip + 2]; let scrutinee = self.get_scrutinee();
let const_idx = combine_bytes(high, low); // let idx = self.stack.len() - self.match_depth as usize - 1;
let idx = self.stack.len() - self.match_depth as usize - 1; self.matches = scrutinee == self.chunk().constants[const_idx];
self.matches = self.stack[idx] == self.chunk().constants[const_idx];
self.ip += 3;
} }
MatchString => { MatchString => {
let pattern_idx = self.chunk().bytecode[self.ip + 1]; let pattern_idx = self.read();
self.ip += 2; let scrutinee = self.get_scrutinee();
let scrutinee_idx = self.stack.len() - self.match_depth as usize - 1;
let scrutinee = self.stack[scrutinee_idx].clone();
self.matches = match scrutinee { self.matches = match scrutinee {
Value::String(str) => self.chunk().string_patterns[pattern_idx as usize] Value::String(str) => self.chunk().string_patterns[pattern_idx as usize]
.re .re
@ -425,13 +381,12 @@ impl Vm {
}; };
} }
PushStringMatches => { PushStringMatches => {
let pattern_idx = self.chunk().bytecode[self.ip + 1]; let pattern_idx = self.read();
self.ip += 2;
let pattern_len = self.chunk().string_patterns[pattern_idx as usize] let pattern_len = self.chunk().string_patterns[pattern_idx as usize]
.words .words
.len(); .len();
let scrutinee_idx = self.stack.len() - self.match_depth as usize - 1; // let scrutinee_idx = self.stack.len() - self.match_depth as usize - 1;
let scrutinee = self.stack[scrutinee_idx].clone(); let scrutinee = self.get_scrutinee();
let scrutinee = match scrutinee { let scrutinee = match scrutinee {
Value::String(str) => str.as_ref().clone(), Value::String(str) => str.as_ref().clone(),
Value::Interned(str) => str.to_string(), Value::Interned(str) => str.to_string(),
@ -447,35 +402,32 @@ impl Vm {
self.match_depth += pattern_len as u8; self.match_depth += pattern_len as u8;
} }
MatchTuple => { MatchTuple => {
let idx = self.stack.len() - self.match_depth as usize - 1; // let idx = self.stack.len() - self.match_depth as usize - 1;
let tuple_len = self.chunk().bytecode[self.ip + 1]; let tuple_len = self.read() as usize;
let scrutinee = self.stack[idx].clone(); let scrutinee = self.get_scrutinee();
match scrutinee { match scrutinee {
Value::Tuple(members) => self.matches = members.len() == tuple_len as usize, Value::Tuple(members) => self.matches = members.len() == tuple_len,
_ => self.matches = false, _ => self.matches = false,
}; };
self.ip += 2;
} }
MatchSplattedTuple => { MatchSplattedTuple => {
let idx = self.stack.len() - self.match_depth as usize - 1; let patt_len = self.read() as usize;
let patt_len = self.chunk().bytecode[self.ip + 1]; let scrutinee = self.get_scrutinee();
let scrutinee = self.stack[idx].clone();
match scrutinee { match scrutinee {
Value::Tuple(members) => self.matches = members.len() >= patt_len as usize, Value::Tuple(members) => self.matches = members.len() >= patt_len,
_ => self.matches = false, _ => self.matches = false,
} }
self.ip += 2;
} }
PushTuple => { PushTuple => {
let tuple_len = self.chunk().bytecode[self.ip + 1]; let tuple_len = self.read() as usize;
let tuple_members = self.stack.split_off(self.stack.len() - tuple_len as usize); let tuple_members = self.stack.split_off(self.stack.len() - tuple_len);
let tuple = Value::Tuple(Rc::new(tuple_members)); let tuple = Value::Tuple(Rc::new(tuple_members));
self.push(tuple); self.push(tuple);
self.ip += 2;
} }
LoadTuple => { LoadTuple => {
let idx = self.stack.len() - self.match_depth as usize - 1; // let idx = self.stack.len() - self.match_depth as usize - 1;
let tuple = self.stack[idx].clone(); // let tuple = self.stack[idx].clone();
let tuple = self.get_scrutinee();
match tuple { match tuple {
Value::Tuple(members) => { Value::Tuple(members) => {
for member in members.iter() { for member in members.iter() {
@ -484,12 +436,10 @@ impl Vm {
} }
_ => return self.panic("internal error: expected tuple"), _ => return self.panic("internal error: expected tuple"),
}; };
self.ip += 1;
} }
LoadSplattedTuple => { LoadSplattedTuple => {
let load_len = self.chunk().bytecode[self.ip + 1] as usize; let load_len = self.read() as usize;
let idx = self.stack.len() - self.match_depth as usize - 1; let tuple = self.get_scrutinee();
let tuple = self.stack[idx].clone();
let Value::Tuple(members) = tuple else { let Value::Tuple(members) = tuple else {
return self.panic("internal error: expected tuple"); return self.panic("internal error: expected tuple");
}; };
@ -501,11 +451,9 @@ impl Vm {
splatted.push_back(members[i].clone()); splatted.push_back(members[i].clone());
} }
self.push(Value::List(Box::new(splatted))); self.push(Value::List(Box::new(splatted)));
self.ip += 2;
} }
PushList => { PushList => {
self.push(Value::List(Box::new(Vector::new()))); self.push(Value::List(Box::new(Vector::new())));
self.ip += 1;
} }
AppendList => { AppendList => {
let value = self.pop(); let value = self.pop();
@ -515,7 +463,6 @@ impl Vm {
}; };
list.push_back(value); list.push_back(value);
self.push(Value::List(list)); self.push(Value::List(list));
self.ip += 1;
} }
ConcatList => { ConcatList => {
let splatted = self.pop(); let splatted = self.pop();
@ -528,31 +475,28 @@ impl Vm {
}; };
target.append(*splatted); target.append(*splatted);
self.push(Value::List(target)); self.push(Value::List(target));
self.ip += 1;
} }
MatchList => { MatchList => {
let idx = self.stack.len() - self.match_depth as usize - 1; // let idx = self.stack.len() - self.match_depth as usize - 1;
let list_len = self.chunk().bytecode[self.ip + 1]; let list_len = self.read() as usize;
let scrutinee = self.stack[idx].clone(); let scrutinee = self.get_scrutinee();
match scrutinee { match scrutinee {
Value::List(members) => self.matches = members.len() == list_len as usize, Value::List(members) => self.matches = members.len() == list_len,
_ => self.matches = false, _ => self.matches = false,
}; };
self.ip += 2;
} }
MatchSplattedList => { MatchSplattedList => {
let idx = self.stack.len() - self.match_depth as usize - 1; // let idx = self.stack.len() - self.match_depth as usize - 1;
let patt_len = self.chunk().bytecode[self.ip + 1]; let patt_len = self.read() as usize;
let scrutinee = self.stack[idx].clone(); let scrutinee = self.get_scrutinee();
match scrutinee { match scrutinee {
Value::List(members) => self.matches = members.len() >= patt_len as usize, Value::List(members) => self.matches = members.len() >= patt_len,
_ => self.matches = false, _ => self.matches = false,
} }
self.ip += 2;
} }
LoadList => { LoadList => {
let idx = self.stack.len() - self.match_depth as usize - 1; // let idx = self.stack.len() - self.match_depth as usize - 1;
let list = self.stack[idx].clone(); let list = self.get_scrutinee();
match list { match list {
Value::List(members) => { Value::List(members) => {
for member in members.iter() { for member in members.iter() {
@ -561,12 +505,10 @@ impl Vm {
} }
_ => return self.panic("internal error: expected list"), _ => return self.panic("internal error: expected list"),
}; };
self.ip += 1;
} }
LoadSplattedList => { LoadSplattedList => {
let loaded_len = self.chunk().bytecode[self.ip + 1] as usize; let loaded_len = self.read() as usize;
let idx = self.stack.len() - self.match_depth as usize - 1; let list = self.get_scrutinee();
let list = self.stack[idx].clone();
let Value::List(members) = list else { let Value::List(members) = list else {
return self.panic("internal error: expected list"); return self.panic("internal error: expected list");
}; };
@ -575,11 +517,9 @@ impl Vm {
} }
let splatted = Value::List(Box::new(members.skip(loaded_len - 1))); let splatted = Value::List(Box::new(members.skip(loaded_len - 1)));
self.push(splatted); self.push(splatted);
self.ip += 2;
} }
PushDict => { PushDict => {
self.push(Value::Dict(Box::new(HashMap::new()))); self.push(Value::Dict(Box::new(HashMap::new())));
self.ip += 1;
} }
AppendDict => { AppendDict => {
let value = self.pop(); let value = self.pop();
@ -591,7 +531,6 @@ impl Vm {
}; };
dict.insert(key, value); dict.insert(key, value);
self.push(Value::Dict(dict)); self.push(Value::Dict(dict));
self.ip += 1;
} }
ConcatDict => { ConcatDict => {
let Value::Dict(splatted) = self.pop() else { let Value::Dict(splatted) = self.pop() else {
@ -602,39 +541,42 @@ impl Vm {
}; };
let union = splatted.union(*target); let union = splatted.union(*target);
self.push(Value::Dict(Box::new(union))); self.push(Value::Dict(Box::new(union)));
self.ip += 1;
} }
LoadDictValue => { LoadDictValue => {
let dict_idx = self.chunk().bytecode[self.ip + 1] as usize; let dict_idx = self.read();
let Value::Dict(dict) = self.stack[dict_idx].clone() else { let dict = match self.get_value_at(dict_idx) {
unreachable!("expected dict, got something else") Value::Dict(dict) => dict,
value => {
println!(
"internal Ludus error in function {}",
self.frame.function.as_fn().name()
);
unreachable!("expected dict, got {value}")
}
}; };
let Value::Keyword(key) = self.pop() else { let Value::Keyword(key) = self.pop() else {
unreachable!("expected keyword, got something else") unreachable!("expected keyword, got something else")
}; };
let value = dict.get(&key).unwrap_or(&Value::Nil); let value = dict.get(&key).unwrap_or(&Value::Nil);
self.push(value.clone()); self.push(value.clone());
self.ip += 2;
} }
MatchDict => { MatchDict => {
let idx = self.stack.len() - self.match_depth as usize - 1; // let idx = self.stack.len() - self.match_depth as usize - 1;
let dict_len = self.chunk().bytecode[self.ip + 1]; let dict_len = self.read();
let scrutinee = self.stack[idx].clone(); let scrutinee = self.get_scrutinee();
match scrutinee { match scrutinee {
Value::Dict(members) => self.matches = members.len() == dict_len as usize, Value::Dict(members) => self.matches = members.len() == dict_len as usize,
_ => self.matches = false, _ => self.matches = false,
}; };
self.ip += 2;
} }
MatchSplattedDict => { MatchSplattedDict => {
let idx = self.stack.len() - self.match_depth as usize - 1; // let idx = self.stack.len() - self.match_depth as usize - 1;
let patt_len = self.chunk().bytecode[self.ip + 1]; let patt_len = self.read() as usize;
let scrutinee = self.stack[idx].clone(); let scrutinee = self.get_scrutinee();
match scrutinee { match scrutinee {
Value::Dict(members) => self.matches = members.len() >= patt_len as usize, Value::Dict(members) => self.matches = members.len() >= patt_len,
_ => self.matches = false, _ => self.matches = false,
} }
self.ip += 2;
} }
DropDictEntry => { DropDictEntry => {
let Value::Keyword(key_to_drop) = self.pop() else { let Value::Keyword(key_to_drop) = self.pop() else {
@ -645,12 +587,10 @@ impl Vm {
}; };
dict.remove(key_to_drop); dict.remove(key_to_drop);
self.push(Value::Dict(dict)); self.push(Value::Dict(dict));
self.ip += 1;
} }
PushBox => { PushBox => {
let val = self.pop(); let val = self.pop();
self.push(Value::Box(Rc::new(RefCell::new(val)))); self.push(Value::Box(Rc::new(RefCell::new(val))));
self.ip += 1;
} }
GetKey => { GetKey => {
let key = self.pop(); let key = self.pop();
@ -663,40 +603,29 @@ impl Vm {
_ => Value::Nil, _ => Value::Nil,
}; };
self.push(value); self.push(value);
self.ip += 1;
} }
JumpIfNoMatch => { JumpIfNoMatch => {
let high = self.chunk().bytecode[self.ip + 1]; let jump_len = self.read2();
let low = self.chunk().bytecode[self.ip + 2];
let jump_len = combine_bytes(high, low);
// let jump_len = self.chunk().bytecode[self.ip + 1] as usize; // let jump_len = self.chunk().bytecode[self.ip + 1] as usize;
if !self.matches { if !self.matches {
self.ip += jump_len + 3; self.ip += jump_len
} else {
self.ip += 3;
} }
} }
JumpIfMatch => { JumpIfMatch => {
let high = self.chunk().bytecode[self.ip + 1]; let jump_len = self.read2();
let low = self.chunk().bytecode[self.ip + 2];
let jump_len = combine_bytes(high, low);
if self.matches { if self.matches {
self.ip += jump_len + 3; self.ip += jump_len;
} else {
self.ip += 3;
} }
} }
TypeOf => { TypeOf => {
let val = self.pop(); let val = self.pop();
let type_of = Value::Keyword(val.type_of()); let type_of = Value::Keyword(val.type_of());
self.push(type_of); self.push(type_of);
self.ip += 1;
} }
Truncate => { ToInt => {
let val = self.pop(); let val = self.pop();
if let Value::Number(x) = val { if let Value::Number(x) = val {
self.push(Value::Number(x as usize as f64)); self.push(Value::Number(x as usize as f64));
self.ip += 1;
} else { } else {
return self.panic("repeat requires a number"); return self.panic("repeat requires a number");
} }
@ -705,18 +634,15 @@ impl Vm {
let val = self.pop(); let val = self.pop();
if let Value::Number(x) = val { if let Value::Number(x) = val {
self.push(Value::Number(x - 1.0)); self.push(Value::Number(x - 1.0));
self.ip += 1;
} else { } else {
return self.panic("you may only decrement a number"); return self.panic("you may only decrement a number");
} }
} }
Duplicate => { Duplicate => {
self.push(self.peek().clone()); self.push(self.peek().clone());
self.ip += 1;
} }
MatchDepth => { MatchDepth => {
self.match_depth = self.chunk().bytecode[self.ip + 1]; self.match_depth = self.read();
self.ip += 2;
} }
PanicNoWhen | PanicNoMatch => { PanicNoWhen | PanicNoMatch => {
return self.panic("no match"); return self.panic("no match");
@ -729,7 +655,6 @@ impl Vm {
} else { } else {
self.push(Value::False) self.push(Value::False)
} }
self.ip += 1;
} }
Add => { Add => {
let first = self.pop(); let first = self.pop();
@ -739,7 +664,6 @@ impl Vm {
} else { } else {
return self.panic("`add` requires two numbers"); return self.panic("`add` requires two numbers");
} }
self.ip += 1;
} }
Sub => { Sub => {
let first = self.pop(); let first = self.pop();
@ -749,7 +673,6 @@ impl Vm {
} else { } else {
return self.panic("`sub` requires two numbers"); return self.panic("`sub` requires two numbers");
} }
self.ip += 1;
} }
Mult => { Mult => {
let first = self.pop(); let first = self.pop();
@ -759,7 +682,6 @@ impl Vm {
} else { } else {
return self.panic("`mult` requires two numbers"); return self.panic("`mult` requires two numbers");
} }
self.ip += 1;
} }
Div => { Div => {
let first = self.pop(); let first = self.pop();
@ -772,7 +694,6 @@ impl Vm {
} else { } else {
return self.panic("`div` requires two numbers"); return self.panic("`div` requires two numbers");
} }
self.ip += 1;
} }
Unbox => { Unbox => {
let the_box = self.pop(); let the_box = self.pop();
@ -782,7 +703,6 @@ impl Vm {
return self.panic("`unbox` requires a box"); return self.panic("`unbox` requires a box");
}; };
self.push(inner); self.push(inner);
self.ip += 1;
} }
BoxStore => { BoxStore => {
let new_value = self.pop(); let new_value = self.pop();
@ -793,14 +713,12 @@ impl Vm {
return self.panic("`store` requires a box"); return self.panic("`store` requires a box");
} }
self.push(new_value); self.push(new_value);
self.ip += 1;
} }
Assert => { Assert => {
let value = self.stack.last().unwrap(); let value = self.stack.last().unwrap();
if let Value::Nil | Value::False = value { if let Value::Nil | Value::False = value {
return self.panic("asserted falsy value"); return self.panic("asserted falsy value");
} }
self.ip += 1;
} }
Get => { Get => {
let key = self.pop(); let key = self.pop();
@ -813,7 +731,6 @@ impl Vm {
_ => return self.panic("keys must be keywords"), _ => return self.panic("keys must be keywords"),
}; };
self.push(value); self.push(value);
self.ip += 1;
} }
At => { At => {
let idx = self.pop(); let idx = self.pop();
@ -829,7 +746,6 @@ impl Vm {
_ => return self.panic("indexes must be numbers"), _ => return self.panic("indexes must be numbers"),
}; };
self.push(value); self.push(value);
self.ip += 1;
} }
Not => { Not => {
let value = self.pop(); let value = self.pop();
@ -838,7 +754,6 @@ impl Vm {
_ => Value::False, _ => Value::False,
}; };
self.push(negated); self.push(negated);
self.ip += 1;
} }
Panic => { Panic => {
let msg = self.pop().show(); let msg = self.pop().show();
@ -846,7 +761,6 @@ impl Vm {
} }
EmptyString => { EmptyString => {
self.push(Value::String(Rc::new("".to_string()))); self.push(Value::String(Rc::new("".to_string())));
self.ip += 1;
} }
//TODO: don't use the schlemiel's algo here //TODO: don't use the schlemiel's algo here
ConcatStrings => { ConcatStrings => {
@ -861,18 +775,15 @@ impl Vm {
_ => unreachable!(), _ => unreachable!(),
}; };
self.push(combined); self.push(combined);
self.ip += 1;
} }
Stringify => { Stringify => {
let to_stringify = self.pop(); let to_stringify = self.pop();
let the_string = to_stringify.stringify(); let the_string = to_stringify.stringify();
let stringified = Value::String(Rc::new(the_string)); let stringified = Value::String(Rc::new(the_string));
self.push(stringified); self.push(stringified);
self.ip += 1;
} }
Partial => { Partial => {
let arity = self.chunk().bytecode[self.ip + 1]; let arity = self.read();
self.ip += 2;
let the_fn = self.pop(); let the_fn = self.pop();
let Value::Fn(ref inner) = the_fn else { let Value::Fn(ref inner) = the_fn else {
return self.panic("only functions may be partially applied"); return self.panic("only functions may be partially applied");
@ -886,24 +797,23 @@ impl Vm {
self.push(Value::Partial(Rc::new(partial))); self.push(Value::Partial(Rc::new(partial)));
} }
TailCall => { TailCall => {
let arity = self.chunk().bytecode[self.ip + 1]; let arity = self.read();
self.ip += 2;
let val = self.pop(); let called = self.pop();
if self.debug { if self.debug {
println!( println!(
"=== tail call into {val}/{arity} from {} ===", "=== tail call into {called}/{arity} from {} ===",
self.frame.function.as_fn().name() self.frame.function.as_fn().name()
); );
} }
match val { match called {
Value::Fn(_) => { Value::Fn(_) => {
if !val.as_fn().accepts(arity) { if !called.as_fn().accepts(arity) {
return self.panic_with(format!( return self.panic_with(format!(
"wrong number of arguments to {} passing {arity} args", "wrong number of arguments to {} passing {arity} args",
val.show() called.show()
)); ));
} }
// first put the arguments in the register // first put the arguments in the register
@ -923,7 +833,7 @@ impl Vm {
i += 1; i += 1;
} }
let splat_arity = val.as_fn().splat_arity(); let splat_arity = called.as_fn().splat_arity();
if splat_arity > 0 && arity >= splat_arity { if splat_arity > 0 && arity >= splat_arity {
let splatted_args = self.stack.split_off( let splatted_args = self.stack.split_off(
self.stack.len() - (arity - splat_arity) as usize - 1, self.stack.len() - (arity - splat_arity) as usize - 1,
@ -938,7 +848,7 @@ impl Vm {
}; };
let mut frame = CallFrame { let mut frame = CallFrame {
function: val, function: called,
arity, arity,
stack_base: self.stack.len() - arity as usize, stack_base: self.stack.len() - arity as usize,
ip: 0, ip: 0,
@ -948,8 +858,6 @@ impl Vm {
frame.ip = self.ip; frame.ip = self.ip;
self.ip = 0; self.ip = 0;
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) {
@ -995,7 +903,7 @@ impl Vm {
let mut frame = CallFrame { let mut frame = CallFrame {
function: the_fn, function: the_fn,
arity: args.len() as u8, arity: args.len() as u8,
stack_base: self.stack.len() - arity as usize - 1, stack_base: self.stack.len() - args.len(),
ip: 0, ip: 0,
}; };
@ -1005,28 +913,29 @@ impl Vm {
self.call_stack.push(frame); self.call_stack.push(frame);
self.ip = 0; self.ip = 0;
} }
_ => return self.panic_with(format!("{} is not a function", val.show())), _ => {
return self.panic_with(format!("{} is not a function", called.show()))
}
} }
} }
Call => { Call => {
let arity = self.chunk().bytecode[self.ip + 1]; let arity = self.read();
self.ip += 2;
let val = self.pop(); let called = self.pop();
if crate::DEBUG_SCRIPT_RUN { if self.debug {
println!("=== calling into {val}/{arity} ==="); println!("=== calling into {called}/{arity} ===");
} }
match val { match called {
Value::Fn(_) => { Value::Fn(_) => {
if !val.as_fn().accepts(arity) { if !called.as_fn().accepts(arity) {
return self.panic_with(format!( return self.panic_with(format!(
"wrong number of arguments to {} passing {arity} args", "wrong number of arguments to {} passing {arity} args",
val.show() called.show()
)); ));
} }
let splat_arity = val.as_fn().splat_arity(); let splat_arity = called.as_fn().splat_arity();
if splat_arity > 0 && arity >= splat_arity { if splat_arity > 0 && arity >= splat_arity {
let splatted_args = self.stack.split_off( let splatted_args = self.stack.split_off(
self.stack.len() - (arity - splat_arity) as usize - 1, self.stack.len() - (arity - splat_arity) as usize - 1,
@ -1040,7 +949,7 @@ impl Vm {
arity arity
}; };
let mut frame = CallFrame { let mut frame = CallFrame {
function: val, function: called,
arity, arity,
stack_base: self.stack.len() - arity as usize, stack_base: self.stack.len() - arity as usize,
ip: 0, ip: 0,
@ -1085,7 +994,7 @@ impl Vm {
let mut frame = CallFrame { let mut frame = CallFrame {
function: the_fn, function: the_fn,
arity: args.len() as u8, arity: args.len() as u8,
stack_base: self.stack.len() - arity as usize - 1, stack_base: self.stack.len() - args.len(),
ip: 0, ip: 0,
}; };
@ -1095,11 +1004,13 @@ impl Vm {
self.call_stack.push(frame); self.call_stack.push(frame);
self.ip = 0; self.ip = 0;
} }
_ => return self.panic_with(format!("{} is not a function", val.show())), _ => {
return self.panic_with(format!("{} is not a function", called.show()))
}
} }
} }
Return => { Return => {
if crate::DEBUG_SCRIPT_RUN { if self.debug {
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();
@ -1111,7 +1022,6 @@ impl Vm {
Print => { Print => {
println!("{}", self.pop().show()); println!("{}", self.pop().show());
self.push(Value::Keyword("ok")); self.push(Value::Keyword("ok"));
self.ip += 1;
} }
SetUpvalue => { SetUpvalue => {
let value = self.pop(); let value = self.pop();
@ -1119,20 +1029,15 @@ impl Vm {
panic!("expected function closing over value, got {}", self.peek()); panic!("expected function closing over value, got {}", self.peek());
}; };
lfn.close(value); lfn.close(value);
self.ip += 1;
} }
GetUpvalue => { GetUpvalue => {
let idx = self.chunk().bytecode[self.ip + 1]; let idx = self.read();
self.ip += 2;
if let Value::Fn(ref inner) = self.frame.function { if let Value::Fn(ref inner) = self.frame.function {
self.push(inner.as_ref().upvalue(idx)); self.push(inner.as_ref().upvalue(idx));
} else { } else {
unreachable!(); unreachable!();
} }
} }
Msg => {
self.ip += 2;
}
} }
} }
} }