diff --git a/src/dec/decode.rs b/src/dec/decode.rs index 1e4060b..edde630 100644 --- a/src/dec/decode.rs +++ b/src/dec/decode.rs @@ -3,13 +3,12 @@ use std::collections::HashMap; use nom::{ branch::alt, bytes::complete::{tag, take_while, take_while1}, - combinator::{opt, map}, + combinator::{map, opt}, multi::{separated_list0, separated_list1}, - IResult, - InputLength, + IResult, InputLength, }; -use crate::dec::{NonListTerm, Term, debug}; +use crate::dec::{debug, AnyTerm, NonListTerm, Term}; const DICT_OPEN: &[u8] = b"{"; const DICT_CLOSE: &[u8] = b"}"; @@ -19,46 +18,52 @@ const STR_EXTRA_CHARS: &[u8] = b"._-*?"; // ---- +/// The error kind returned by the `decode` function. #[derive(Eq, PartialEq)] -pub enum Error<'a> { +pub enum DecodeError<'a> { + /// Indicates that there is trailing garbage at the end of the decoded string Garbage(&'a [u8]), + /// Indicates that the entered string does not represent a complete NetText term IncompleteInput, + /// Indicates a syntax error in the decoded term NomError(&'a [u8], nom::error::ErrorKind), } -impl<'a> std::fmt::Debug for Error<'a> { +impl<'a> std::fmt::Debug for DecodeError<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { match self { - Error::Garbage(g) => write!(f, "Garbage: `{}`", debug(g)), - Error::IncompleteInput => write!(f, "Incomplete input"), - Error::NomError(s, e) => write!(f, "Nom: {:?}, at: `{}`", e, debug(s)), + DecodeError::Garbage(g) => write!(f, "Garbage: `{}`", debug(g)), + DecodeError::IncompleteInput => write!(f, "Incomplete input"), + DecodeError::NomError(s, e) => write!(f, "Nom: {:?}, at: `{}`", e, debug(s)), } } } -pub type Result<'a, T> = std::result::Result>; +/// The result type returned by the `decode` function +pub type DecodeResult<'a, T> = std::result::Result>; -impl<'a> From>> for Error<'a> { - fn from(e: nom::Err>) -> Error<'a> { +impl<'a> From>> for DecodeError<'a> { + fn from(e: nom::Err>) -> DecodeError<'a> { match e { - nom::Err::Incomplete(_) => Error::IncompleteInput, - nom::Err::Error(e) | nom::Err::Failure(e) => Error::NomError(e.input, e.code), + nom::Err::Incomplete(_) => DecodeError::IncompleteInput, + nom::Err::Error(e) | nom::Err::Failure(e) => DecodeError::NomError(e.input, e.code), } } } // ---- -pub fn decode<'a>(input: &'a [u8]) -> Result<'a, Term<'a>> { +/// Decodes a NetText string into the term it represents. +pub fn decode<'a>(input: &'a [u8]) -> DecodeResult<'a, Term<'a, 'static>> { let (rest, term) = decode_term(input)?; let (end, _) = take_while(is_whitespace)(rest)?; if !end.is_empty() { - return Err(Error::Garbage(end)); + return Err(DecodeError::Garbage(end)); } - Ok(term) + Ok(Term(term)) } -pub fn decode_term<'a>(input: &'a [u8]) -> IResult<&'a [u8], Term<'a>> { +fn decode_term<'a>(input: &'a [u8]) -> IResult<&'a [u8], AnyTerm<'a, 'static>> { eprintln!("DT: `{}`", debug(input)); let (start, _) = take_while(is_whitespace)(input)?; eprintln!("DT2: `{}`", debug(start)); @@ -70,11 +75,11 @@ pub fn decode_term<'a>(input: &'a [u8]) -> IResult<&'a [u8], Term<'a>> { } else { let raw_len = start.input_len() - rest.input_len(); let list_raw = &start[..raw_len]; - Ok((rest, Term::List(list_raw, list))) + Ok((rest, AnyTerm::List(list_raw, list))) } } -pub fn decode_nonlist_term<'a>(input: &'a [u8]) -> IResult<&'a [u8], NonListTerm<'a>> { +fn decode_nonlist_term<'a>(input: &'a [u8]) -> IResult<&'a [u8], NonListTerm<'a, 'static>> { eprintln!("DNLT: `{}`", debug(input)); let (rest, term) = alt(( map(decode_str, NonListTerm::Str), @@ -90,7 +95,7 @@ fn decode_str<'a>(input: &'a [u8]) -> IResult<&'a [u8], &'a [u8]> { Ok((rest, data)) } -type DictType<'a> = (&'a [u8], HashMap<&'a [u8], Term<'a>>); +type DictType<'a> = (&'a [u8], HashMap<&'a [u8], AnyTerm<'a, 'static>>); fn decode_dict<'a>(dict_begin: &'a [u8]) -> IResult<&'a [u8], DictType<'a>> { eprintln!("DDbegin: `{}`", debug(dict_begin)); @@ -98,7 +103,7 @@ fn decode_dict<'a>(dict_begin: &'a [u8]) -> IResult<&'a [u8], DictType<'a>> { eprintln!("DD2: `{}`", debug(d)); let (d, items) = separated_list0(dict_separator, decode_dict_item)(d)?; eprintln!("DD3: `{}`", debug(d)); - let (d, _) = opt(dict_separator)(d)?; + let (d, _) = opt(dict_separator)(d)?; let (d, _) = take_while(is_whitespace)(d)?; eprintln!("DD4: `{}`", debug(d)); let (dict_end, _) = tag(DICT_CLOSE)(d)?; @@ -118,7 +123,7 @@ fn dict_separator<'a>(d: &'a [u8]) -> IResult<&'a [u8], ()> { Ok((d, ())) } -fn decode_dict_item<'a>(d: &'a [u8]) -> IResult<&'a [u8], (&'a [u8], Term<'a>)> { +fn decode_dict_item<'a>(d: &'a [u8]) -> IResult<&'a [u8], (&'a [u8], AnyTerm<'a, 'static>)> { eprintln!("DDI: `{}`", debug(d)); let (d, _) = take_while(is_whitespace)(d)?; eprintln!("DDI1: `{}`", debug(d)); @@ -149,7 +154,7 @@ mod tests { #[test] fn simple_str() { let bytes = b" plop "; - assert_eq!(decode(bytes), Ok(Term::Str(b"plop"))); + assert_eq!(decode(bytes), Ok(AnyTerm::Str(b"plop").into())); } #[test] @@ -157,7 +162,7 @@ mod tests { let bytes = b" plop plap plip ploup "; assert_eq!( decode(bytes), - Ok(Term::List( + Ok(AnyTerm::List( b"plop plap plip ploup", vec![ NonListTerm::Str(b"plop"), @@ -165,7 +170,8 @@ mod tests { NonListTerm::Str(b"plip"), NonListTerm::Str(b"ploup"), ] - )) + ) + .into()) ); } @@ -174,22 +180,23 @@ mod tests { let bytes = b" { aze = hello, by = bojzkz pipo, ccde = ke } "; assert_eq!( decode(bytes), - Ok(Term::Dict( + Ok(AnyTerm::Dict( b"{ aze = hello, by = bojzkz pipo, ccde = ke }", [ - (&b"aze"[..], Term::Str(b"hello")), + (&b"aze"[..], AnyTerm::Str(b"hello")), ( &b"by"[..], - Term::List( + AnyTerm::List( b"bojzkz pipo", vec![NonListTerm::Str(b"bojzkz"), NonListTerm::Str(b"pipo")] ) ), - (&b"ccde"[..], Term::Str(b"ke")), + (&b"ccde"[..], AnyTerm::Str(b"ke")), ] .into_iter() .collect() - )) + ) + .into()) ); } @@ -198,22 +205,23 @@ mod tests { let bytes = b" { aze = hello, by = bojzkz pipo , ccde = ke , } "; assert_eq!( decode(bytes), - Ok(Term::Dict( + Ok(AnyTerm::Dict( b"{ aze = hello, by = bojzkz pipo , ccde = ke , }", [ - (&b"aze"[..], Term::Str(b"hello")), + (&b"aze"[..], AnyTerm::Str(b"hello")), ( &b"by"[..], - Term::List( + AnyTerm::List( b"bojzkz pipo", vec![NonListTerm::Str(b"bojzkz"), NonListTerm::Str(b"pipo")] ) ), - (&b"ccde"[..], Term::Str(b"ke")), + (&b"ccde"[..], AnyTerm::Str(b"ke")), ] .into_iter() .collect() - )) + ) + .into()) ); } @@ -222,10 +230,11 @@ mod tests { let bytes = b"HEAD alexpubkey"; assert_eq!( decode(bytes), - Ok(Term::List( + Ok(AnyTerm::List( b"HEAD alexpubkey", vec![NonListTerm::Str(b"HEAD"), NonListTerm::Str(b"alexpubkey")] - )), + ) + .into()), ); } @@ -234,7 +243,7 @@ mod tests { let bytes = b"STANCE sthash stsign { author = alexpubkey, height = 12, parent = parenthash, data = MESSAGE { text = hello } }"; assert_eq!( decode(bytes), - Ok(Term::List( + Ok(AnyTerm::List( &bytes[..], vec![ NonListTerm::Str(b"STANCE"), @@ -242,17 +251,17 @@ mod tests { NonListTerm::Str(b"stsign"), NonListTerm::Dict(b"{ author = alexpubkey, height = 12, parent = parenthash, data = MESSAGE { text = hello } }", [ - (&b"author"[..], Term::Str(b"alexpubkey")), - (&b"height"[..], Term::Str(b"12")), - (&b"parent"[..], Term::Str(b"parenthash")), - (&b"data"[..], Term::List( + (&b"author"[..], AnyTerm::Str(b"alexpubkey")), + (&b"height"[..], AnyTerm::Str(b"12")), + (&b"parent"[..], AnyTerm::Str(b"parenthash")), + (&b"data"[..], AnyTerm::List( b"MESSAGE { text = hello }", vec![ NonListTerm::Str(b"MESSAGE"), NonListTerm::Dict( b"{ text = hello }", [ - (&b"text"[..], Term::Str(b"hello")), + (&b"text"[..], AnyTerm::Str(b"hello")), ] .into_iter() .collect() @@ -261,7 +270,7 @@ mod tests { )) ].into_iter().collect() ), - ])), - ); + ]).into(), + )); } } diff --git a/src/dec/mod.rs b/src/dec/mod.rs index aa75a9d..8731a26 100644 --- a/src/dec/mod.rs +++ b/src/dec/mod.rs @@ -4,67 +4,428 @@ use std::collections::HashMap; pub use decode::*; -#[derive(Eq, PartialEq)] -pub enum Term<'a> { +/// A parsed NetText term. +/// +/// Lifetime 'a is the lifetime of the buffer containing the encoded data. +/// +/// Lifetime 'b is the lifetime of another Term from which this one is borrowed, when it +/// is returned by one of the helper functions, or 'static when first returned from +/// `decode()` +#[derive(Eq, PartialEq, Debug)] +pub struct Term<'a, 'b>(AnyTerm<'a, 'b>); + +#[derive(Eq, PartialEq, Clone)] +pub(crate) enum AnyTerm<'a, 'b> { Str(&'a [u8]), - Dict(&'a [u8], HashMap<&'a [u8], Term<'a>>), - List(&'a [u8], Vec>), + Dict(&'a [u8], HashMap<&'a [u8], AnyTerm<'a, 'b>>), + DictRef(&'a [u8], &'b HashMap<&'a [u8], AnyTerm<'a, 'b>>), + List(&'a [u8], Vec>), + ListRef(&'a [u8], &'b [NonListTerm<'a, 'b>]), } -#[derive(Eq, PartialEq)] -pub enum NonListTerm<'a> { +#[derive(Eq, PartialEq, Clone)] +pub(crate) enum NonListTerm<'a, 'b> { Str(&'a [u8]), - Dict(&'a [u8], HashMap<&'a [u8], Term<'a>>), + Dict(&'a [u8], HashMap<&'a [u8], AnyTerm<'a, 'b>>), + DictRef(&'a [u8], &'b HashMap<&'a [u8], AnyTerm<'a, 'b>>), } -impl<'a> From> for Term<'a> { - fn from(x: NonListTerm<'a>) -> Term<'a> { +impl<'a, 'b> From> for AnyTerm<'a, 'b> { + fn from(x: NonListTerm<'a, 'b>) -> AnyTerm<'a, 'b> { match x { - NonListTerm::Str(s) => Term::Str(s), - NonListTerm::Dict(raw, d) => Term::Dict(raw, d), + NonListTerm::Str(s) => AnyTerm::Str(s), + NonListTerm::Dict(raw, d) => AnyTerm::Dict(raw, d), + NonListTerm::DictRef(raw, d) => AnyTerm::DictRef(raw, d), } } } -// ---- +impl<'a, 'b> TryFrom> for NonListTerm<'a, 'b> { + type Error = (); + fn try_from(x: AnyTerm<'a, 'b>) -> Result, ()> { + match x { + AnyTerm::Str(s) => Ok(NonListTerm::Str(s)), + AnyTerm::Dict(raw, d) => Ok(NonListTerm::Dict(raw, d)), + AnyTerm::DictRef(raw, d) => Ok(NonListTerm::DictRef(raw, d)), + _ => Err(()), + } + } +} + +impl<'a> From> for Term<'a, 'static> { + fn from(x: AnyTerm<'a, 'static>) -> Term<'a, 'static> { + Term(x) + } +} + +// ---- PUBLIC IMPLS ---- + +#[derive(Debug, Clone)] +pub enum TypeError { + WrongType(&'static str), + WrongLength(usize, usize), + MissingKey(String), + UnexpectedKey(String), +} + +impl<'a, 'b> Term<'a, 'b> { + /// Get the term's raw representation + /// + /// Example: + /// + /// ``` + /// use nettext::dec::decode; + /// + /// let term = decode(b"hello world").unwrap(); + /// assert_eq!(term.raw(), b"hello world"); + /// ``` + pub fn raw(&self) -> &'a [u8] { + self.0.raw() + } + + /// If the term is a single string, get that string + /// + /// Example: + /// + /// ``` + /// use nettext::dec::decode; + /// + /// let term1 = decode(b" hello ").unwrap(); + /// assert_eq!(term1.str().unwrap(), b"hello"); + /// + /// let term2 = decode(b"hello world").unwrap(); + /// assert!(term2.str().is_err()); + /// ``` + pub fn str(&self) -> Result<&'a [u8], TypeError> { + match &self.0 { + AnyTerm::Str(s) => Ok(s), + _ => Err(TypeError::WrongType("STR")), + } + } + + /// If the term is a single string, or a list containing only strings, + /// get its raw representation + /// + /// Example: + /// + /// ``` + /// use nettext::dec::decode; + /// + /// let term1 = decode(b" hello world ").unwrap(); + /// assert_eq!(term1.string().unwrap(), b"hello world"); + /// + /// let term2 = decode(b"hello { a= 5}").unwrap(); + /// assert!(term2.string().is_err()); + /// ``` + pub fn string(&self) -> Result<&'a [u8], TypeError> { + match &self.0 { + AnyTerm::Str(s) => Ok(s), + AnyTerm::List(r, l) if l.iter().all(|x| matches!(x, NonListTerm::Str(_))) => Ok(r), + _ => Err(TypeError::WrongType("STRING")), + } + } + + /// Return a list of terms made from this term. + /// If it is a str or a dict, returns a list of a single term. + /// If it is a list, that's the list of terms we return. + /// + /// Example: + /// + /// ``` + /// use nettext::dec::decode; + /// + /// let term1 = decode(b" hello ").unwrap(); + /// let list1 = term1.list(); + /// assert_eq!(list1.len(), 1); + /// assert_eq!(list1[0].str().unwrap(), b"hello"); + /// + /// let term2 = decode(b" hello world ").unwrap(); + /// let list2 = term2.list(); + /// assert_eq!(list2.len(), 2); + /// assert_eq!(list2[0].str().unwrap(), b"hello"); + /// assert_eq!(list2[1].str().unwrap(), b"world"); + /// ``` + pub fn list(&self) -> Vec> { + match self.0.mkref() { + AnyTerm::ListRef(_r, l) => l.iter().map(|x| Term(x.mkref().into())).collect::>(), + x => vec![Term(x)], + } + } + + /// Same as `.list()`, but deconstructs it in a const length array. + /// This allows to directly bind the resulting list into discrete variables. + /// + /// Example: + /// + /// ``` + /// use nettext::dec::decode; + /// + /// let term1 = decode(b" hello ").unwrap(); + /// let [s1] = term1.list_of().unwrap(); + /// assert_eq!(s1.str().unwrap(), b"hello"); + /// + /// let term2 = decode(b" hello world ").unwrap(); + /// let [s2a, s2b] = term2.list_of().unwrap(); + /// assert_eq!(s2a.str().unwrap(), b"hello"); + /// assert_eq!(s2b.str().unwrap(), b"world"); + /// ``` + pub fn list_of(&self) -> Result<[Term<'a, '_>; N], TypeError> { + let list = self.list(); + let list_len = list.len(); + list.try_into() + .map_err(|_| TypeError::WrongLength(list_len, N)) + } + + /// Same as `.list_of()`, but only binds the first N-1 terms. + /// If there are exactly N terms, the last one is bound to the Nth return variable. + /// If there are more then N terms, the remaining terms are bound to a new list term + /// that is returned as the Nth return variable. + /// + /// Example: + /// + /// ``` + /// use nettext::dec::decode; + /// + /// let term1 = decode(b" hello world ").unwrap(); + /// let [s1a, s1b] = term1.list_of_first().unwrap(); + /// assert_eq!(s1a.str().unwrap(), b"hello"); + /// assert_eq!(s1b.str().unwrap(), b"world"); + /// + /// let term2 = decode(b" hello mighty world ").unwrap(); + /// let [s2a, s2b] = term2.list_of_first().unwrap(); + /// assert_eq!(s2a.str().unwrap(), b"hello"); + /// assert_eq!(s2b.list().len(), 2); + /// assert_eq!(s2b.raw(), b"mighty world"); + /// ``` + pub fn list_of_first(&self) -> Result<[Term<'a, '_>; N], TypeError> { + match self.0.mkref() { + AnyTerm::ListRef(raw, list) => { + if list.len() < N { + Err(TypeError::WrongLength(list.len(), N)) + } else if list.len() == N { + Ok(list + .iter() + .map(|x| Term(x.mkref().into())) + .collect::>() + .try_into() + .unwrap()) + } else { + let mut ret = Vec::with_capacity(N); + for item in list[0..N - 1].iter() { + ret.push(Term(item.mkref().into())); + } + + let remaining_begin = list[N - 1].raw().as_ptr() as usize; + let remaining_offset = remaining_begin - raw.as_ptr() as usize; + let remaining_raw = &raw[remaining_offset..]; + + let remaining = list[N - 1..] + .iter() + .map(|x| x.mkref()) + .collect::>>(); + ret.push(Term(AnyTerm::List(remaining_raw, remaining))); + + Ok(ret.try_into().unwrap()) + } + } + x if N == 1 => Ok([Term(x)] + .into_iter() + .collect::>() + .try_into() + .unwrap()), + _ => Err(TypeError::WrongLength(1, N)), + } + } + + /// Checks term is a dictionnary and returns hashmap of inner terms + /// + /// Example: + /// + /// ``` + /// use nettext::dec::decode; + /// + /// let term = decode(b"{ k1 = v1, k2 = v2 }").unwrap(); + /// let dict = term.dict().unwrap(); + /// assert_eq!(dict.get(&b"k1"[..]).unwrap().str().unwrap(), b"v1"); + /// assert_eq!(dict.get(&b"k2"[..]).unwrap().str().unwrap(), b"v2"); + /// ``` + pub fn dict(&self) -> Result>, TypeError> { + match self.0.mkref() { + AnyTerm::DictRef(_, d) => Ok(d.iter().map(|(k, t)| (*k, Term(t.mkref()))).collect()), + _ => Err(TypeError::WrongType("DICT")), + } + } + + /// Checks term is a dictionnary whose keys are exactly those supplied, + /// and returns the associated values as a list. + /// + /// Example: + /// + /// ``` + /// use nettext::dec::decode; + /// + /// let term = decode(b"{ k1 = v1, k2 = v2 }").unwrap(); + /// let [s1, s2] = term.dict_of([b"k1", b"k2"]).unwrap(); + /// assert_eq!(s1.str().unwrap(), b"v1"); + /// assert_eq!(s2.str().unwrap(), b"v2"); + /// ``` + pub fn dict_of( + &self, + keys: [&'static [u8]; N], + ) -> Result<[Term<'a, '_>; N], TypeError> { + match self.0.mkref() { + AnyTerm::DictRef(_, dict) => { + // Check all required keys exist in dictionnary + for k in keys.iter() { + if !dict.contains_key(k) { + return Err(TypeError::MissingKey(debug(k).to_string())); + } + } + // Check that dictionnary contains no extraneous keys + for k in dict.keys() { + if !keys.contains(k) { + return Err(TypeError::UnexpectedKey(debug(k).to_string())); + } + } + Ok(keys.map(|k| Term(dict.get(k).unwrap().mkref()))) + } + _ => Err(TypeError::WrongType("DICT")), + } + } + + /// Checks term is a dictionnary whose keys are included in those supplied, + /// and returns the associated values as a list of options. + /// + /// Example: + /// + /// ``` + /// use nettext::dec::decode; + /// + /// let term = decode(b"{ k1 = v1, k2 = v2 }").unwrap(); + /// let [s1, s2, s3] = term.dict_of_opt([b"k1", b"k2", b"k3"]).unwrap(); + /// assert_eq!(s1.unwrap().str().unwrap(), b"v1"); + /// assert_eq!(s2.unwrap().str().unwrap(), b"v2"); + /// assert!(s3.is_none()); + /// ``` + pub fn dict_of_opt( + &self, + keys: [&'static [u8]; N], + ) -> Result<[Option>; N], TypeError> { + match self.0.mkref() { + AnyTerm::DictRef(_, dict) => { + // Check that dictionnary contains no extraneous keys + for k in dict.keys() { + if !keys.contains(k) { + return Err(TypeError::UnexpectedKey(debug(k).to_string())); + } + } + Ok(keys.map(|k| dict.get(k).map(|x| Term(x.mkref())))) + } + _ => Err(TypeError::WrongType("DICT")), + } + } +} + +// ---- INTERNAL IMPLS ---- + +impl<'a, 'b> AnyTerm<'a, 'b> { + fn raw(&self) -> &'a [u8] { + match self { + AnyTerm::Str(s) => s, + AnyTerm::Dict(r, _) + | AnyTerm::DictRef(r, _) + | AnyTerm::List(r, _) + | AnyTerm::ListRef(r, _) => r, + } + } + + fn mkref(&self) -> AnyTerm<'a, '_> { + match &self { + AnyTerm::Str(s) => AnyTerm::Str(s), + AnyTerm::Dict(r, d) => AnyTerm::DictRef(r, &d), + AnyTerm::DictRef(r, d) => AnyTerm::DictRef(r, d), + AnyTerm::List(r, l) => AnyTerm::ListRef(r, &l[..]), + AnyTerm::ListRef(r, l) => AnyTerm::ListRef(r, l), + } + } +} + +impl<'a, 'b> NonListTerm<'a, 'b> { + fn raw(&self) -> &'a [u8] { + match &self { + NonListTerm::Str(s) => s, + NonListTerm::Dict(r, _) | NonListTerm::DictRef(r, _) => r, + } + } + + fn mkref(&self) -> NonListTerm<'a, '_> { + match &self { + NonListTerm::Str(s) => NonListTerm::Str(s), + NonListTerm::Dict(r, d) => NonListTerm::DictRef(r, &d), + NonListTerm::DictRef(r, d) => NonListTerm::DictRef(r, d), + } + } +} + +// ---- DISPLAY REPR = Raw NetText representation ---- + +impl<'a, 'b> std::fmt::Display for AnyTerm<'a, 'b> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + write!( + f, + "{}", + std::str::from_utf8(self.raw()).map_err(|_| Default::default())? + ) + } +} + +impl<'a, 'b> std::fmt::Display for Term<'a, 'b> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + write!(f, "{}", self.0) + } +} + +// ---- DEBUG REPR ---- pub fn debug<'a>(x: &'a [u8]) -> &'a str { std::str::from_utf8(x).unwrap_or("") } -impl<'a> std::fmt::Debug for Term<'a> { +impl<'a, 'b> std::fmt::Debug for AnyTerm<'a, 'b> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - match self { - Term::Str(s) => write!(f, "Str(`{}`)", debug(s)), - Term::Dict(raw, d) => { + match self.mkref() { + AnyTerm::Str(s) => write!(f, "Str(`{}`)", debug(s)), + AnyTerm::DictRef(raw, d) => { write!(f, "Dict<`{}`", debug(raw))?; for (k, v) in d.iter() { write!(f, "\n `{}`={:?}", debug(k), v)?; } write!(f, ">") } - Term::List(raw, l) => { + AnyTerm::ListRef(raw, l) => { write!(f, "List[`{}`", debug(raw))?; for i in l.iter() { write!(f, "\n {:?}", i)?; } write!(f, "]") } + _ => unreachable!(), } } } -impl<'a> std::fmt::Debug for NonListTerm<'a> { +impl<'a, 'b> std::fmt::Debug for NonListTerm<'a, 'b> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - match self { + match self.mkref() { NonListTerm::Str(s) => write!(f, "Str(`{}`)", debug(s)), - NonListTerm::Dict(raw, d) => { + NonListTerm::DictRef(raw, d) => { write!(f, "Dict<`{}`", debug(raw))?; for (k, v) in d.iter() { write!(f, "\n `{}`={:?}", debug(k), v)?; } write!(f, ">") } + _ => unreachable!(), } } }