dict splatterns first draft

This commit is contained in:
Scott Richmond 2025-06-19 15:56:23 -04:00
parent 4dd4b8ff7e
commit 442532ecd3
3 changed files with 70 additions and 8 deletions

View File

@ -55,6 +55,8 @@ pub enum Op {
ConcatDict,
LoadDictValue,
MatchDict,
MatchSplattedDict,
DropDictEntry,
PushBox,
GetKey,
PanicNoWhen,
@ -179,6 +181,8 @@ impl std::fmt::Display for Op {
ConcatDict => "concat_dict",
LoadDictValue => "load_dict_value",
MatchDict => "match_dict",
MatchSplattedDict => "match_splatted_dict",
DropDictEntry => "drop_dict_entry",
PushBox => "push_box",
GetKey => "get_key",
PanicNoWhen => "panic_no_when",
@ -269,9 +273,9 @@ impl Chunk {
*i += 1;
}
PushBinding | MatchTuple | MatchSplattedTuple | LoadSplattedTuple | MatchList
| MatchSplattedList | LoadSplattedList | MatchDict | LoadDictValue | PushTuple
| PushBox | MatchDepth | PopN | StoreAt | Call | SetUpvalue | GetUpvalue | Partial
| MatchString | PushStringMatches => {
| MatchSplattedList | LoadSplattedList | MatchDict | MatchSplattedDict
| DropDictEntry | 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;
@ -853,13 +857,28 @@ impl<'a> Compiler<'a> {
// then, match against all the values
// then push the dict itself as last value
// and then emit an opcode and constant/keyword to OMIT that key from the dict
let mut is_splatted = false;
if let Some((Splattern(_), _)) = pairs.last() {
is_splatted = true;
self.emit_op(Op::MatchSplattedDict);
} else {
self.emit_op(Op::MatchDict);
}
self.emit_byte(pairs.len());
let before_load_dict_idx = self.stub_jump(Op::JumpIfNoMatch);
let mut jump_idxes = vec![];
let dict_stack_pos = self.stack_depth - 1;
for pair in pairs {
let dict_stack_pos = self.stack_depth - self.match_depth - 1;
let mut splattern = None;
let mut pairs_len = pairs.len();
if is_splatted {
splattern = pairs.last();
pairs_len -= 1;
}
let mut match_depth = self.match_depth;
self.match_depth = 0;
for pair in pairs.iter().take(pairs_len) {
let (PairPattern(key, pattern), _) = pair else {
unreachable!()
};
@ -869,6 +888,28 @@ impl<'a> Compiler<'a> {
self.visit(pattern);
jump_idxes.push(self.stub_jump(Op::JumpIfNoMatch));
}
if is_splatted {
// pull the dict out of the stack
// drop every value in the pattern
self.emit_op(Op::PushBinding);
self.emit_byte(dict_stack_pos);
for pair in pairs.iter().take(pairs_len) {
let (PairPattern(key, _), _) = pair else {
unreachable!()
};
self.emit_constant(Value::Keyword(key));
self.emit_op(Op::DropDictEntry);
}
if let Some(splatt) = splattern {
self.visit(splatt);
}
}
self.match_depth = match_depth + pairs.len();
let jump_idx = self.stub_jump(Op::Jump);
for idx in jump_idxes {

View File

@ -75,8 +75,8 @@ pub fn run(src: &'static str) {
pub fn main() {
env::set_var("RUST_BACKTRACE", "1");
let src = "
let (...y) = (1, 2, 3, 4, 5)
y
let (#{a, ...}, d, (e)) = (#{:a 1, :b 2}, :foo, (:bar))
(a, d, e)
";
run(src);
}

View File

@ -594,6 +594,27 @@ impl Vm {
};
self.ip += 2;
}
MatchSplattedDict => {
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::Dict(members) => self.matches = members.len() >= patt_len as usize,
_ => self.matches = false,
}
self.ip += 2;
}
DropDictEntry => {
let Value::Keyword(key_to_drop) = self.pop() else {
unreachable!()
};
let Value::Dict(mut dict) = self.pop() else {
unreachable!()
};
dict.remove(key_to_drop);
self.push(Value::Dict(dict));
self.ip += 1;
}
PushBox => {
let val = self.pop();
self.push(Value::Box(Rc::new(RefCell::new(val))));