first draft of tuple splatterns

This commit is contained in:
Scott Richmond 2025-06-19 12:37:29 -04:00
parent a4d68fa02e
commit 35d7d3b1c8
3 changed files with 52 additions and 6 deletions

View File

@ -39,8 +39,10 @@ pub enum Op {
PushStringMatches, PushStringMatches,
MatchType, MatchType,
MatchTuple, MatchTuple,
MatchSplattedTuple,
PushTuple, PushTuple,
LoadTuple, LoadTuple,
LoadSplattedTuple,
MatchList, MatchList,
MatchSplattedList, MatchSplattedList,
LoadList, LoadList,
@ -161,8 +163,10 @@ impl std::fmt::Display for Op {
PushStringMatches => "push_string_matches", PushStringMatches => "push_string_matches",
MatchType => "match_type", MatchType => "match_type",
MatchTuple => "match_tuple", MatchTuple => "match_tuple",
MatchSplattedTuple => "match_splatted_tuple",
PushTuple => "push_tuple", PushTuple => "push_tuple",
LoadTuple => "load_tuple", LoadTuple => "load_tuple",
LoadSplattedTuple => "load_splatted_tuple",
MatchList => "match_list", MatchList => "match_list",
MatchSplattedList => "match_splatted_list", MatchSplattedList => "match_splatted_list",
LoadList => "load_list", LoadList => "load_list",
@ -264,9 +268,10 @@ impl Chunk {
println!("{i:04}: {:16} {next:03}: {value}", op.to_string()); println!("{i:04}: {:16} {next:03}: {value}", op.to_string());
*i += 1; *i += 1;
} }
PushBinding | MatchTuple | MatchList | MatchSplattedList | LoadSplattedList PushBinding | MatchTuple | MatchSplattedTuple | LoadSplattedTuple | MatchList
| MatchDict | LoadDictValue | PushTuple | PushBox | MatchDepth | PopN | StoreAt | MatchSplattedList | LoadSplattedList | MatchDict | LoadDictValue | PushTuple
| Call | SetUpvalue | GetUpvalue | Partial | MatchString | PushStringMatches => { | PushBox | MatchDepth | PopN | StoreAt | Call | SetUpvalue | GetUpvalue | Partial
| MatchString | PushStringMatches => {
let next = self.bytecode[*i + 1]; let next = self.bytecode[*i + 1];
println!("{i:04}: {:16} {next:03}", op.to_string()); println!("{i:04}: {:16} {next:03}", op.to_string());
*i += 1; *i += 1;
@ -729,7 +734,16 @@ impl<'a> Compiler<'a> {
} }
TuplePattern(members) => { TuplePattern(members) => {
// first, test the tuple against length // 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()); self.emit_byte(members.len());
// skip everything if tuple lengths don't match // skip everything if tuple lengths don't match
let before_load_tup_idx = self.stub_jump(Op::JumpIfNoMatch); let before_load_tup_idx = self.stub_jump(Op::JumpIfNoMatch);
@ -741,7 +755,12 @@ impl<'a> Compiler<'a> {
self.match_depth = members.len(); self.match_depth = members.len();
// load the tuple and update the stack 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(); self.stack_depth += members.len();
// visit each member // visit each member

View File

@ -75,7 +75,7 @@ pub fn run(src: &'static str) {
pub fn main() { pub fn main() {
env::set_var("RUST_BACKTRACE", "1"); env::set_var("RUST_BACKTRACE", "1");
let src = " let src = "
let [x, ...y] = [1, 2, 3, 4, 5] let (...y) = (1, 2)
y y
"; ";
run(src); run(src);

View File

@ -409,6 +409,16 @@ impl Vm {
}; };
self.ip += 2; 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 => { PushTuple => {
let tuple_len = self.chunk().bytecode[self.ip + 1]; let tuple_len = self.chunk().bytecode[self.ip + 1];
let tuple_members = self.stack.split_off(self.stack.len() - tuple_len as usize); let tuple_members = self.stack.split_off(self.stack.len() - tuple_len as usize);
@ -429,6 +439,23 @@ impl Vm {
}; };
self.ip += 1; 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 => { PushList => {
self.push(Value::List(Box::new(Vector::new()))); self.push(Value::List(Box::new(Vector::new())));
self.ip += 1; self.ip += 1;