diff --git a/src/compiler.rs b/src/compiler.rs index 9c95112..649b59a 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -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 - self.emit_op(Op::MatchDict); + 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 { diff --git a/src/main.rs b/src/main.rs index b5eb6b6..0cb82bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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); } diff --git a/src/vm.rs b/src/vm.rs index 1c0924b..8691b48 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -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))));