Compare commits

..

No commits in common. "4f80c500a27c4df4adaffa2620c252fe4cab0a3b" and "24f57c15291f3a58d1110c6d3e9d6dfe3d8387b2" have entirely different histories.

10 changed files with 11004 additions and 1308 deletions

View File

@ -968,7 +968,8 @@ 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! "Unwrapped :err! {msg}" ((:err, msg)) -> panic! string ("Unwrapped :err! ", msg)
(_) -> panic! "Cannot unwrap something that's not an error tuple."
} }
fn unwrap_or { fn unwrap_or {
@ -1146,39 +1147,38 @@ 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) -> { (state, command) -> match command with {
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) -> { let #{heading, position, ...} = state
let #{heading, position, ...} = state let unit = heading/vector (heading)
let unit = heading/vector (heading) let vect = mult (steps, unit)
let vect = mult (steps, unit) update (state, :position, add (vect, _))
update (state, :position, add (vect, _)) }
} (:back, steps) -> {
(:back, steps) -> { let #{heading, position, ...} = state
let #{heading, position, ...} = state let unit = heading/vector (heading)
let unit = heading/vector (heading) let vect = mult (steps, unit)
let vect = mult (steps, unit) update (state, :position, sub (_, vect))
update (state, :position, sub (_, vect)) }
} (:penup) -> assoc (state, :pendown?, false)
(:penup) -> assoc (state, :pendown?, false) (:pendown) -> assoc (state, :pendown?, true)
(:pendown) -> assoc (state, :pendown?, true) (:penwidth, pixels) -> assoc (state, :penwidth, pixels)
(:penwidth, pixels) -> assoc (state, :penwidth, pixels) (:pencolor, color) -> assoc (state, :pencolor, color)
(:pencolor, color) -> assoc (state, :pencolor, color) (:setheading, heading) -> assoc (state, :heading, heading)
(:setheading, heading) -> assoc (state, :heading, heading) (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor) -> #{position, heading, visible?, pendown?, penwidth, pencolor}
(:loadstate, position, heading, visible?, pendown?, penwidth, pencolor) -> #{position, heading, visible?, pendown?, penwidth, pencolor} (: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,171 +1210,168 @@ fn penwidth {
box state = nil box state = nil
#{ #{
apply_command type
add_command! coll?
ordered?
abs assoc?
abs nil?
add some?
angle some
any? eq?
bool?
true?
false?
bool
not
tuple?
fn?
first
rest
inc
dec
count
empty?
any?
list?
list
first
fold
foldr
append append
assert! map
assoc filter
assoc? keep
at
atan/2
back!
background!
between?
bg!
bk!
bool
bool?
box?
butlast
ceil
chars
chars/safe
clear!
coll?
colors
concat concat
contains? contains?
cos print!
count show
dec report!
deg/rad doc!
deg/turn string
dict string?
dict? join
dissoc split
dist trim
upcase
downcase
chars
chars/safe
ws?
strip
words
sentence
to_number
box?
unbox
store!
update!
add
sub
mult
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
join abs
keep neg
keys zero?
keyword? gt?
last gte?
left!
list
list?
loadstate!
lt!
lt? lt?
lte? lte?
map between?
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
mult even?
neg
neg?
nil?
not
odd? odd?
ok square
ok?
ordered?
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
sqrt/safe sqrt/safe
square dist
state heading/vector
store! floor
string ceil
string? round
strip range
sub at
tan first
tau second
to_number last
trim butlast
true? slice
tuple? keyword?
turn/deg assoc
turn/rad dissoc
turtle_commands update
turtle_init keys
turtle_state values
type get
unbox has?
dict
dict?
each!
random
random_int
ok
ok?
err
err?
unwrap! unwrap!
unwrap_or unwrap_or
upcase assert!
update colors
update! turtle_init
values turtle_commands
words turtle_state
ws? forward!
zero? fd!
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,32 +494,8 @@ 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,19 +1,46 @@
fn circle! () -> repeat 20 { let state = #{:position (0, 0), :heading 0, :pencolor :white}
fd! (2)
rt! (inv (20))
}
fn flower! () -> repeat 10 { let command = (:forward, 10)
circle! ()
rt! (inv (10))
}
fn garland! () -> repeat 10 { & match command with {
flower! () & & (:goto, (x, y)) -> assoc (state, :position, (x, y))
fd! (30) & & (:home) -> do state >
} & & 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 | ToInt | Noop | LoadTuple | LoadList | Eq | Add | Sub | Duplicate | Decrement | Truncate | 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 | UnconditionalMatch | Print | ConcatStrings | Stringify | MatchType | Return | Match | Print | AppendList
| AppendList | ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing | ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing | PushGlobal
| PushGlobal | SetUpvalue => { | 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 { pub struct Compiler<'a> {
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 {
pub span: SimpleSpan, pub span: SimpleSpan,
pub src: &'static str, pub src: &'static str,
pub name: &'static str, pub name: &'static str,
pub depth: usize, pub enclosing: Option<&'a Compiler<'a>>,
pub upvalues: Vec<&'static str>, pub upvalues: Vec<&'static str>,
loop_info: Vec<LoopInfo>, loop_info: Vec<LoopInfo>,
tail_pos: bool, tail_pos: bool,
@ -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 Compiler { impl<'a> Compiler<'a> {
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,
depth: usize, enclosing: Option<&'a Compiler>,
env: imbl::HashMap<&'static str, Value>, env: imbl::HashMap<&'static str, Value>,
debug: bool, debug: bool,
) -> Compiler { ) -> Compiler<'a> {
let chunk = Chunk { let chunk = Chunk {
constants: vec![], constants: vec![],
bytecode: vec![], bytecode: vec![],
@ -116,7 +116,6 @@ impl Compiler {
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,
@ -125,6 +124,7 @@ impl Compiler {
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,17 +297,17 @@ impl Compiler {
self.stack_depth += 1; self.stack_depth += 1;
return; return;
} }
if self.depth == 0 { if self.chunk.env.contains_key(name) {
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);
} else { return;
self.msg(format!("as enclosing upvalue {}", self.upvalues.len()));
self.emit_op(Op::GetUpvalue);
self.emit_byte(self.upvalues.len());
self.upvalues.push(name);
self.stack_depth += 1;
} }
self.msg(format!("as enclosing upvalue {}", self.upvalues.len()));
self.emit_op(Op::GetUpvalue);
self.emit_byte(self.upvalues.len());
self.upvalues.push(name);
self.stack_depth += 1;
} }
fn duplicate(&mut self) { fn duplicate(&mut self) {
@ -393,12 +393,10 @@ impl Compiler {
} }
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) {
@ -521,12 +519,12 @@ impl Compiler {
self.report_depth("after let binding"); self.report_depth("after let binding");
} }
WordPattern(name) => { WordPattern(name) => {
self.emit_op(Op::UnconditionalMatch); self.emit_op(Op::Match);
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::UnconditionalMatch); self.emit_op(Op::Match);
} }
NilPattern => { NilPattern => {
self.emit_op(Op::MatchNil); self.emit_op(Op::MatchNil);
@ -752,18 +750,18 @@ impl Compiler {
} }
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());
} }
@ -917,7 +915,7 @@ impl Compiler {
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 -= args.len() - 1; self.stack_depth -= 1;
} else { } else {
match get_builtin(fn_name, args.len()) { match get_builtin(fn_name, args.len()) {
Some(code) => { Some(code) => {
@ -1096,7 +1094,7 @@ impl Compiler {
clause, clause,
name, name,
self.src, self.src,
self.depth + 1, Some(self),
self.chunk.env.clone(), self.chunk.env.clone(),
self.debug, self.debug,
); );
@ -1124,7 +1122,7 @@ impl Compiler {
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::UnconditionalMatch); compiler.emit_op(Op::Match);
} }
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 {
@ -1197,6 +1195,7 @@ impl Compiler {
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);
@ -1218,10 +1217,6 @@ impl Compiler {
}) })
.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)
} }
@ -1239,10 +1234,8 @@ impl Compiler {
} }
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::ToInt); self.emit_op(Op::Truncate);
// 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);
@ -1262,7 +1255,6 @@ impl Compiler {
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 = false; const DEBUG_SCRIPT_COMPILE: bool = true;
const DEBUG_SCRIPT_RUN: bool = false; const DEBUG_SCRIPT_RUN: bool = true;
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,
0, None,
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, 0, prelude, DEBUG_SCRIPT_COMPILE); let mut compiler = Compiler::new(parsed, "sandbox", src, None, prelude, DEBUG_SCRIPT_COMPILE);
// let base = base::make_base(); // let base = base::make_base();
// compiler.emit_constant(base); // compiler.emit_constant(base);
// compiler.bind("base"); // compiler.bind("base");
@ -146,12 +146,10 @@ 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.to_string(), Ok(val) => val.show(),
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,
UnconditionalMatch, Match,
MatchNil, MatchNil,
MatchTrue, MatchTrue,
MatchFalse, MatchFalse,
@ -60,7 +60,7 @@ pub enum Op {
JumpIfZero, JumpIfZero,
Duplicate, Duplicate,
Decrement, Decrement,
ToInt, Truncate,
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",
UnconditionalMatch => "match", Match => "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",
ToInt => "truncate", Truncate => "truncate",
Duplicate => "duplicate", Duplicate => "duplicate",
MatchDepth => "match_depth", MatchDepth => "match_depth",
Panic => "panic", Panic => "panic",

View File

@ -26,12 +26,8 @@ impl LFn {
match self { match self {
LFn::Declared { .. } => unreachable!(), LFn::Declared { .. } => unreachable!(),
LFn::Defined { closed, .. } => { LFn::Defined { closed, .. } => {
let shown = value.show(); println!("closing over in {}: {}", self.name(), 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,7 +90,6 @@ 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 {
@ -120,7 +119,6 @@ impl Vm {
match_depth: 0, match_depth: 0,
result: None, result: None,
debug, debug,
last_code: 0,
} }
} }
@ -150,18 +148,24 @@ 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.last_code); println!("{:04}: [{inner}] ({register})", self.ip);
} }
fn print_debug(&self) { fn print_debug(&self) {
self.print_stack(); self.print_stack();
let mut ip = self.last_code; let mut ip = self.ip;
self.chunk().dissasemble_instr(&mut ip); self.chunk().dissasemble_instr(&mut ip);
} }
@ -196,104 +200,120 @@ 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 {
if self.at_end() { let Some(byte) = self.chunk().bytecode.get(self.ip) else {
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(code).unwrap(); let op = Op::from_u8(*byte).unwrap();
use Op::*; use Op::*;
match op { match op {
Noop => (), Noop => {
Nil => self.push(Value::Nil), self.ip += 1;
Nothing => self.push(Value::Nothing), }
True => self.push(Value::True), Nil => {
False => self.push(Value::False), self.push(Value::Nil);
Msg => { self.ip += 1;
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 const_idx = self.read2(); let high = self.chunk().bytecode[self.ip + 1];
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 jump_len = self.read2(); let high = self.chunk().bytecode[self.ip + 1];
self.ip += jump_len; let low = self.chunk().bytecode[self.ip + 2];
let jump_len = combine_bytes(high, low);
// let jump_len = self.chunk().bytecode[self.ip + 1];
self.ip += jump_len + 3;
} }
JumpBack => { JumpBack => {
let jump_len = self.read2(); let high = self.chunk().bytecode[self.ip + 1];
self.ip -= jump_len + 3; let low = self.chunk().bytecode[self.ip + 2];
let jump_len = combine_bytes(high, low);
// let jump_len = self.chunk().bytecode[self.ip + 1];
self.ip -= jump_len;
} }
JumpIfFalse => { JumpIfFalse => {
let jump_len = self.read2(); let high = self.chunk().bytecode[self.ip + 1];
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 => self.ip += jump_len, Value::Nil | Value::False => {
_ => (), self.ip += jump_len + 3;
}
_ => {
self.ip += 3;
}
} }
} }
JumpIfTrue => { JumpIfTrue => {
let jump_len = self.read2(); let high = self.chunk().bytecode[self.ip + 1];
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 += 3;
}
_ => {
self.ip += jump_len + 3;
}
} }
} }
JumpIfZero => { JumpIfZero => {
let jump_len = self.read2(); let high = self.chunk().bytecode[self.ip + 1];
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 => self.ip += jump_len, Value::Number(x) if x <= 0.0 => {
Value::Number(..) => (), self.ip += jump_len + 3;
}
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.read() as usize; let n = self.chunk().bytecode[self.ip + 1] as usize;
self.stack.truncate(self.stack.len() - n); self.stack.truncate(self.stack.len() - n);
self.ip += 2;
} }
PushBinding => { PushBinding => {
let idx = self.read(); let binding_idx =
let value = self.get_value_at(idx); self.chunk().bytecode[self.ip + 1] as usize + self.frame.stack_base;
self.push(value); let binding_value = self.stack[binding_idx].clone();
self.push(binding_value);
self.ip += 2;
} }
PushGlobal => { PushGlobal => {
let key = self.pop(); let key = self.pop();
@ -302,74 +322,98 @@ 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.read() as usize; let n = self.chunk().bytecode[self.ip + 1] 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.read() as usize; let n = self.chunk().bytecode[self.ip + 1] 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;
} }
UnconditionalMatch => { Match => {
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 value = self.get_scrutinee(); let idx = self.stack.len() - self.match_depth as usize - 1;
let val_type = value.type_of(); let val_type = self.stack[idx].type_of();
self.matches = val_type == as_type; self.matches = val_type == as_type;
self.ip += 1;
} }
MatchNil => { MatchNil => {
let value = self.get_scrutinee(); let idx = self.stack.len() - self.match_depth as usize - 1;
self.matches = value == Value::Nil; if self.stack[idx] == Value::Nil {
self.matches = true;
};
self.ip += 1;
} }
MatchTrue => { MatchTrue => {
let value = self.get_scrutinee(); let idx = self.stack.len() - self.match_depth as usize - 1;
self.matches = value == Value::True; if self.stack[idx] == Value::True {
self.matches = true;
};
self.ip += 1;
} }
MatchFalse => { MatchFalse => {
let value = self.get_scrutinee(); let idx = self.stack.len() - self.match_depth as usize - 1;
self.matches = value == Value::False; if self.stack[idx] == 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 const_idx = self.read2(); let high = self.chunk().bytecode[self.ip + 1];
let scrutinee = self.get_scrutinee(); let low = self.chunk().bytecode[self.ip + 2];
// let idx = self.stack.len() - self.match_depth as usize - 1; let const_idx = combine_bytes(high, low);
self.matches = scrutinee == self.chunk().constants[const_idx]; let idx = self.stack.len() - self.match_depth as usize - 1;
self.matches = self.stack[idx] == self.chunk().constants[const_idx];
self.ip += 3;
} }
MatchString => { MatchString => {
let pattern_idx = self.read(); let pattern_idx = self.chunk().bytecode[self.ip + 1];
let scrutinee = self.get_scrutinee(); self.ip += 2;
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
@ -381,12 +425,13 @@ impl Vm {
}; };
} }
PushStringMatches => { PushStringMatches => {
let pattern_idx = self.read(); let pattern_idx = self.chunk().bytecode[self.ip + 1];
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.get_scrutinee(); let scrutinee = self.stack[scrutinee_idx].clone();
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(),
@ -402,32 +447,35 @@ 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.read() as usize; let tuple_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() == tuple_len, Value::Tuple(members) => self.matches = members.len() == tuple_len as usize,
_ => self.matches = false, _ => self.matches = false,
}; };
self.ip += 2;
} }
MatchSplattedTuple => { MatchSplattedTuple => {
let patt_len = self.read() as usize; let idx = self.stack.len() - self.match_depth as usize - 1;
let scrutinee = self.get_scrutinee(); let patt_len = self.chunk().bytecode[self.ip + 1];
let scrutinee = self.stack[idx].clone();
match scrutinee { match scrutinee {
Value::Tuple(members) => self.matches = members.len() >= patt_len, Value::Tuple(members) => self.matches = members.len() >= patt_len as usize,
_ => self.matches = false, _ => self.matches = false,
} }
self.ip += 2;
} }
PushTuple => { PushTuple => {
let tuple_len = self.read() as usize; let tuple_len = self.chunk().bytecode[self.ip + 1];
let tuple_members = self.stack.split_off(self.stack.len() - tuple_len); let tuple_members = self.stack.split_off(self.stack.len() - tuple_len as usize);
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() {
@ -436,10 +484,12 @@ 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.read() as usize; let load_len = self.chunk().bytecode[self.ip + 1] as usize;
let tuple = self.get_scrutinee(); let idx = self.stack.len() - self.match_depth as usize - 1;
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");
}; };
@ -451,9 +501,11 @@ 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();
@ -463,6 +515,7 @@ 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();
@ -475,28 +528,31 @@ 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.read() as usize; let list_len = self.chunk().bytecode[self.ip + 1];
let scrutinee = self.get_scrutinee(); let scrutinee = self.stack[idx].clone();
match scrutinee { match scrutinee {
Value::List(members) => self.matches = members.len() == list_len, Value::List(members) => self.matches = members.len() == list_len as usize,
_ => 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.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::List(members) => self.matches = members.len() >= patt_len, Value::List(members) => self.matches = members.len() >= patt_len as usize,
_ => 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.get_scrutinee(); let list = self.stack[idx].clone();
match list { match list {
Value::List(members) => { Value::List(members) => {
for member in members.iter() { for member in members.iter() {
@ -505,10 +561,12 @@ 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.read() as usize; let loaded_len = self.chunk().bytecode[self.ip + 1] as usize;
let list = self.get_scrutinee(); let idx = self.stack.len() - self.match_depth as usize - 1;
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");
}; };
@ -517,9 +575,11 @@ 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();
@ -531,6 +591,7 @@ 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 {
@ -541,42 +602,39 @@ 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.read(); let dict_idx = self.chunk().bytecode[self.ip + 1] as usize;
let dict = match self.get_value_at(dict_idx) { let Value::Dict(dict) = self.stack[dict_idx].clone() else {
Value::Dict(dict) => dict, unreachable!("expected dict, got something else")
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.read(); let dict_len = self.chunk().bytecode[self.ip + 1];
let scrutinee = self.get_scrutinee(); let scrutinee = self.stack[idx].clone();
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.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::Dict(members) => self.matches = members.len() >= patt_len, Value::Dict(members) => self.matches = members.len() >= patt_len as usize,
_ => 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 {
@ -587,10 +645,12 @@ 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();
@ -603,29 +663,40 @@ impl Vm {
_ => Value::Nil, _ => Value::Nil,
}; };
self.push(value); self.push(value);
self.ip += 1;
} }
JumpIfNoMatch => { JumpIfNoMatch => {
let jump_len = self.read2(); let high = self.chunk().bytecode[self.ip + 1];
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 self.ip += jump_len + 3;
} else {
self.ip += 3;
} }
} }
JumpIfMatch => { JumpIfMatch => {
let jump_len = self.read2(); let high = self.chunk().bytecode[self.ip + 1];
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; self.ip += jump_len + 3;
} 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;
} }
ToInt => { Truncate => {
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");
} }
@ -634,15 +705,18 @@ 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.read(); self.match_depth = self.chunk().bytecode[self.ip + 1];
self.ip += 2;
} }
PanicNoWhen | PanicNoMatch => { PanicNoWhen | PanicNoMatch => {
return self.panic("no match"); return self.panic("no match");
@ -655,6 +729,7 @@ 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();
@ -664,6 +739,7 @@ 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();
@ -673,6 +749,7 @@ 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();
@ -682,6 +759,7 @@ 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();
@ -694,6 +772,7 @@ 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();
@ -703,6 +782,7 @@ 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();
@ -713,12 +793,14 @@ 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();
@ -731,6 +813,7 @@ 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();
@ -746,6 +829,7 @@ 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();
@ -754,6 +838,7 @@ 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();
@ -761,6 +846,7 @@ 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 => {
@ -775,15 +861,18 @@ 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.read(); let arity = self.chunk().bytecode[self.ip + 1];
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");
@ -797,23 +886,24 @@ impl Vm {
self.push(Value::Partial(Rc::new(partial))); self.push(Value::Partial(Rc::new(partial)));
} }
TailCall => { TailCall => {
let arity = self.read(); let arity = self.chunk().bytecode[self.ip + 1];
self.ip += 2;
let called = self.pop(); let val = self.pop();
if self.debug { if self.debug {
println!( println!(
"=== tail call into {called}/{arity} from {} ===", "=== tail call into {val}/{arity} from {} ===",
self.frame.function.as_fn().name() self.frame.function.as_fn().name()
); );
} }
match called { match val {
Value::Fn(_) => { Value::Fn(_) => {
if !called.as_fn().accepts(arity) { if !val.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",
called.show() val.show()
)); ));
} }
// first put the arguments in the register // first put the arguments in the register
@ -833,7 +923,7 @@ impl Vm {
i += 1; i += 1;
} }
let splat_arity = called.as_fn().splat_arity(); let splat_arity = val.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,
@ -848,7 +938,7 @@ impl Vm {
}; };
let mut frame = CallFrame { let mut frame = CallFrame {
function: called, function: val,
arity, arity,
stack_base: self.stack.len() - arity as usize, stack_base: self.stack.len() - arity as usize,
ip: 0, ip: 0,
@ -858,6 +948,8 @@ 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) {
@ -903,7 +995,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() - args.len(), stack_base: self.stack.len() - arity as usize - 1,
ip: 0, ip: 0,
}; };
@ -913,29 +1005,28 @@ 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.read(); let arity = self.chunk().bytecode[self.ip + 1];
self.ip += 2;
let called = self.pop(); let val = self.pop();
if self.debug { if crate::DEBUG_SCRIPT_RUN {
println!("=== calling into {called}/{arity} ==="); println!("=== calling into {val}/{arity} ===");
} }
match called { match val {
Value::Fn(_) => { Value::Fn(_) => {
if !called.as_fn().accepts(arity) { if !val.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",
called.show() val.show()
)); ));
} }
let splat_arity = called.as_fn().splat_arity(); let splat_arity = val.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,
@ -949,7 +1040,7 @@ impl Vm {
arity arity
}; };
let mut frame = CallFrame { let mut frame = CallFrame {
function: called, function: val,
arity, arity,
stack_base: self.stack.len() - arity as usize, stack_base: self.stack.len() - arity as usize,
ip: 0, ip: 0,
@ -994,7 +1085,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() - args.len(), stack_base: self.stack.len() - arity as usize - 1,
ip: 0, ip: 0,
}; };
@ -1004,13 +1095,11 @@ 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 self.debug { if crate::DEBUG_SCRIPT_RUN {
println!("== returning from {} ==", self.frame.function.show()) println!("== returning from {} ==", self.frame.function.show())
} }
self.frame = self.call_stack.pop().unwrap(); self.frame = self.call_stack.pop().unwrap();
@ -1022,6 +1111,7 @@ 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();
@ -1029,15 +1119,20 @@ 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.read(); let idx = self.chunk().bytecode[self.ip + 1];
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;
}
} }
} }
} }