From 35d7d3b1c80b20b7e8fbf80d2442a6ebddc07ee4 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 19 Jun 2025 12:37:29 -0400 Subject: [PATCH] first draft of tuple splatterns --- src/compiler.rs | 29 ++++++++++++++++++++++++----- src/main.rs | 2 +- src/vm.rs | 27 +++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/compiler.rs b/src/compiler.rs index 8e028d1..e089273 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -39,8 +39,10 @@ pub enum Op { PushStringMatches, MatchType, MatchTuple, + MatchSplattedTuple, PushTuple, LoadTuple, + LoadSplattedTuple, MatchList, MatchSplattedList, LoadList, @@ -161,8 +163,10 @@ impl std::fmt::Display for Op { PushStringMatches => "push_string_matches", MatchType => "match_type", MatchTuple => "match_tuple", + MatchSplattedTuple => "match_splatted_tuple", PushTuple => "push_tuple", LoadTuple => "load_tuple", + LoadSplattedTuple => "load_splatted_tuple", MatchList => "match_list", MatchSplattedList => "match_splatted_list", LoadList => "load_list", @@ -264,9 +268,10 @@ impl Chunk { println!("{i:04}: {:16} {next:03}: {value}", op.to_string()); *i += 1; } - PushBinding | MatchTuple | MatchList | MatchSplattedList | LoadSplattedList - | MatchDict | LoadDictValue | PushTuple | PushBox | MatchDepth | PopN | StoreAt - | Call | SetUpvalue | GetUpvalue | Partial | MatchString | PushStringMatches => { + PushBinding | MatchTuple | MatchSplattedTuple | LoadSplattedTuple | MatchList + | MatchSplattedList | LoadSplattedList | MatchDict | LoadDictValue | PushTuple + | PushBox | MatchDepth | PopN | StoreAt | Call | SetUpvalue | GetUpvalue | Partial + | MatchString | PushStringMatches => { let next = self.bytecode[*i + 1]; println!("{i:04}: {:16} {next:03}", op.to_string()); *i += 1; @@ -729,7 +734,16 @@ impl<'a> Compiler<'a> { } TuplePattern(members) => { // first, test the tuple against length - self.emit_op(Op::MatchTuple); + // check if we're splatted + // different opcodes w/ splats, but same logic + let mut is_splatted = false; + if let Some((Splattern(_), _)) = members.last() { + is_splatted = true; + self.emit_op(Op::MatchSplattedTuple); + } else { + self.emit_op(Op::MatchTuple); + } + self.emit_byte(members.len()); // skip everything if tuple lengths don't match let before_load_tup_idx = self.stub_jump(Op::JumpIfNoMatch); @@ -741,7 +755,12 @@ impl<'a> Compiler<'a> { self.match_depth = members.len(); // load the tuple and update the stack len - self.emit_op(Op::LoadTuple); + if is_splatted { + self.emit_op(Op::LoadSplattedTuple); + self.emit_byte(members.len()); + } else { + self.emit_op(Op::LoadTuple); + } self.stack_depth += members.len(); // visit each member diff --git a/src/main.rs b/src/main.rs index 9245bd7..c63518a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -75,7 +75,7 @@ pub fn run(src: &'static str) { pub fn main() { env::set_var("RUST_BACKTRACE", "1"); let src = " -let [x, ...y] = [1, 2, 3, 4, 5] +let (...y) = (1, 2) y "; run(src); diff --git a/src/vm.rs b/src/vm.rs index e67b3af..1c0924b 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -409,6 +409,16 @@ impl Vm { }; self.ip += 2; } + MatchSplattedTuple => { + let idx = self.stack.len() - self.match_depth as usize - 1; + let patt_len = self.chunk().bytecode[self.ip + 1]; + let scrutinee = self.stack[idx].clone(); + match scrutinee { + Value::Tuple(members) => self.matches = members.len() >= patt_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); @@ -429,6 +439,23 @@ impl Vm { }; self.ip += 1; } + LoadSplattedTuple => { + let load_len = self.chunk().bytecode[self.ip + 1] as usize; + let idx = self.stack.len() - self.match_depth as usize - 1; + let tuple = self.stack[idx].clone(); + let Value::Tuple(members) = tuple else { + return self.panic("internal error: expected tuple"); + }; + for i in 0..load_len - 1 { + self.push(members[i].clone()); + } + let mut splatted = Vector::new(); + for i in load_len - 1..members.len() { + splatted.push_back(members[i].clone()); + } + self.push(Value::List(Box::new(splatted))); + self.ip += 2; + } PushList => { self.push(Value::List(Box::new(Vector::new()))); self.ip += 1;