summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml1
-rw-r--r--src/errors.rs15
-rw-r--r--src/parser.rs88
-rw-r--r--src/vm/de.rs385
-rw-r--r--src/vm/mod.rs17
5 files changed, 419 insertions, 87 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 780a162..3b205c1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,6 +15,7 @@ homepage = "https://soniex2.github.io/ganarchy/project/c0b4a8a326a320ac33c5d9d6b
 erased-serde = "0.3.21"
 impl_trait = "0.1.7"
 indexmap = "1.9.1"
+postcard = "1.0.2"
 regex = "1"
 serde = "1.0.140"
 serde_transmute = "0.1.4"
diff --git a/src/errors.rs b/src/errors.rs
index 25b28ec..914b70a 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -42,8 +42,8 @@ pub enum PatternError<'a> {
     Unimplemented(usize, &'a str),
 }
 
-// /// These are errors that may be returned by the matcher when matching a
-// /// pattern.
+/// These are errors that may be returned by the matcher when matching a
+/// pattern.
 // #[derive(Clone, Debug)]
 #[derive(Debug)]
 #[non_exhaustive]
@@ -52,12 +52,7 @@ pub enum MatchError {
      StackOverflow,
      /// Returned if the pattern rejects the input.
      ValidationError,
-//     /// Returned if the pattern attempts an unsupported operation.
-//     ///
-//     /// In particular, if the [`PatternTypes`] doesn't support `get` or `pairs`
-//     /// for a given value, this error will be returned. It can be treated as a
-//     /// ValidationError, or as a bug in the pattern, at the user's discretion.
-//     UnsupportedOperation,
-//     /// Returned if an unspecified non-critical error occurred.
-//     Other
+     /// Returned if the pattern contains conflicting/unsatisfiable type
+     /// requirements.
+     Unsatisfiable,
 }
diff --git a/src/parser.rs b/src/parser.rs
index 595f157..0698b6b 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -144,6 +144,10 @@ impl_trait! {
             }
         }
 
+        fn is_empty(&self) -> bool {
+            self.root.consts.protos.last().unwrap().is_empty()
+        }
+
         fn commit(self) -> usize {
             let mut self_ = ManuallyDrop::new(self);
             let proto = self_.root.consts.protos.pop().unwrap();
@@ -207,11 +211,11 @@ impl_trait! {
         }
 
         fn commit(self) {
-            let _self = &mut *std::mem::ManuallyDrop::new(self);
+            let self_ = &mut *std::mem::ManuallyDrop::new(self);
             // we could write a proper parser for the token stream.
             //
             // we could also just do this instead.
-            match _self.root.tokens.drain(_self.len..).as_slice() {
+            match self_.root.tokens.drain(self_.len..).as_slice() {
                 &[
                     PatternToken::Arrow,
                     PatternToken::KeySubtree(index),
@@ -221,9 +225,9 @@ impl_trait! {
                     let tag = PatternElement::Tag {
                         key_subtree: Some(index),
                     };
-                    _self.root.consts.protos.last_mut().unwrap().push(tag);
+                    self_.root.consts.protos.last_mut().unwrap().push(tag);
                     let value = collect_name_and_value(name_value);
-                    _self.root.consts.protos.last_mut().unwrap().push(value);
+                    self_.root.consts.protos.last_mut().unwrap().push(value);
                 },
                 &[
                     PatternToken::Arrow,
@@ -233,9 +237,9 @@ impl_trait! {
                     let tag = PatternElement::Tag {
                         key_subtree: None,
                     };
-                    _self.root.consts.protos.last_mut().unwrap().push(tag);
+                    self_.root.consts.protos.last_mut().unwrap().push(tag);
                     let value = collect_name_and_value(name_value);
-                    _self.root.consts.protos.last_mut().unwrap().push(value);
+                    self_.root.consts.protos.last_mut().unwrap().push(value);
                 },
                 other => {
                     unreachable!("{other:?}");
@@ -259,9 +263,8 @@ impl_trait! {
 
         impl trait Drop {
             fn drop(&mut self) {
-                let proto = &mut self.root.tokens;
-                assert!(proto.len() >= self.len);
-                proto.drain(self.len..);
+                assert!(self.root.tokens.len() >= self.len);
+                self.root.tokens.drain(self.len..);
             }
         }
     }
@@ -355,8 +358,7 @@ where
                 self_.consts.strings.push(string);
                 self_.consts.strings.len() - 1
             });
-            let proto = &mut self.tokens;
-            proto.push(PatternToken::String(id, skippable));
+            self.tokens.push(PatternToken::String(id, skippable));
             *s = cursor;
             true
         }))
@@ -413,8 +415,7 @@ where
                 self_.consts.regices.push(re);
                 self_.consts.regices.len() - 1
             });
-            let proto = &mut self.tokens;
-            proto.push(PatternToken::Regex(id, skippable));
+            self.tokens.push(PatternToken::Regex(id, skippable));
             *s = cursor;
             true
         }))
@@ -447,8 +448,7 @@ where
             bry!('matches strip_prefix(&mut cursor, "->"));
             let mut self_ = TagHelper::start(&mut *self);
             {
-                let proto = &mut self_.tokens;
-                proto.push(PatternToken::Arrow);
+                self_.tokens.push(PatternToken::Arrow);
             }
             self_.sp(&mut cursor);
             let _ = self_.key_subtree(&mut cursor)?;
@@ -460,8 +460,7 @@ where
             }
             self_.sp(&mut cursor);
             {
-                let proto = &mut self_.tokens;
-                proto.push(PatternToken::End);
+                self_.tokens.push(PatternToken::End);
             }
             self_.commit();
             *s = cursor;
@@ -504,8 +503,7 @@ where
                 self.consts.strings.push(name.into());
                 self.consts.strings.len() - 1
             });
-            let proto = &mut self.tokens;
-            proto.push(PatternToken::Identifier(id));
+            self.tokens.push(PatternToken::Identifier(id));
             self.sp(&mut cursor);
             *s = cursor;
             true
@@ -538,8 +536,7 @@ where
                 },
                 Ok,
             )?;
-            let proto = &mut self.tokens;
-            proto.push(PatternToken::Parameter(id, skippable));
+            self.tokens.push(PatternToken::Parameter(id, skippable));
             self.sp(&mut cursor);
             *s = cursor;
             true
@@ -557,8 +554,9 @@ where
             let start = cursor;
             bry!('matches self.identifier(&mut cursor)?);
             let name = &start[..pos_of(start, cursor).unwrap_or(start.len())];
-            let proto = &mut self.tokens;
-            proto.push(PatternToken::Type(match name {
+            self.tokens.push(PatternToken::Type(match name {
+                "any" => Type::Any,
+                "ignored_any" => Type::IgnoredAny,
                 "bool" => Type::Bool,
                 "i8" => Type::I8,
                 "i16" => Type::I16,
@@ -620,8 +618,7 @@ where
                 },
                 Ok,
             )?;
-            let proto = &mut self.tokens;
-            proto.push(PatternToken::ApplyPredicate(id, skippable));
+            self.tokens.push(PatternToken::ApplyPredicate(id, skippable));
             self.sp(&mut cursor);
             *s = cursor;
             let pos = pos_of(self.base, start).unwrap();
@@ -639,7 +636,7 @@ where
             bry!('matches strip_prefix(&mut cursor, "["));
             self.sp(&mut cursor);
             let mut subtree = SubtreeHelper::start(&mut *self);
-            // FIXME handle `?`
+            // FIXME we may want to clean up tokens if these fail
             let marker = subtree.tokens.len();
             if !subtree.matcher(&mut cursor)? {
                 bry!('matches subtree.name(&mut cursor)?);
@@ -664,8 +661,7 @@ where
             //let skippable = strip_prefix(&mut cursor, "?");
             *s = cursor;
             let id = subtree.commit();
-            let proto = &mut self.tokens;
-            proto.push(PatternToken::KeySubtree(id));
+            self.tokens.push(PatternToken::KeySubtree(id));
             true
         }))
     }
@@ -688,11 +684,12 @@ where
                 subtree.unexpected_end(&mut cursor)?
             );
             subtree.sp(&mut cursor);
-            let skippable = strip_prefix(&mut cursor, "?");
+            let optional = strip_prefix(&mut cursor, "?");
             *s = cursor;
-            let id = subtree.commit();
-            let proto = &mut self.tokens;
-            proto.push(PatternToken::ValueSubtree(id, skippable));
+            if !subtree.is_empty() {
+                let id = subtree.commit();
+                self.tokens.push(PatternToken::ValueSubtree(id, optional));
+            }
             true
         }))
     }
@@ -720,9 +717,26 @@ where
         while self.tag(&mut cursor)? {
         }
         self.sp(&mut cursor);
+        let mut has_subtrees = false;
+        let marker = self.tokens.len();
+        self.consts.protos.last_mut().unwrap().push({
+            PatternElement::SubtreeMarker
+        });
         while self.value_subtree(&mut cursor)? {
-            let proto = &mut self.tokens;
-            proto.push(PatternToken::End);
+            let subtree = match self.tokens.drain(marker..).as_slice() {
+                &[] => continue,
+                &[PatternToken::ValueSubtree(index, optional)] => {
+                    PatternElement::ValueSubtree { index, optional }
+                },
+                other => {
+                    unreachable!("{other:?}")
+                }
+            };
+            self.consts.protos.last_mut().unwrap().push(subtree);
+        }
+        let proto = self.consts.protos.last_mut().unwrap();
+        if let Some(PatternElement::SubtreeMarker) = proto.last() {
+            proto.pop();
         }
         self.sp(&mut cursor);
         *s = cursor;
@@ -735,7 +749,7 @@ where
         let mut cursor = *s;
         Ok(lblock!('matches: {
             let mut subtree = SubtreeHelper::start(&mut *self);
-            // FIXME handle `?`
+            // FIXME we may want to clean up tokens if subtree.matcher errors
             let marker = subtree.tokens.len();
             let _ = subtree.matcher(&mut cursor)?;
             let value = match subtree.tokens.drain(marker..).as_slice() {
@@ -845,7 +859,8 @@ mod tests {
             let _ = prep_parser(&s).value_subtree(&mut &*s);
             let _ = prep_parser(&s).unexpected_end(&mut &*s);
             let _ = prep_parser(&s).unexpected_token(&mut &*s);
-            let _ = prep_parser(&s).subtree(&mut &*s);
+            // NOTE: never call subtree directly!!!
+            //let _ = prep_parser(&s).subtree(&mut &*s);
             let _ = prep_parser(&s).pattern(&mut &*s);
         }
     }
@@ -861,6 +876,7 @@ mod tests {
         run_pattern(":map");
         run_pattern(":?map");
         run_pattern(":map->[:str]:str");
+        run_pattern(":map(->[:str]:str)()");
     }
 }
 
diff --git a/src/vm/de.rs b/src/vm/de.rs
index e26ec5e..dc3e9b3 100644
--- a/src/vm/de.rs
+++ b/src/vm/de.rs
@@ -134,6 +134,8 @@ impl<'pat, 'state, 'de, O: Serialize> Packer<'pat, 'state, O> {
             if frame.overstep > 0 || !frame.matches {
                 // overstepped and non-matching frames
                 frame.overstep += 1;
+                // FIXME check if this is correct (it probably isn't)
+                frame.matches = false;
             } else {
                 if !frame.next() {
                     // empty/end-of frames
@@ -153,7 +155,7 @@ impl<'pat, 'state, 'de, O: Serialize> Packer<'pat, 'state, O> {
                     frame.overstep = 1;
                     let mut at = index + 1;
                     while self.interp.frames[index].next() {
-                        let op = self.interp.frames[index].op();
+                        let op = self.interp.frames[index].raw_op();
                         if let PatternElement::ValueSubtree {
                             index: subtree, ..
                         } = op {
@@ -187,12 +189,14 @@ impl<'pat, 'state, 'de, O: Serialize> Packer<'pat, 'state, O> {
         self.call_limit += 1;
         let mut index_iter = 0..;
         let mut pack_index = packs.len();
+        let orig_len = self.interp.frames.len();
         while let Some(index) = index_iter.next().filter(|&i| {
-            i < self.interp.frames.len()
+            i < orig_len
         }) {
             // iterate backwards
-            let index = self.interp.frames.len() - index - 1;
+            let index = orig_len - index - 1;
             let frame = &mut self.interp.frames[index];
+            dbg!(index);
             let has_pack = frame.matches;
             if frame.overstep > 0 {
                 // handle overstep
@@ -212,44 +216,44 @@ impl<'pat, 'state, 'de, O: Serialize> Packer<'pat, 'state, O> {
                     let mut target_unwound = false;
                     while count > 0 && target > 0 {
                         target -= 1;
+                        if self.interp.frames[target].matches {
+                            debug_assert!(target_pack > 0);
+                            target_pack -= 1;
+                        }
                         match self.interp.frames[target].num_subtrees() {
-                            Some((num, _)) if num < count => {
-                                if has_pack {
-                                    debug_assert!(target_pack > 0);
-                                    target_pack -= 1;
-                                }
+                            Some((num, unwound)) if num < count => {
                                 count -= num;
                             },
                             Some((num, unwound)) => {
-                                if has_pack {
-                                    debug_assert!(target_pack > 0);
-                                    target_pack -= 1;
-                                }
                                 target_unwound = unwound;
                                 count = 0;
                             },
                             None => {
-                                if has_pack {
-                                    if self.interp.frames[target].matches {
-                                        debug_assert!(target_pack > 0);
-                                        target_pack -= 1;
-                                    }
-                                }
                                 count += 1;
                             },
                         }
                     }
                     if count == 0 {
-                        let frame = self.interp.frames.remove(target);
-                        // TODO what to do with `frame`?
+                        let frame = self.interp.frames.remove(index);
+                        let target_frame = &mut self.interp.frames[target];
+                        // FIXME check frame.matches vs frames[target].op()
+                        // FIXME actually test that this is correct
+                        let op = target_frame.raw_op();
+                        target_frame.prev().then(|| ()).unwrap();
+                        if !target_unwound {
+                            packs.insert(target_pack, Default::default());
+                            pack_index += 1;
+                            // FIXME this is VERY wrong
+                            target_frame.matches = true;
+                        }
                         if has_pack {
                             // has parent frame
                             let pack = packs.remove(pack_index);
-                            if !target_unwound {
-                                packs.insert(target_pack, pack);
-                            } else {
-                                packs[target_pack].merge_from(pack);
-                            }
+                            packs[target_pack].merge_from(pack);
+                        }
+                        if let Some((0, _)) = target_frame.num_subtrees() {
+                            //target_frame.prev().then(|| ()).unwrap();
+                            target_frame.overstep = 0;
                         }
                     }
                 }
@@ -278,11 +282,13 @@ where
             Type::IgnoredAny,
             |target_type, frame| {
                 Ok(match (target_type, frame.get_type()) {
+                    // FIXME handle None correctly
                     // required type binds stronger than any/ignored_any
                     (Type::IgnoredAny, Some((ty, true))) => ty,
                     (Type::Any, Some((ty, true))) => ty,
-                    (ty, Some((Type::IgnoredAny, true))) => ty,
-                    (ty, Some((Type::Any, true))) => ty,
+                    // and also stronger than optional any/ignored_any
+                    (ty, Some((Type::IgnoredAny, _))) => ty,
+                    (ty, Some((Type::Any, _))) => ty,
                     // prefer owned if any branch prefers owned
                     (Type::String, Some((Type::Str, true))) => {
                         Type::String
@@ -306,7 +312,7 @@ where
                     // types which are not the same are an error because we
                     // only request a specific type if it's actually required
                     (left, Some((right, _))) => {
-                        return Err(todo!());
+                        return Err(MatchError::Unsatisfiable);
                     },
                     _ => Type::Any,
                 })
@@ -467,14 +473,167 @@ where
     where
         E: serde::de::Error,
     {
-        // no real option but to clone.
-        vs!(self (Cow::Owned(v.to_owned())) Str (Type::String | Type::Str))
+        let pat = self.interp.pat;
+        let mut obj = None;
+        if self.collecting {
+            obj = Some(SerdeObject::Str(Cow::Owned(v.into())));
+        }
+        let mut packs = Vec::new();
+        let result = {
+            self.frames_mut().iter_active_mut().try_for_each(|frame| {
+                let ty = frame.get_type();
+                match ty {
+                    | Some((Type::String, _))
+                    | Some((Type::Str, _))
+                    | Some((Type::Any, _))
+                    | Some((Type::IgnoredAny, _))
+                    | None
+                    => {},
+                    Some((_, false)) => {
+                        frame.matches = false;
+                        return Ok(());
+                    },
+                    Some((_, true)) => {
+                        return Err(MatchError::ValidationError)
+                    },
+                }
+                match frame.op() {
+                    PatternElement::Value { value: Some(value), .. } => {
+                        match value {
+                            | Value::String { index, skippable }
+                            if pat.strings[index] != v => {
+                                if skippable {
+                                    frame.matches = false;
+                                    return Ok(());
+                                } else {
+                                    return Err(MatchError::ValidationError);
+                                }
+                            },
+                            | Value::Regex { index, skippable }
+                            if !pat.regices[index].is_match(v) => {
+                                if skippable {
+                                    frame.matches = false;
+                                    return Ok(());
+                                } else {
+                                    return Err(MatchError::ValidationError);
+                                }
+                            },
+                            | Value::Type { .. }
+                            | Value::Regex { .. }
+                            | Value::String { .. }
+                            => {}, // ok
+                        }
+                    },
+                    PatternElement::Value { value: None, .. } => {},
+                    _ => unreachable!(),
+                }
+                let mut pack = Pack::default();
+                if let Some(name) = frame.get_name(pat) {
+                    let mut map = IndexMap::new();
+                    map.insert(
+                        name,
+                        (
+                            Pack::default(),
+                            SerdeObject::Str(Cow::Owned(v.into())),
+                        ),
+                    );
+                    pack.subpacks.push(map);
+                }
+                packs.push(pack);
+                Ok(())
+            })
+        };
+        match result {
+            Err(e) => {
+                self.interp.error.insert(e);
+                return Err(todo!());
+            },
+            _ => (),
+        }
+        Ok((packs, obj))
     }
     fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
     where
         E: serde::de::Error,
     {
-        vs!(self (Cow::Borrowed(v)) Str (Type::String | Type::Str))
+        let pat = self.interp.pat;
+        let mut obj = None;
+        if self.collecting {
+            obj = Some(SerdeObject::Str(Cow::Borrowed(v)));
+        }
+        let mut packs = Vec::new();
+        let result = {
+            self.frames_mut().iter_active_mut().try_for_each(|frame| {
+                let ty = frame.get_type();
+                match ty {
+                    | Some((Type::String, _))
+                    | Some((Type::Str, _))
+                    | Some((Type::Any, _))
+                    | Some((Type::IgnoredAny, _))
+                    | None
+                    => {},
+                    Some((_, false)) => {
+                        frame.matches = false;
+                        return Ok(());
+                    },
+                    Some((_, true)) => {
+                        return Err(MatchError::ValidationError)
+                    },
+                }
+                match frame.op() {
+                    PatternElement::Value { value: Some(value), .. } => {
+                        match value {
+                            | Value::String { index, skippable }
+                            if pat.strings[index] != v => {
+                                if skippable {
+                                    frame.matches = false;
+                                    return Ok(());
+                                } else {
+                                    return Err(MatchError::ValidationError);
+                                }
+                            },
+                            | Value::Regex { index, skippable }
+                            if !pat.regices[index].is_match(v) => {
+                                if skippable {
+                                    frame.matches = false;
+                                    return Ok(());
+                                } else {
+                                    return Err(MatchError::ValidationError);
+                                }
+                            },
+                            | Value::Type { .. }
+                            | Value::Regex { .. }
+                            | Value::String { .. }
+                            => {}, // ok
+                        }
+                    },
+                    PatternElement::Value { value: None, .. } => {},
+                    _ => unreachable!(),
+                }
+                let mut pack = Pack::default();
+                if let Some(name) = frame.get_name(pat) {
+                    let mut map = IndexMap::new();
+                    map.insert(
+                        name,
+                        (
+                            Pack::default(),
+                            SerdeObject::Str(Cow::Borrowed(v)),
+                        ),
+                    );
+                    pack.subpacks.push(map);
+                }
+                packs.push(pack);
+                Ok(())
+            })
+        };
+        match result {
+            Err(e) => {
+                self.interp.error.insert(e);
+                return Err(todo!());
+            },
+            _ => (),
+        }
+        Ok((packs, obj))
     }
     fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
     where
@@ -684,11 +843,11 @@ where
             }
         }
         let obj = SerdeObject::Map(obj_inner);
+        dbg!(&self.interp.frames);
         let mut final_packs = self.step_out(output_packs);
-        let mut iter_final_packs = final_packs.iter_mut();
-        self.frames_mut().iter_active_mut().zip({
-            final_packs.iter_mut()
-        }).for_each(|(frame, pack)| {
+        let mut iter_final_packs = 0..;
+        dbg!(&self.interp.frames);
+        self.frames_mut().iter_active_mut().for_each(|frame| {
             let ty = frame.get_type();
             match ty {
                 | Some((Type::Map, _))
@@ -696,10 +855,17 @@ where
                 | Some((Type::IgnoredAny, _))
                 | None
                 => {
-                    frame.matches = true;
+                    let matched = std::mem::replace(&mut frame.matches, true);
+                    if !matched {
+                        final_packs.insert(
+                            iter_final_packs.start,
+                            Pack::default(),
+                        );
+                    }
                 },
                 _ => return,
             }
+            let pack = &mut final_packs[iter_final_packs.next().unwrap()];
             if let Some(name) = frame.get_name(pat) {
                 // we can assume collecting == true
                 let old_pack = std::mem::take(pack);
@@ -860,6 +1026,7 @@ mod tests {
     use crate::vm::SerdeObject;
     use crate::vm::Frame;
     use serde_json::Deserializer as JsonDeserializer;
+    use postcard::Deserializer as PostcardDeserializer;
     use serde::de::DeserializeSeed as _;
 
     #[test]
@@ -940,7 +1107,6 @@ mod tests {
         let mut frames = Default::default();
         let interp = Interpreter::new(&consts, &mut err, &mut frames);
         let packed = Packer::new(interp, MAX_CALLS).deserialize(&mut der);
-        dbg!(&packed);
         // error produced by serde_json
         assert!(packed.is_err());
     }
@@ -1003,6 +1169,7 @@ mod tests {
 
     #[test]
     fn test_map() {
+        // test visit_map
         let mut consts = PatternConstants::<()>::default();
         consts.strings.push("key".into());
         consts.strings.push("value".into());
@@ -1055,5 +1222,149 @@ mod tests {
             SerdeObject::U64(1),
         );
     }
+
+    #[test]
+    fn test_parser_empty() {
+        // use a parsed empty pattern to test Packer
+        let consts = crate::parser::parse::<&'static str, &'static str, ()>(
+            "",
+            None,
+            None,
+        ).unwrap();
+        let mut der = JsonDeserializer::from_str(r#"{"hello": 0, "world": 1}"#);
+        let mut err = Default::default();
+        let mut frames = Default::default();
+        let interp = Interpreter::new(
+            &consts,
+            &mut err,
+            &mut frames,
+            //&mut output,
+        );
+        let (mut packs, obj) = Packer::new(
+            interp,
+            MAX_CALLS,
+        ).deserialize(&mut der).unwrap();
+        assert!(obj.is_none());
+        assert!(packs.len() == 1);
+        let pack = packs.pop().unwrap();
+        assert!(pack.subpacks.is_empty());
+    }
+
+    #[test]
+    fn test_parser_basic() {
+        // use a basic parsed pattern to test Packer
+        let consts = crate::parser::parse::<&'static str, &'static str, ()>(
+            ":map->[name:str]value:str",
+            None,
+            None,
+        ).unwrap();
+        let data = &[
+            0x02, // map length (2)
+            0x04, // string length (4)
+            0x6E, 0x61, 0x6D, 0x65, // b'name'
+            0x01, // string length (1)
+            0x61, // b'a'
+            0x05, // string length (5)
+            0x76, 0x61, 0x6C, 0x75, 0x65, // b'value'
+            0x01, // string length (1)
+            0x62, // b'b'
+        ];
+        let mut der = PostcardDeserializer::from_bytes(data);
+        let mut err = Default::default();
+        let mut frames = Default::default();
+        let interp = Interpreter::new(
+            &consts,
+            &mut err,
+            &mut frames,
+            //&mut output,
+        );
+        let result = Packer::new(
+            interp,
+            MAX_CALLS,
+        ).deserialize(&mut der);
+        let (mut packs, obj) = result.unwrap();
+        assert!(obj.is_none());
+        assert!(packs.len() == 1);
+        let pack = packs.pop().unwrap();
+        assert!(pack.subpacks.len() == 2);
+    }
+
+    #[test]
+    fn test_parser_basic_subtree() {
+        // use a basic parsed pattern with a subtree to test Packer
+        let consts = crate::parser::parse::<&'static str, &'static str, ()>(
+            ":map(->[name:str]value:str)",
+            None,
+            None,
+        ).unwrap();
+        let data = &[
+            0x02, // map length (2)
+            0x04, // string length (4)
+            0x6E, 0x61, 0x6D, 0x65, // b'name'
+            0x01, // string length (1)
+            0x61, // b'a'
+            0x05, // string length (5)
+            0x76, 0x61, 0x6C, 0x75, 0x65, // b'value'
+            0x01, // string length (1)
+            0x62, // b'b'
+        ];
+        let mut der = PostcardDeserializer::from_bytes(data);
+        let mut err = Default::default();
+        let mut frames = Default::default();
+        let interp = Interpreter::new(
+            &consts,
+            &mut err,
+            &mut frames,
+            //&mut output,
+        );
+        let result = Packer::new(
+            interp,
+            MAX_CALLS,
+        ).deserialize(&mut der);
+        let (mut packs, obj) = result.unwrap();
+        assert!(obj.is_none());
+        assert!(packs.len() == 1);
+        let pack = packs.pop().unwrap();
+        assert!(pack.subpacks.len() == 2);
+    }
+
+    #[test]
+    fn test_parser_subtrees() {
+        // use a parsed pattern with subtrees to test Packer
+        // also test a non-self-describing format (postcard)
+        let consts = crate::parser::parse::<&'static str, &'static str, ()>(
+            ":map(->['name'?]name:str)(->['value'?]value:u32)(->[:str]:?ignored_any)",
+            None,
+            None,
+        ).unwrap();
+        let data = &[
+            0x02, // map length (2)
+            0x04, // string length (4)
+            0x6E, 0x61, 0x6D, 0x65, // b'name'
+            0x01, // string length (1)
+            0x61, // b'a'
+            0x05, // string length (5)
+            0x76, 0x61, 0x6C, 0x75, 0x65, // b'value'
+            0x01, // 1
+        ];
+        let mut der = PostcardDeserializer::from_bytes(data);
+        let mut err = Default::default();
+        let mut frames = Default::default();
+        let interp = Interpreter::new(
+            &consts,
+            &mut err,
+            &mut frames,
+            //&mut output,
+        );
+        let result = Packer::new(
+            interp,
+            MAX_CALLS,
+        ).deserialize(&mut der);
+        let (mut packs, obj) = result.unwrap();
+        assert!(obj.is_none());
+        assert!(packs.len() == 1);
+        let pack = packs.pop().unwrap();
+        assert!(pack.subpacks.len() == 2);
+    }
 }
 
diff --git a/src/vm/mod.rs b/src/vm/mod.rs
index 06f12e5..131e48a 100644
--- a/src/vm/mod.rs
+++ b/src/vm/mod.rs
@@ -355,8 +355,17 @@ pub struct Pack<'pat, 'de> {
 impl<'pat, 'de> Pack<'pat, 'de> {
     /// Merges two packs, with elements from `other` coming after `self`.
     fn merge_from(&mut self, mut other: Self) {
-        for (left, right) in self.subpacks.iter_mut().zip(other.subpacks) {
-            left.extend(right)
+        match (self.subpacks.len(), other.subpacks.len()) {
+            (0, _) => {
+                *self = other;
+            },
+            (_, 0) => {},
+            (a, b) if a == b => {
+                for (l, r) in self.subpacks.iter_mut().zip(other.subpacks) {
+                    l.extend(r)
+                }
+            },
+            _ => unreachable!("merge_from unbalanced iterations"),
         }
     }
 
@@ -476,7 +485,7 @@ impl<'pat> Frame<'pat> {
     /// Panics if called on a non-matching frame or if iteration hasn't begun.
     fn op(&self) -> PatternElement {
         assert!(self.active(), "op() called on inactive frame");
-        self.ops[self.iar.expect("ops[iar]")]
+        self.raw_op()
     }
 
     /// Counts the number of *active* subtrees, if any, and whether any
@@ -486,7 +495,7 @@ impl<'pat> Frame<'pat> {
     ///
     /// Panics if iteration hasn't begun.
     fn num_subtrees(&self) -> Option<(usize, bool)> {
-        let iar = self.iar?;
+        let iar = self.iar.unwrap();
         // check if there are any subtrees
         matches!(
             self.ops[iar],