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

View File

@ -494,32 +494,8 @@ 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,19 +1,46 @@
fn circle! () -> repeat 20 {
fd! (2)
rt! (inv (20))
}
let state = #{:position (0, 0), :heading 0, :pencolor :white}
fn flower! () -> repeat 10 {
circle! ()
rt! (inv (10))
}
let command = (:forward, 10)
fn garland! () -> repeat 10 {
flower! ()
fd! (30)
}
& 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
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 | 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
| ConcatStrings | Stringify | MatchType | Return | UnconditionalMatch | Print
| AppendList | ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing
| PushGlobal | SetUpvalue => {
| ConcatStrings | Stringify | MatchType | Return | Match | 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 {
pub struct Compiler<'a> {
pub chunk: Chunk,
pub bindings: Vec<Binding>,
pub scope_depth: isize,
@ -75,7 +75,7 @@ pub struct Compiler {
pub span: SimpleSpan,
pub src: &'static str,
pub name: &'static str,
pub depth: usize,
pub enclosing: Option<&'a Compiler<'a>>,
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 Compiler {
impl<'a> Compiler<'a> {
pub fn new(
ast: &'static Spanned<Ast>,
name: &'static str,
src: &'static str,
depth: usize,
enclosing: Option<&'a Compiler>,
env: imbl::HashMap<&'static str, Value>,
debug: bool,
) -> Compiler {
) -> Compiler<'a> {
let chunk = Chunk {
constants: vec![],
bytecode: vec![],
@ -116,7 +116,6 @@ impl Compiler {
Compiler {
chunk,
bindings: vec![],
depth,
scope_depth: -1,
match_depth: 0,
stack_depth: 0,
@ -125,6 +124,7 @@ impl Compiler {
ast: &ast.0,
span: ast.1,
loop_info: vec![],
enclosing,
upvalues: vec![],
src,
name,
@ -297,18 +297,18 @@ impl Compiler {
self.stack_depth += 1;
return;
}
if self.depth == 0 {
if self.chunk.env.contains_key(name) {
self.msg("as global".to_string());
self.emit_constant(Value::Keyword(name));
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;
}
}
fn duplicate(&mut self) {
self.emit_op(Op::Duplicate);
@ -393,13 +393,11 @@ impl Compiler {
}
fn msg(&mut self, str: String) {
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) {
self.msg(format!("***{label} stack depth: {}", self.stack_depth));
@ -521,12 +519,12 @@ impl Compiler {
self.report_depth("after let binding");
}
WordPattern(name) => {
self.emit_op(Op::UnconditionalMatch);
self.emit_op(Op::Match);
self.bind(name);
}
Word(name) | Splat(name) => self.resolve_binding(name),
PlaceholderPattern => {
self.emit_op(Op::UnconditionalMatch);
self.emit_op(Op::Match);
}
NilPattern => {
self.emit_op(Op::MatchNil);
@ -752,18 +750,18 @@ impl Compiler {
}
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());
}
@ -917,7 +915,7 @@ impl Compiler {
self.resolve_binding(fn_name);
self.emit_op(Op::Partial);
self.emit_byte(arity);
self.stack_depth -= args.len() - 1;
self.stack_depth -= 1;
} else {
match get_builtin(fn_name, args.len()) {
Some(code) => {
@ -1096,7 +1094,7 @@ impl Compiler {
clause,
name,
self.src,
self.depth + 1,
Some(self),
self.chunk.env.clone(),
self.debug,
);
@ -1124,7 +1122,7 @@ impl Compiler {
tup_jump_idxes.push(compiler.stub_jump(Op::JumpIfNoMatch));
}
if pattern.is_empty() {
compiler.emit_op(Op::UnconditionalMatch);
compiler.emit_op(Op::Match);
}
let jump_idx = compiler.stub_jump(Op::Jump);
for idx in tup_jump_idxes {
@ -1197,6 +1195,7 @@ impl Compiler {
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);
@ -1218,10 +1217,6 @@ impl Compiler {
})
.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)
}
@ -1239,10 +1234,8 @@ impl Compiler {
}
FnBody(_) => unreachable!(),
Repeat(times, body) => {
let tail_pos = self.tail_pos;
self.tail_pos = false;
self.visit(times);
self.emit_op(Op::ToInt);
self.emit_op(Op::Truncate);
// skip the decrement the first time
self.emit_op(Op::Jump);
self.emit_byte(0);
@ -1262,7 +1255,6 @@ impl Compiler {
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 = false;
const DEBUG_SCRIPT_RUN: bool = false;
const DEBUG_SCRIPT_COMPILE: bool = true;
const DEBUG_SCRIPT_RUN: bool = true;
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,
0,
None,
HashMap::new(),
DEBUG_PRELUDE_COMPILE,
);
@ -124,7 +124,7 @@ pub fn run(src: &'static str) {
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();
// compiler.emit_constant(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 result = vm.run();
let output = match result {
Ok(val) => val.to_string(),
Ok(val) => val.show(),
Err(panic) => format!("Ludus panicked! {panic}"),
};
if DEBUG_SCRIPT_RUN {
vm.print_stack();
}
println!("{output}");
}

View File

@ -21,7 +21,7 @@ pub enum Op {
Load,
LoadN,
ResetMatch,
UnconditionalMatch,
Match,
MatchNil,
MatchTrue,
MatchFalse,
@ -60,7 +60,7 @@ pub enum Op {
JumpIfZero,
Duplicate,
Decrement,
ToInt,
Truncate,
MatchDepth,
Panic,
EmptyString,
@ -152,7 +152,7 @@ impl std::fmt::Display for Op {
Stash => "stash",
Load => "load",
LoadN => "load_n",
UnconditionalMatch => "match",
Match => "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",
ToInt => "truncate",
Truncate => "truncate",
Duplicate => "duplicate",
MatchDepth => "match_depth",
Panic => "panic",

View File

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