oh god, so many changes. working on tuple matching

This commit is contained in:
Scott Richmond 2025-05-23 00:09:35 -04:00
parent 6c803cdf5a
commit 4a4b2b22ed
4 changed files with 281 additions and 56 deletions

View File

@ -237,3 +237,19 @@ In particular, the issue is that if, as I have been planning, a function closes
One way to handle this, I think is using `std::cell::OnceCell`. Rather than a `RefCell`, `OnceCell` has no runtime overhead. Instead, what happens is you effectively put a `None` in the cell. Then, once you have the value you want to put in there, you call `set` on the `OnceCell`, and it does what it needs to.
This allows for the closures to be closed over right after compilation.
### 2024-12-27
Working on `loop` and `recur`, rather than `fn`--this is the gentler slope.
And discovering that we actually need a `[Value; 15]` return register.
`recur` needs to push all the arguments to the stack, then yank them off into the return register, then pop back to the loop root, then push all the things back onto the stack, then jump to the beginning of the loop.
And that also means I need a different `Value` variant that's a true `Nothing`, not even `nil`, which will _never_ end up anywhere other than a placeholder value in the register and on the stack.
So, next steps:
1. Add `Value::Nothing` and fix all the compiler errors
2. Make the return register `[Value; 15]`, populated with `Value::Nothing`s at initialization.
3. Update `load` and `store` to work with the array rather than a single value.
4. Create `store_n` and `load_n` to work with multiple values.
5. Create a `Vm.arity` method that computes how many non-nothings were passed into the register.
6. Then, implement `recur`
7. And, then, fix up jump indexes in `loop`
8. Fix all the off-by-one errors in the jumps

View File

@ -1,4 +1,5 @@
use crate::parser::Ast;
use crate::pattern::Pattern;
use crate::spans::Spanned;
use crate::value::*;
use chumsky::prelude::SimpleSpan;
@ -9,6 +10,7 @@ use std::rc::Rc;
#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
pub enum Op {
Noop,
Nil,
True,
False,
@ -16,6 +18,7 @@ pub enum Op {
Jump,
JumpIfFalse,
Pop,
PopN,
PushBinding,
Store,
Load,
@ -28,12 +31,14 @@ pub enum Op {
MatchConstant,
MatchTuple,
PushTuple,
LoadTuple,
PushList,
PushDict,
PushBox,
GetKey,
PanicNoWhen,
JumpIfNoMatch,
JumpIfMatch,
PanicNoMatch,
TypeOf,
JumpBack,
@ -48,6 +53,7 @@ impl std::fmt::Display for Op {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use Op::*;
let rep = match self {
Noop => "noop",
Nil => "nil",
True => "true",
False => "false",
@ -55,6 +61,7 @@ impl std::fmt::Display for Op {
Jump => "jump",
JumpIfFalse => "jump_if_false",
Pop => "pop",
PopN => "pop_n",
PushBinding => "push_binding",
Store => "store",
Load => "load",
@ -67,12 +74,14 @@ impl std::fmt::Display for Op {
MatchConstant => "match_constant",
MatchTuple => "match_tuple",
PushTuple => "push_tuple",
LoadTuple => "load_tuple",
PushList => "push_list",
PushDict => "push_dict",
PushBox => "push_box",
GetKey => "get_key",
PanicNoWhen => "panic_no_when",
JumpIfNoMatch => "jump_if_no_match",
JumpIfMatch => "jump_if_match",
PanicNoMatch => "panic_no_match",
TypeOf => "type_of",
JumpBack => "jump_back",
@ -90,6 +99,7 @@ impl std::fmt::Display for Op {
pub struct Binding {
name: &'static str,
depth: isize,
stack_pos: usize,
}
#[derive(Clone, Debug, PartialEq)]
@ -107,7 +117,7 @@ impl Chunk {
match op {
Pop | Store | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse
| PanicIfNoMatch | MatchWord | ResetMatch | GetKey | PanicNoWhen | PanicNoMatch
| TypeOf | Duplicate | Decrement | Truncate => {
| TypeOf | Duplicate | Decrement | Truncate | Noop | LoadTuple => {
println!("{i:04}: {op}")
}
Constant | MatchConstant => {
@ -116,7 +126,8 @@ impl Chunk {
println!("{i:04}: {:16} {next:04}: {value}", op.to_string());
}
PushBinding | MatchTuple | PushTuple | PushDict | PushList | PushBox | Jump
| JumpIfFalse | JumpIfNoMatch | JumpBack | JumpIfZero | MatchDepth => {
| JumpIfFalse | JumpIfNoMatch | JumpIfMatch | JumpBack | JumpIfZero | MatchDepth
| PopN => {
let next = self.bytecode[i + 1];
println!("{i:04}: {:16} {next:04}", op.to_string());
}
@ -132,18 +143,32 @@ impl Chunk {
}
}
#[derive(Debug, Clone, PartialEq)]
struct LoopInfo {
start: usize,
stack_root: usize,
}
impl LoopInfo {
fn new(start: usize, stack_root: usize) -> LoopInfo {
LoopInfo { start, stack_root }
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Compiler {
pub chunk: Chunk,
pub bindings: Vec<Binding>,
scope_depth: isize,
num_bindings: usize,
match_depth: usize,
stack_depth: usize,
pub spans: Vec<SimpleSpan>,
pub nodes: Vec<&'static Ast>,
pub ast: &'static Ast,
pub span: SimpleSpan,
pub src: &'static str,
pub name: &'static str,
loop_idxes: Vec<usize>,
loop_info: Vec<LoopInfo>,
}
fn is_binding(expr: &Spanned<Ast>) -> bool {
@ -170,12 +195,13 @@ impl Compiler {
chunk,
bindings: vec![],
scope_depth: -1,
num_bindings: 0,
match_depth: 0,
stack_depth: 0,
spans: vec![],
nodes: vec![],
ast: &ast.0,
span: ast.1,
loop_idxes: vec![],
loop_info: vec![],
src,
name,
}
@ -213,6 +239,7 @@ impl Compiler {
self.spans.push(self.span);
self.chunk.bytecode.push(constant_index as u8);
self.spans.push(self.span);
self.stack_depth += 1;
}
fn match_constant(&mut self, val: Value) {
@ -233,7 +260,7 @@ impl Compiler {
self.spans.push(self.span);
self.chunk.bytecode.push(constant_index as u8);
self.spans.push(self.span);
self.bind("");
// self.bind("");
}
fn emit_op(&mut self, op: Op) {
@ -251,31 +278,61 @@ impl Compiler {
}
fn bind(&mut self, name: &'static str) {
self.bindings.push(Binding {
let binding = Binding {
name,
depth: self.scope_depth,
});
stack_pos: self.stack_depth - self.match_depth - 1,
};
println!("{:?}", binding);
println!("stack: {}; match: {}", self.stack_depth, self.match_depth);
self.bindings.push(binding);
}
fn pop(&mut self) {
self.emit_op(Op::Pop);
self.stack_depth -= 1;
}
fn pop_n(&mut self, n: usize) {
self.emit_op(Op::PopN);
self.emit_byte(n);
self.stack_depth -= n;
}
fn enter_loop(&mut self) {
self.loop_idxes.push(self.len());
self.loop_info
.push(LoopInfo::new(self.len(), self.bindings.len()));
}
fn leave_loop(&mut self) {
self.loop_idxes.pop();
self.loop_info.pop();
}
fn loop_info(&mut self) -> LoopInfo {
self.loop_info.last().unwrap().clone()
}
fn loop_idx(&mut self) -> usize {
*self.loop_idxes.last().unwrap()
self.loop_info.last().unwrap().start
}
fn loop_root(&mut self) -> usize {
self.loop_info.last().unwrap().stack_root
}
pub fn compile(&mut self) {
use Ast::*;
match self.ast {
Error => unreachable!(),
Nil => self.emit_op(Op::Nil),
Nil => {
self.emit_op(Op::Nil);
self.stack_depth += 1;
}
Number(n) => self.emit_constant(Value::Number(*n)),
Boolean(b) => self.emit_op(if *b { Op::True } else { Op::False }),
Boolean(b) => {
self.emit_op(if *b { Op::True } else { Op::False });
self.stack_depth += 1;
}
String(s) => {
let existing_str = self.chunk.strings.iter().position(|e| e == s);
let str_index = match existing_str {
@ -303,27 +360,32 @@ impl Compiler {
self.visit(expr);
} else {
self.visit(expr);
self.emit_op(Op::Pop);
self.pop();
// self.emit_op(Op::Pop);
}
}
let last_expr = lines.last().unwrap();
if is_binding(last_expr) {
self.visit(last_expr);
self.emit_op(Op::Duplicate);
self.stack_depth += 1;
} else {
self.visit(last_expr);
self.stack_depth += 1;
}
self.emit_op(Op::Store);
self.scope_depth -= 1;
while let Some(binding) = self.bindings.last() {
if binding.depth > self.scope_depth {
self.emit_op(Op::Pop);
self.pop();
// self.emit_op(Op::Pop);
self.bindings.pop();
} else {
break;
}
}
self.emit_op(Op::Pop);
self.pop();
// self.emit_op(Op::Pop);
self.emit_op(Op::Load);
}
If(cond, then, r#else) => {
@ -331,11 +393,13 @@ impl Compiler {
let jif_idx = self.len();
self.emit_op(Op::JumpIfFalse);
self.emit_byte(0xff);
self.stack_depth -= 1;
self.visit(then);
let jump_idx = self.len();
self.emit_op(Op::Jump);
self.emit_byte(0xff);
self.visit(r#else);
self.stack_depth -= 1;
let end_idx = self.len();
let jif_offset = jump_idx - jif_idx;
let jump_offset = end_idx - jump_idx - 2;
@ -343,6 +407,7 @@ impl Compiler {
self.chunk.bytecode[jump_idx + 1] = jump_offset as u8;
}
Let(patt, expr) => {
self.match_depth = 0;
self.emit_op(Op::ResetMatch);
self.visit(expr);
self.visit(patt);
@ -354,29 +419,30 @@ impl Compiler {
}
Word(name) => {
self.emit_op(Op::PushBinding);
let biter = self.bindings.iter().enumerate().rev();
for (i, binding) in biter {
self.stack_depth += 1;
let biter = self.bindings.iter().rev();
for binding in biter {
if binding.name == *name {
self.emit_byte(i);
self.emit_byte(binding.stack_pos);
break;
}
}
}
PlaceholderPattern => {
self.emit_op(Op::MatchWord);
self.bind("");
// self.bind("");
}
NilPattern => {
self.emit_op(Op::MatchNil);
self.bind("");
// self.bind("");
}
BooleanPattern(b) => {
if *b {
self.emit_op(Op::MatchTrue);
self.bind("");
// self.bind("");
} else {
self.emit_op(Op::MatchFalse);
self.bind("");
// self.bind("");
}
}
NumberPattern(n) => {
@ -404,12 +470,72 @@ impl Compiler {
}
self.match_constant(Value::Interned(str_index));
}
// TODO: finish this work
// What's going on:
// Currently, bindings are made in lockstep with the stack.
// And the index of the binding in the bindings array in the compiler gets converted to a u8 as the index into the stack
// I suspect this will have to change when we get stack frames
// But what's happening here is that nested tuple bindings are out of order
// When a tuple gets unfolded at the end of the stack, the binding that matches is now not where you'd expect
// The whole "match depth" construct, while working, is what allows for out-of-order matching/binding
// So either:
// - Bindings need to work differently, where there's some way of representing them that's not just the index of where the name is
// - Or we need a way of working with nested tuples that ensure bindings continue to be orederly
// My sense is that the former is the correct approach.
// It introduces some complexity: a binding will have to be a `(name, offset)` tuple or some such
// Working with nested tuples could perhaps be solved by representing tuples on the stack, but then you're going to run into similar problems with list and dict patterns
// And so, the thing to, I think, is to get clever with an offset
// But to do that, probably the compiler needs to model the stack? Ugh.
// SO, THEN:
// [x] 1. we need a stack counter, that increases any time anything gets pushed to the stack, and decreases any time anything gets popped
// [x] 2. We need to change the representation of bindings to be a tuple (name, index), where index is `stack size - match depth`
// [x] 3. This means we get to remove the "silent" bindings where all patterns add a `""` binding
// [x] 4. Question: given that we need both of these things, should I model this as methods rather than explicit `emit_op` calls? Probably.
// Currently: there's still an off by one error with the current test code with nested tuples, but I can prolly fix this?
TuplePattern(members) => {
self.emit_op(Op::MatchTuple);
self.emit_byte(members.len());
self.emit_op(Op::JumpIfNoMatch);
let before_load_tup_idx = self.len();
self.emit_byte(0xff);
let mut jump_idxes = vec![];
self.match_depth += members.len();
self.emit_op(Op::LoadTuple);
self.stack_depth += members.len();
for member in members {
self.match_depth -= 1;
self.emit_op(Op::MatchDepth);
self.emit_byte(self.match_depth);
self.visit(member);
self.emit_op(Op::JumpIfNoMatch);
jump_idxes.push(self.len());
self.emit_byte(0xff);
}
self.emit_op(Op::Jump);
let jump_idx = self.len();
self.emit_byte(0xff);
for idx in jump_idxes {
self.chunk.bytecode[idx] = self.len() as u8 - idx as u8 - 1;
}
for _ in 0..members.len() {
// self.pop();
// this only runs if there's no match
// so don't change the representation of the stack
// contingencies will be handled by the binding forms
// thus: emit Op::Pop directly
self.emit_op(Op::Pop);
}
self.chunk.bytecode[before_load_tup_idx] =
self.len() as u8 - before_load_tup_idx as u8 - 1;
self.chunk.bytecode[jump_idx] = self.len() as u8 - jump_idx as u8 - 1;
}
Tuple(members) => {
for member in members {
self.visit(member);
}
self.emit_op(Op::PushTuple);
self.emit_byte(members.len());
self.stack_depth = self.stack_depth + 1 - members.len();
}
List(members) => {
for member in members {
@ -417,10 +543,12 @@ impl Compiler {
}
self.emit_op(Op::PushList);
self.emit_byte(members.len());
self.stack_depth = self.stack_depth + 1 - members.len();
}
LBox(name, expr) => {
self.visit(expr);
self.emit_op(Op::PushBox);
self.stack_depth += 1;
self.bind(name);
}
Dict(pairs) => {
@ -429,6 +557,7 @@ impl Compiler {
}
self.emit_op(Op::PushDict);
self.emit_byte(pairs.len());
self.stack_depth = self.stack_depth + 1 - pairs.len();
}
Pair(key, value) => {
let existing_kw = self.chunk.keywords.iter().position(|kw| kw == key);
@ -464,9 +593,14 @@ impl Compiler {
todo!()
}
}
// TODO: Keep track of the stack in
// WHEN and MATCH:
// each needs to just hold onto the stack depth
// before each clause, and reset it after each
When(clauses) => {
let mut jump_idxes = vec![];
let mut clauses = clauses.iter();
let stack_depth = self.stack_depth;
while let Some((WhenClause(cond, body), _)) = clauses.next() {
self.visit(cond.as_ref());
self.emit_op(Op::JumpIfFalse);
@ -477,7 +611,9 @@ impl Compiler {
jump_idxes.push(self.len());
self.emit_byte(0xff);
self.chunk.bytecode[jif_jump_idx] = self.len() as u8 - jif_jump_idx as u8 - 1;
self.stack_depth = stack_depth;
}
self.stack_depth += 1;
self.emit_op(Op::PanicNoWhen);
for idx in jump_idxes {
self.chunk.bytecode[idx] = self.len() as u8 - idx as u8 + 1;
@ -486,10 +622,12 @@ impl Compiler {
WhenClause(..) => unreachable!(),
Match(scrutinee, clauses) => {
self.visit(scrutinee.as_ref());
let stack_depth = self.stack_depth;
let mut jump_idxes = vec![];
let mut clauses = clauses.iter();
while let Some((MatchClause(pattern, guard, body), _)) = clauses.next() {
self.scope_depth += 1;
self.match_depth = 0;
self.visit(pattern);
self.emit_op(Op::JumpIfNoMatch);
let jnm_jump_idx = self.len();
@ -507,7 +645,8 @@ impl Compiler {
self.scope_depth -= 1;
while let Some(binding) = self.bindings.last() {
if binding.depth > self.scope_depth {
self.emit_op(Op::Pop);
self.pop();
// self.emit_op(Op::Pop);
self.bindings.pop();
} else {
break;
@ -526,7 +665,8 @@ impl Compiler {
self.scope_depth -= 1;
while let Some(binding) = self.bindings.last() {
if binding.depth > self.scope_depth {
self.emit_op(Op::Pop);
self.pop();
// self.emit_op(Op::Pop);
self.bindings.pop();
} else {
break;
@ -539,12 +679,16 @@ impl Compiler {
self.len() as u8 - jnm_jump_idx as u8 - 1;
}
}
self.stack_depth = stack_depth;
}
self.emit_op(Op::PanicNoMatch);
self.emit_op(Op::Load);
for idx in jump_idxes {
self.chunk.bytecode[idx] = self.len() as u8 - idx as u8;
self.chunk.bytecode[idx] = self.len() as u8 - idx as u8 - 1;
}
self.pop();
// self.emit_op(Op::Pop);
self.emit_op(Op::Load);
self.stack_depth += 1;
}
MatchClause(..) => unreachable!(),
Fn(name, body, doc) => {
@ -580,7 +724,9 @@ impl Compiler {
}
FnBody(clauses) => {
self.emit_op(Op::ResetMatch);
todo!();
}
// TODO: add stack-tracking to this
Repeat(times, body) => {
self.visit(times);
self.emit_op(Op::Truncate);
@ -591,23 +737,27 @@ impl Compiler {
self.emit_op(Op::Decrement);
let repeat_begin = self.len();
self.emit_op(Op::Duplicate);
self.stack_depth += 1;
self.emit_op(Op::JumpIfZero);
self.emit_byte(0xff);
// compile the body
self.visit(body);
// pop whatever value the body returns
self.emit_op(Op::Pop);
self.pop();
// self.emit_op(Op::Pop);
self.emit_op(Op::JumpBack);
// set jump points
let repeat_end = self.len();
self.emit_byte(repeat_end - repeat_begin);
self.chunk.bytecode[repeat_begin + 2] = (repeat_end - repeat_begin - 2) as u8;
// pop the counter
self.emit_op(Op::Pop);
self.pop();
// self.emit_op(Op::Pop);
// and emit nil
self.emit_constant(Value::Nil);
}
Loop(value, clauses) => {
todo!();
//algo:
//first, put the values on the stack
let (Ast::Tuple(members), _) = value.as_ref() else {
@ -622,27 +772,43 @@ impl Compiler {
self.emit_op(Op::ResetMatch);
//next, compile each clause:
let mut clauses = clauses.iter();
let mut jump_idxes = vec![];
while let Some((Ast::MatchClause(pattern, _, body), _)) = clauses.next() {
self.scope_depth += 1;
let (Ast::TuplePattern(members), _) = pattern.as_ref() else {
unreachable!()
};
// TODO: finish compiling match clauses
// I just added "match depth" to the VM
// this will set match depth to artiy
// and decrement it each pattern
// the compiler will need to know about match depth for binding to work
// we should match against ALL args first
// rather than jump_no_matching after every arg check
// compile the body
// and then jump_no_match to the next clause
// at the end, panic_no_match
let mut match_depth = arity;
let mut members = members.iter();
while match_depth > 0 {
self.emit_op(Op::MatchDepth);
self.emit_byte(match_depth - 1);
self.visit(members.next().unwrap());
match_depth -= 1;
}
self.emit_op(Op::JumpIfNoMatch);
let jnm_idx = self.len();
self.emit_byte(0xff);
self.visit(body);
self.emit_op(Op::Jump);
self.emit_byte(0xff);
jump_idxes.push(self.len());
self.chunk.bytecode[jnm_idx] = self.len() as u8 - jnm_idx as u8;
self.scope_depth -= 1;
}
//match against the values on the stack
//we know the (fixed) arity, so we should know where to look
//compile the clauses exactly as in `match`
self.emit_op(Op::PanicNoMatch);
// TODO: fix up jump indexes a la match
self.leave_loop();
}
Recur(_) => {
// algo
// visit each member of the arguments
// then store those in the return register
// then pop back to loop stack root
// then jump to loop start
}
Recur(args) => {}
Interpolated(..)
| Arguments(..)
| Placeholder
@ -652,7 +818,6 @@ impl Compiler {
| InterpolatedPattern(..)
| AsPattern(..)
| Splattern(..)
| TuplePattern(..)
| ListPattern(..)
| PairPattern(..)
| DictPattern(..) => todo!(),
@ -667,9 +832,9 @@ impl Compiler {
let op = Op::from_u8(*byte).unwrap();
use Op::*;
match op {
Pop | Store | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse
| MatchWord | ResetMatch | PanicIfNoMatch | GetKey | PanicNoWhen | PanicNoMatch
| TypeOf | Duplicate | Truncate | Decrement => {
Noop | Pop | Store | Load | Nil | True | False | MatchNil | MatchTrue
| MatchFalse | MatchWord | ResetMatch | PanicIfNoMatch | GetKey | PanicNoWhen
| PanicNoMatch | TypeOf | Duplicate | Truncate | Decrement | LoadTuple => {
println!("{i:04}: {op}")
}
Constant | MatchConstant => {
@ -678,7 +843,8 @@ impl Compiler {
println!("{i:04}: {:16} {next:04}: {value}", op.to_string());
}
PushBinding | MatchTuple | PushTuple | PushDict | PushList | PushBox | Jump
| JumpIfFalse | JumpIfNoMatch | JumpBack | JumpIfZero | MatchDepth => {
| JumpIfFalse | JumpIfNoMatch | JumpIfMatch | JumpBack | JumpIfZero
| MatchDepth | PopN => {
let (_, next) = codes.next().unwrap();
println!("{i:04}: {:16} {next:04}", op.to_string());
}

View File

@ -1,4 +1,5 @@
use chumsky::{input::Stream, prelude::*};
use std::env;
const DEBUG_COMPILE: bool = true;
const DEBUG_RUN: bool = true;
@ -19,6 +20,7 @@ mod validator;
mod compiler;
use crate::compiler::Compiler;
mod pattern;
mod value;
mod vm;
@ -49,6 +51,8 @@ pub fn run(src: &'static str) {
let mut compiler = Compiler::new(parsed, "test", src);
compiler.compile();
if DEBUG_COMPILE {
println!("=== source code ===");
println!("{src}");
compiler.disassemble();
println!("\n\n")
}
@ -67,10 +71,13 @@ pub fn run(src: &'static str) {
}
pub fn main() {
env::set_var("RUST_BACKTRACE", "1");
let src = "
match :foo with {
:bar -> :no
:foo -> :yes
match (-1, (2)) with {
(0) -> :nope
(1) -> :yup
(x, (1)) -> :thing
(x, (y)) -> y
}
";
run(src);

View File

@ -102,6 +102,9 @@ impl<'a> Vm<'a> {
let op = Op::from_u8(*byte).unwrap();
use Op::*;
match op {
Noop => {
self.ip += 1;
}
Nil => {
self.push(Value::Nil);
self.ip += 1;
@ -159,6 +162,11 @@ impl<'a> Vm<'a> {
self.pop();
self.ip += 1;
}
PopN => {
let n = self.chunk.bytecode[self.ip + 1] as usize;
self.stack.truncate(self.stack.len() - n);
self.ip += 2;
}
PushBinding => {
let binding_idx = self.chunk.bytecode[self.ip + 1] as usize;
let binding_value = self.stack[binding_idx].clone();
@ -220,6 +228,16 @@ impl<'a> Vm<'a> {
self.matches = self.stack[idx] == self.chunk.constants[const_idx as usize];
self.ip += 2;
}
MatchTuple => {
let idx = self.stack.len() - self.match_depth as usize - 1;
let tuple_len = self.chunk.bytecode[self.ip + 1];
let scrutinee = self.stack[idx].clone();
match scrutinee {
Value::Tuple(members) => self.matches = members.len() == tuple_len as usize,
_ => self.matches = false,
};
self.ip += 2;
}
PushTuple => {
let tuple_len = self.chunk.bytecode[self.ip + 1];
let tuple_members = self.stack.split_off(self.stack.len() - tuple_len as usize);
@ -227,6 +245,19 @@ impl<'a> Vm<'a> {
self.stack.push(tuple);
self.ip += 2;
}
LoadTuple => {
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() {
self.push(member.clone());
}
}
_ => self.panic("internal error: expected tuple"),
};
self.ip += 1;
}
PushList => {
let list_len = self.chunk.bytecode[self.ip + 1];
let list_members = self.stack.split_off(self.stack.len() - list_len as usize);
@ -267,9 +298,6 @@ impl<'a> Vm<'a> {
self.push(value);
self.ip += 1;
}
MatchTuple => {
todo!()
}
JumpIfNoMatch => {
let jump_len = self.chunk.bytecode[self.ip + 1] as usize;
if !self.matches {
@ -278,6 +306,14 @@ impl<'a> Vm<'a> {
self.ip += 2;
}
}
JumpIfMatch => {
let jump_len = self.chunk.bytecode[self.ip + 1] as usize;
if self.matches {
self.ip += jump_len + 2;
} else {
self.ip += 2;
}
}
TypeOf => {
let val = self.pop();
let type_of = self.chunk.kw_from(val.type_of()).unwrap();
@ -290,7 +326,7 @@ impl<'a> Vm<'a> {
self.push(Value::Number(x as usize as f64));
self.ip += 1;
} else {
self.panic("repeate requires a number");
self.panic("repeat requires a number");
}
}
Decrement => {