diff --git a/src/compiler.rs b/src/compiler.rs index c1975a3..9a84d6f 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1,5 +1,4 @@ use crate::parser::Ast; -use crate::pattern::Pattern; use crate::spans::Spanned; use crate::value::*; use chumsky::prelude::SimpleSpan; @@ -32,6 +31,8 @@ pub enum Op { MatchTuple, PushTuple, LoadTuple, + MatchList, + LoadList, PushList, PushDict, PushBox, @@ -75,6 +76,8 @@ impl std::fmt::Display for Op { MatchTuple => "match_tuple", PushTuple => "push_tuple", LoadTuple => "load_tuple", + MatchList => "match_list", + LoadList => "load_list", PushList => "push_list", PushDict => "push_dict", PushBox => "push_box", @@ -117,7 +120,7 @@ impl Chunk { match op { Pop | Store | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse | PanicIfNoMatch | MatchWord | ResetMatch | GetKey | PanicNoWhen | PanicNoMatch - | TypeOf | Duplicate | Decrement | Truncate | Noop | LoadTuple => { + | TypeOf | Duplicate | Decrement | Truncate | Noop | LoadTuple | LoadList => { println!("{i:04}: {op}") } Constant | MatchConstant => { @@ -125,9 +128,9 @@ impl Chunk { let value = &self.constants[next as usize].show(self); println!("{i:04}: {:16} {next:04}: {value}", op.to_string()); } - PushBinding | MatchTuple | PushTuple | PushDict | PushList | PushBox | Jump - | JumpIfFalse | JumpIfNoMatch | JumpIfMatch | JumpBack | JumpIfZero | MatchDepth - | PopN => { + PushBinding | MatchTuple | MatchList | PushTuple | PushDict | PushList | PushBox + | Jump | JumpIfFalse | JumpIfNoMatch | JumpIfMatch | JumpBack | JumpIfZero + | MatchDepth | PopN => { let next = self.bytecode[i + 1]; println!("{i:04}: {:16} {next:04}", op.to_string()); } @@ -260,7 +263,6 @@ impl Compiler { self.spans.push(self.span); self.chunk.bytecode.push(constant_index as u8); self.spans.push(self.span); - // self.bind(""); } fn emit_op(&mut self, op: Op) { @@ -434,19 +436,15 @@ impl Compiler { } PlaceholderPattern => { self.emit_op(Op::MatchWord); - // self.bind(""); } NilPattern => { self.emit_op(Op::MatchNil); - // self.bind(""); } BooleanPattern(b) => { if *b { self.emit_op(Op::MatchTrue); - // self.bind(""); } else { self.emit_op(Op::MatchFalse); - // self.bind(""); } } NumberPattern(n) => { @@ -474,28 +472,6 @@ 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()); @@ -522,10 +498,10 @@ impl Compiler { 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 + // (i.e., `let` and `match`) // thus: emit Op::Pop directly self.emit_op(Op::Pop); } @@ -533,6 +509,43 @@ impl Compiler { 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; } + ListPattern(members) => { + self.emit_op(Op::MatchList); + self.emit_byte(members.len()); + self.emit_op(Op::JumpIfNoMatch); + let before_load_list_idx = self.len(); + self.emit_byte(0xff); + let mut jump_idxes = vec![]; + self.match_depth += members.len(); + self.emit_op(Op::LoadList); + 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() { + // 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 + // (i.e., `let` and `match`) + // thus: emit Op::Pop directly + self.emit_op(Op::Pop); + } + self.chunk.bytecode[before_load_list_idx] = + self.len() as u8 - before_load_list_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); @@ -556,12 +569,20 @@ impl Compiler { self.bind(name); } Dict(pairs) => { + println!("Compiling dict of len {}", pairs.len()); + println!("Stack len: {}", self.stack_depth); for pair in pairs { self.visit(pair); + println!("Visited pair {:?}", pair); + println!("Current stack len: {}", self.stack_depth); } self.emit_op(Op::PushDict); self.emit_byte(pairs.len()); - self.stack_depth = self.stack_depth + 1 - pairs.len(); + self.stack_depth = self.stack_depth + 1 - (pairs.len() * 2); + println!( + "Finished compiling dict, stack len now: {}", + self.stack_depth + ); } Pair(key, value) => { let existing_kw = self.chunk.keywords.iter().position(|kw| kw == key); @@ -597,10 +618,6 @@ 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(); @@ -611,11 +628,11 @@ impl Compiler { self.emit_byte(0xff); self.stack_depth -= 1; self.visit(body); + self.stack_depth -= 1; self.emit_op(Op::Jump); 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 -= 1; } self.emit_op(Op::PanicNoWhen); for idx in jump_idxes { @@ -824,7 +841,6 @@ impl Compiler { | InterpolatedPattern(..) | AsPattern(..) | Splattern(..) - | ListPattern(..) | PairPattern(..) | DictPattern(..) => todo!(), } @@ -840,7 +856,8 @@ impl Compiler { match op { Noop | Pop | Store | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse | MatchWord | ResetMatch | PanicIfNoMatch | GetKey | PanicNoWhen - | PanicNoMatch | TypeOf | Duplicate | Truncate | Decrement | LoadTuple => { + | PanicNoMatch | TypeOf | Duplicate | Truncate | Decrement | LoadTuple + | LoadList => { println!("{i:04}: {op}") } Constant | MatchConstant => { @@ -848,9 +865,9 @@ impl Compiler { let value = &self.chunk.constants[*next as usize].show(&self.chunk); println!("{i:04}: {:16} {next:04}: {value}", op.to_string()); } - PushBinding | MatchTuple | PushTuple | PushDict | PushList | PushBox | Jump - | JumpIfFalse | JumpIfNoMatch | JumpIfMatch | JumpBack | JumpIfZero - | MatchDepth | PopN => { + PushBinding | MatchTuple | MatchList | PushTuple | PushDict | PushList + | PushBox | Jump | JumpIfFalse | JumpIfNoMatch | JumpIfMatch | JumpBack + | JumpIfZero | MatchDepth | PopN => { let (_, next) = codes.next().unwrap(); println!("{i:04}: {:16} {next:04}", op.to_string()); } diff --git a/src/main.rs b/src/main.rs index 10b374c..bd6d4eb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -74,7 +74,7 @@ pub fn run(src: &'static str) { pub fn main() { env::set_var("RUST_BACKTRACE", "1"); let src = " - +#{:a 1, :b 2, :c 3} "; run(src); } diff --git a/src/vm.rs b/src/vm.rs index 71cf25f..f730d2c 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -265,6 +265,29 @@ impl<'a> Vm<'a> { self.stack.push(list); self.ip += 2; } + MatchList => { + 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::List(members) => self.matches = members.len() == tuple_len as usize, + _ => self.matches = false, + }; + self.ip += 2; + } + LoadList => { + let idx = self.stack.len() - self.match_depth as usize - 1; + let tuple = self.stack[idx].clone(); + match tuple { + Value::List(members) => { + for member in members.iter() { + self.push(member.clone()); + } + } + _ => self.panic("internal error: expected tuple"), + }; + self.ip += 1; + } PushDict => { let dict_len = self.chunk.bytecode[self.ip + 1] as usize * 2; let dict_members = self.stack.split_off(self.stack.len() - dict_len);