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! {
"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
((:err, msg)) -> panic! string ("Unwrapped :err! ", msg)
(_) -> panic! "Cannot unwrap something that's not an error tuple."
((:err, msg)) -> panic! "Unwrapped :err! {msg}"
}
fn unwrap_or {
@ -1147,38 +1146,39 @@ fn loadstate! {
fn apply_command {
"Takes a turtle state and a command and calculates a new state."
(state, command) -> match command with {
(:goto, (x, y)) -> assoc (state, :position, (x, y))
(: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) -> {
let #{heading, position, ...} = state
let unit = heading/vector (heading)
let vect = mult (steps, unit)
update (state, :position, add (vect, _))
}
(: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
}
(state, command) -> {
match command with {
(:goto, (x, y)) -> assoc (state, :position, (x, y))
(: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) -> {
let #{heading, position, ...} = state
let unit = heading/vector (heading)
let vect = mult (steps, unit)
update (state, :position, add (vect, _))
}
(: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
}}
}
& position () -> (x, y)
@ -1210,168 +1210,171 @@ fn penwidth {
box state = nil
#{
type
coll?
ordered?
assoc?
nil?
some?
some
eq?
bool?
true?
false?
bool
not
tuple?
fn?
first
rest
inc
dec
count
empty?
any?
list?
list
first
fold
foldr
apply_command
add_command!
abs
abs
add
angle
any?
append
map
filter
keep
concat
contains?
print!
show
report!
doc!
string
string?
join
split
trim
upcase
downcase
assert!
assoc
assoc?
at
atan/2
back!
background!
between?
bg!
bk!
bool
bool?
box?
butlast
ceil
chars
chars/safe
ws?
strip
words
sentence
to_number
box?
unbox
store!
update!
add
sub
mult
clear!
coll?
colors
concat
contains?
cos
count
dec
deg/rad
deg/turn
dict
dict?
dissoc
dist
div
div/0
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/0
inv/safe
abs
neg
zero?
gt?
gte?
join
keep
keys
keyword?
last
left!
list
list?
loadstate!
lt!
lt?
lte?
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
map
mod
mod/0
mod/safe
even?
mult
neg
neg?
nil?
not
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?
err
err?
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/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_or
assert!
colors
turtle_init
turtle_commands
turtle_state
forward!
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
upcase
update
update!
values
words
ws?
zero?
}

View File

@ -494,8 +494,32 @@ Here's a list of things that need doing:
- I need this fixed for optimization reasons.
- I _think_ I just fixed this by fixing tail position tracking in collections
- [ ] test this
- I did not fix it.
* [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
* Other forms in the language need help:
* [ ] 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 {
& & (:goto, (x, y)) -> assoc (state, :position, (x, y))
& & (: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
fn garland! () -> repeat 10 {
flower! ()
fd! (30)
}
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 {
Pop | Store | Stash | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse
| 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
| ConcatStrings | Stringify | MatchType | Return | Match | Print | AppendList
| ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing | PushGlobal
| SetUpvalue => {
| ConcatStrings | Stringify | MatchType | Return | UnconditionalMatch | Print
| AppendList | ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing
| PushGlobal | SetUpvalue => {
println!("{i:04}: {op}")
}
Constant | MatchConstant => {

View File

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

View File

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

View File

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

View File

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