Compare commits
No commits in common. "4f80c500a27c4df4adaffa2620c252fe4cab0a3b" and "24f57c15291f3a58d1110c6d3e9d6dfe3d8387b2" have entirely different histories.
4f80c500a2
...
24f57c1529
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
59
sandbox.ld
59
sandbox.ld
|
@ -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!
|
|
||||||
|
|
11359
sandbox_run.txt
11359
sandbox_run.txt
File diff suppressed because it is too large
Load Diff
|
@ -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 => {
|
||||||
|
|
|
@ -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");
|
||||||
|
|
14
src/main.rs
14
src/main.rs
|
@ -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}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
411
src/vm.rs
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user