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,
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
// 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
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

View File

@ -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);

View File

@ -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;