diff --git a/src/expr.rs b/src/expr.rs index 364e29946f..56319ce53f 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -28,6 +28,7 @@ pub enum Expression { Str(Ptr, Ptr), Thunk(Thunk), RootSym, + RootKey, Sym(Ptr, Ptr), Key(Ptr, Ptr), Char(char), diff --git a/src/parser/error.rs b/src/parser/error.rs index f440b268cc..6f71fa96c5 100644 --- a/src/parser/error.rs +++ b/src/parser/error.rs @@ -15,6 +15,7 @@ pub enum ParseErrorKind { ParseIntErr(ParseIntError), InvalidChar(String), Nom(ErrorKind), + InterningError(String), } impl fmt::Display for ParseErrorKind { diff --git a/src/parser/syntax.rs b/src/parser/syntax.rs index 75ec5d2f0c..25e44e6258 100644 --- a/src/parser/syntax.rs +++ b/src/parser/syntax.rs @@ -95,14 +95,21 @@ fn intern_path<'a, F: LurkField>( path: &[String], keyword: Option, ) -> ParseResult<'a, F, SymbolRef> { + use nom::Err::Failure; match keyword { - Some(keyword) => match state.borrow_mut().intern_path(&path, keyword) { + Some(keyword) => match state.borrow_mut().intern_path(path, keyword) { Ok(symbol) => Ok((upto, symbol)), - Err(e) => todo!(), + Err(e) => Err(Failure(ParseError::new( + upto, + ParseErrorKind::InterningError(format!("{e}")), + ))), }, - None => match state.borrow_mut().intern_relative_path(&path) { + None => match state.borrow_mut().intern_relative_path(path) { Ok(symbol) => Ok((upto, symbol)), - Err(e) => todo!(), + Err(e) => Err(Failure(ParseError::new( + upto, + ParseErrorKind::InterningError(format!("{e}")), + ))), }, } } @@ -168,7 +175,7 @@ pub fn parse_symbol( parse_absolute_symbol(state.clone()), parse_relative_symbol(state.clone()), ))(from)?; - Ok((upto, Syntax::Symbol(Pos::from_upto(from, upto), sym.into()))) + Ok((upto, Syntax::Symbol(Pos::from_upto(from, upto), sym))) } } @@ -298,12 +305,14 @@ pub fn parse_list( move |from: Span<'_>| { let (i, _) = tag("(")(from)?; let (i, xs) = if meta { + // parse the head symbol in the meta package let saved_package = state.borrow().get_current_package_name().clone(); state .borrow_mut() .set_current_package(meta_package_symbol().into()) .expect("meta package is available"); let (i, h) = preceded(parse_space, parse_symbol(state.clone()))(i)?; + // then recover the previous package state .borrow_mut() .set_current_package(saved_package) diff --git a/src/store.rs b/src/store.rs index 96a294fab2..e1b7a9ed76 100644 --- a/src/store.rs +++ b/src/store.rs @@ -703,17 +703,22 @@ impl Store { } pub fn fetch_symbol(&self, ptr: &Ptr) -> Option { - if ptr.tag == ExprTag::Nil { - return Some(lurk_sym("nil")); - } - let mut ptr = *ptr; - let mut path = Vec::new(); - while let Some((car, cdr)) = self.fetch_symcons(&ptr) { - let string = self.fetch_string(&car)?; - path.push(string); - ptr = cdr + match ptr.tag { + ExprTag::Nil => Some(lurk_sym("nil")), + ExprTag::Sym | ExprTag::Key => { + let is_key = ptr.tag == ExprTag::Key; + let mut ptr = *ptr; + let mut path = Vec::new(); + while let Some((car, cdr)) = self.fetch_symcons(&ptr) { + let string = self.fetch_string(&car)?; + path.push(string); + ptr = cdr + } + path.reverse(); + Some(Symbol::new_from_vec(path, is_key)) + } + _ => None, } - Some(Symbol::sym(&path.into_iter().rev().collect::>())) } pub fn fetch_strcons(&self, ptr: &Ptr) -> Option<(Ptr, Ptr)> { @@ -807,6 +812,7 @@ impl Store { ExprTag::Sym => self .fetch_symcons(ptr) .map(|(car, cdr)| Expression::Sym(car, cdr)), + ExprTag::Key if ptr.raw.is_null() => Some(Expression::RootKey), ExprTag::Key => self .fetch_symcons(ptr) .map(|(car, cdr)| Expression::Key(car, cdr)), @@ -1019,6 +1025,10 @@ impl Store { ZExpr::RootSym.z_ptr(&self.poseidon_cache), Some(ZExpr::RootSym), ), + Some(Expression::RootKey) => ( + ZExpr::RootKey.z_ptr(&self.poseidon_cache), + Some(ZExpr::RootKey), + ), Some(Expression::Sym(car, cdr)) => { let (z_car, _) = self.get_z_expr(&car, z_store)?; let (z_cdr, _) = self.get_z_expr(&cdr, z_store)?; @@ -1494,6 +1504,11 @@ impl Store { self.create_z_expr_ptr(ptr, *z_ptr.value()); Some(ptr) } + (ExprTag::Key, Some(RootSym)) => { + let ptr = self.intern_symnil(true); + self.create_z_expr_ptr(ptr, *z_ptr.value()); + Some(ptr) + } (ExprTag::Sym, Some(Sym(symcar, symcdr))) => { let symcar = self.intern_z_expr_ptr(&symcar, z_store)?; let symcdr = self.intern_z_expr_ptr(&symcdr, z_store)?; @@ -1546,7 +1561,7 @@ impl Store { Some(ptr) } _ => { - //println!("Failed to get ptr for zptr: {:?}", z_ptr); + // println!("Failed to get ptr for zptr: {:?}", z_ptr); None } } @@ -1687,9 +1702,9 @@ impl Store { } impl Expression { - pub fn is_keyword_sym(&self) -> bool { - matches!(self, Expression::Key(_, _)) - } + // pub fn is_keyword_sym(&self) -> bool { + // matches!(self, Expression::Key(_, _)) + // } pub const fn as_str(&self) -> Option<&str> { match self { @@ -1915,11 +1930,11 @@ impl ZStore { pub fn to_store_with_z_ptr(&self, z_ptr: &ZExprPtr) -> Result<(Store, Ptr), Error> { let mut store = Store::new(); - for ptr in self.expr_map.keys() { - store.intern_z_expr_ptr(ptr, self); + for z_ptr in self.expr_map.keys() { + store.intern_z_expr_ptr(z_ptr, self); } - for ptr in self.cont_map.keys() { - store.intern_z_cont_ptr(ptr, self); + for z_ptr in self.cont_map.keys() { + store.intern_z_cont_ptr(z_ptr, self); } match store.intern_z_expr_ptr(z_ptr, self) { Some(ptr_ret) => Ok((store, ptr_ret)), diff --git a/src/symbol.rs b/src/symbol.rs index 4590dc4f5a..bbc8daf2bc 100644 --- a/src/symbol.rs +++ b/src/symbol.rs @@ -79,7 +79,7 @@ impl Symbol { } } - pub fn new_of_vec(path: Vec, keyword: bool) -> Self { + pub fn new_from_vec(path: Vec, keyword: bool) -> Self { Self { path, keyword } } @@ -94,13 +94,13 @@ impl Symbol { } #[inline] - pub fn sym_of_vec(path: Vec) -> Self { - Self::new_of_vec(path, false) + pub fn sym_from_vec(path: Vec) -> Self { + Self::new_from_vec(path, false) } #[inline] - pub fn key_of_vec(path: Vec) -> Self { - Self::new_of_vec(path, true) + pub fn key_from_vec(path: Vec) -> Self { + Self::new_from_vec(path, true) } /// Creates a new Symbol with the path extended by the given vector of path segments. diff --git a/src/syntax.rs b/src/syntax.rs index afe27efdac..afb89378d7 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -1,5 +1,6 @@ use std::fmt; +use crate::expr::Expression; use crate::field::LurkField; use crate::num::Num; use crate::package::SymbolRef; @@ -7,6 +8,7 @@ use crate::parser::position::Pos; use crate::ptr::Ptr; use crate::state::lurk_sym; use crate::store::Store; +use crate::tag::ExprTag; use crate::uint::UInt; use crate::Symbol; @@ -134,153 +136,147 @@ impl Store { } } - // /// Tries to fetch a syntactic list from an expression pointer, by looping over cons cells and - // /// collecting their contents. If the ptr does not point to a cons or nil (i.e. not a list) we - // /// return None. If after traversing zero or more cons cells we hit a `nil`, we return a proper - // /// list (`Syntax::List`), otherwise an improper list (`Syntax::Improper`). If the proper list - // /// is a quotation `(quote x)`, then we return the syntactic quotation `Syntax::Quote` - // fn fetch_syntax_list(&self, mut ptr: Ptr) -> Option> { - // let mut list = vec![]; - // loop { - // match self.fetch(&ptr)? { - // Expression::Cons(car, cdr) => { - // list.push(self.fetch_syntax(car)?); - // ptr = cdr; - // } - // Expression::Nil => { - // return Some(Syntax::List(Pos::No, list)); - // } - // _ => { - // if list.is_empty() { - // return None; - // } else { - // let end = Box::new(self.fetch_syntax(ptr)?); - // return Some(Syntax::Improper(Pos::No, list, end)); - // } - // } - // } - // } - // } + /// Tries to fetch a syntactic list from an expression pointer, by looping over cons cells and + /// collecting their contents. If the ptr does not point to a cons or nil (i.e. not a list) we + /// return None. If after traversing zero or more cons cells we hit a `nil`, we return a proper + /// list (`Syntax::List`), otherwise an improper list (`Syntax::Improper`). If the proper list + /// is a quotation `(quote x)`, then we return the syntactic quotation `Syntax::Quote` + #[allow(dead_code)] + fn fetch_syntax_list(&self, mut ptr: Ptr) -> Option> { + let mut list = vec![]; + loop { + match self.fetch(&ptr)? { + Expression::Cons(car, cdr) => { + list.push(self.fetch_syntax(car)?); + ptr = cdr; + } + Expression::Nil => { + return Some(Syntax::List(Pos::No, list)); + } + _ => { + if list.is_empty() { + return None; + } else { + let end = Box::new(self.fetch_syntax(ptr)?); + return Some(Syntax::Improper(Pos::No, list, end)); + } + } + } + } + } - // pub fn fetch_syntax(&self, ptr: Ptr) -> Option> { - // let expr = self.fetch(&ptr)?; - // match expr { - // Expression::Num(f) => Some(Syntax::Num(Pos::No, f)), - // Expression::Char(_) => Some(Syntax::Char(Pos::No, self.fetch_char(&ptr)?)), - // Expression::UInt(_) => Some(Syntax::UInt(Pos::No, self.fetch_uint(&ptr)?)), - // Expression::Nil | Expression::Cons(..) => self.fetch_syntax_list(ptr), - // Expression::EmptyStr => Some(Syntax::String(Pos::No, "".to_string())), - // Expression::Str(..) => Some(Syntax::String(Pos::No, self.fetch_string(&ptr)?)), - // Expression::RootSym => Some(Syntax::Path(Pos::No, vec![], false)), - // Expression::Sym(..) => { - // let sym = self.fetch_sym(&ptr)?; - // Some(Syntax::Path(Pos::No, sym.path().to_vec(), false)) - // } - // Expression::Key(..) => { - // let sym = self.fetch_key(&ptr)?; - // Some(Syntax::Path(Pos::No, sym.path().to_vec(), true)) - // } - // Expression::Comm(..) | Expression::Thunk(..) | Expression::Fun(..) => None, - // } - // } + fn fetch_syntax(&self, ptr: Ptr) -> Option> { + match ptr.tag { + ExprTag::Num => Some(Syntax::Num(Pos::No, *self.fetch_num(&ptr)?)), + ExprTag::Char => Some(Syntax::Char(Pos::No, self.fetch_char(&ptr)?)), + ExprTag::U64 => Some(Syntax::UInt(Pos::No, self.fetch_uint(&ptr)?)), + ExprTag::Str => Some(Syntax::String(Pos::No, self.fetch_string(&ptr)?)), + ExprTag::Nil => Some(Syntax::Symbol(Pos::No, lurk_sym("nil").into())), + ExprTag::Cons => self.fetch_syntax_list(ptr), + ExprTag::Sym => Some(Syntax::Symbol(Pos::No, self.fetch_sym(&ptr)?.into())), + ExprTag::Key => Some(Syntax::Symbol(Pos::No, self.fetch_key(&ptr)?.into())), + _ => None, + } + } } -// #[cfg(test)] -// mod test { -// use super::*; -// use blstrs::Scalar as Fr; +#[cfg(test)] +mod test { + use super::*; + use blstrs::Scalar as Fr; -// #[test] -// fn display_syntax() { -// let mut s = Store::::default(); + #[test] + fn display_syntax() { + let mut s = Store::::default(); -// macro_rules! improper { -// ( $( $x:expr ),+ ) => { -// { -// let mut vec = vec!($($x,)*); -// let mut tmp = vec.pop().unwrap(); -// while let Some(x) = vec.pop() { -// tmp = s.cons(x, tmp); -// } -// tmp -// } -// }; -// } + macro_rules! improper { + ( $( $x:expr ),+ ) => { + { + let mut vec = vec!($($x,)*); + let mut tmp = vec.pop().unwrap(); + while let Some(x) = vec.pop() { + tmp = s.cons(x, tmp); + } + tmp + } + }; + } -// macro_rules! list { -// ( $( $x:expr ),* ) => { -// { -// let mut vec = vec!($($x,)*); -// let mut tmp = s.nil(); -// while let Some(x) = vec.pop() { -// tmp = s.cons(x, tmp); -// } -// tmp -// } -// }; -// } + macro_rules! list { + ( $( $x:expr ),* ) => { + { + let mut vec = vec!($($x,)*); + let mut tmp = s.nil_ptr(); + while let Some(x) = vec.pop() { + tmp = s.cons(x, tmp); + } + tmp + } + }; + } -// macro_rules! sym { -// ( $sym:ident ) => {{ -// s.sym(stringify!($sym)) -// }}; -// } + macro_rules! sym { + ( $sym:ident ) => {{ + s.sym(stringify!($sym)) + }}; + } -// // Quote tests -// let expr = list!(sym!(quote), list!(sym!(f), sym!(x), sym!(y))); -// let output = s.fetch_syntax(expr).unwrap(); -// assert_eq!("'(f x y)".to_string(), format!("{}", output)); + // Quote tests + let expr = list!(sym!(quote), list!(sym!(f), sym!(x), sym!(y))); + let output = s.fetch_syntax(expr).unwrap(); + assert_eq!("'(f x y)", &format!("{}", output)); -// let expr = list!(sym!(quote), sym!(f), sym!(x), sym!(y)); -// let output = s.fetch_syntax(expr).unwrap(); -// assert_eq!("(quote f x y)".to_string(), format!("{}", output)); + let expr = list!(sym!(quote), sym!(f), sym!(x), sym!(y)); + let output = s.fetch_syntax(expr).unwrap(); + assert_eq!("(quote f x y)", &format!("{}", output)); -// // List tests -// let expr = list!(); -// let output = s.fetch_syntax(expr).unwrap(); -// assert_eq!("nil".to_string(), format!("{}", output)); + // List tests + let expr = list!(); + let output = s.fetch_syntax(expr).unwrap(); + assert_eq!("nil", &format!("{}", output)); -// let expr = improper!(sym!(x), sym!(y), sym!(z)); -// let output = s.fetch_syntax(expr).unwrap(); -// assert_eq!("(x y . z)".to_string(), format!("{}", output)); + let expr = improper!(sym!(x), sym!(y), sym!(z)); + let output = s.fetch_syntax(expr).unwrap(); + assert_eq!("(x y . z)", &format!("{}", output)); -// let expr = improper!(sym!(x), sym!(y), sym!(nil)); -// let output = s.fetch_syntax(expr).unwrap(); -// assert_eq!("(x y)".to_string(), format!("{}", output)); -// } + let expr = improper!(sym!(x), sym!(y), sym!(nil)); + let output = s.fetch_syntax(expr).unwrap(); + assert_eq!("(x y)", &format!("{}", output)); + } -// #[test] -// fn syntax_rootkey_roundtrip() { -// let mut store1 = Store::::default(); -// let ptr1 = store1.intern_syntax(Syntax::Path(Pos::No, vec![], true)); -// let (z_store, z_ptr) = store1.to_z_store_with_ptr(&ptr1).unwrap(); -// let (store2, ptr2) = z_store.to_store_with_z_ptr(&z_ptr).unwrap(); -// let y = store2.fetch_syntax(ptr2).unwrap(); -// let ptr2 = store1.intern_syntax(y); -// assert!(store1.ptr_eq(&ptr1, &ptr2).unwrap()); -// } -// #[test] -// fn syntax_empty_keyword_roundtrip() { -// let mut store1 = Store::::default(); -// let ptr1 = store1.intern_syntax(Syntax::Path(Pos::No, vec!["".into()], true)); -// let (z_store, z_ptr) = store1.to_z_store_with_ptr(&ptr1).unwrap(); -// let (store2, ptr2) = z_store.to_store_with_z_ptr(&z_ptr).unwrap(); -// let y = store2.fetch_syntax(ptr2).unwrap(); -// let ptr2 = store1.intern_syntax(y); -// assert!(store1.ptr_eq(&ptr1, &ptr2).unwrap()); -// } + #[test] + fn syntax_rootkey_roundtrip() { + let mut store1 = Store::::default(); + let ptr1 = store1.intern_syntax(Syntax::Symbol(Pos::No, Symbol::root_key().into())); + let (z_store, z_ptr) = store1.to_z_store_with_ptr(&ptr1).unwrap(); + let (store2, ptr2) = z_store.to_store_with_z_ptr(&z_ptr).unwrap(); + let y = store2.fetch_syntax(ptr2).unwrap(); + let ptr2 = store1.intern_syntax(y); + assert!(store1.ptr_eq(&ptr1, &ptr2).unwrap()); + } -// proptest! { -// // TODO: Proptest the Store/ZStore roundtrip with two distinct syntaxes -// #[test] -// fn syntax_full_roundtrip(x in any::>()) { -// let mut store1 = Store::::default(); -// let ptr1 = store1.intern_syntax(x); -// let (z_store, z_ptr) = store1.to_z_store_with_ptr(&ptr1).unwrap(); -// let (store2, ptr2) = z_store.to_store_with_z_ptr(&z_ptr).unwrap(); -// let y = store2.fetch_syntax(ptr2).unwrap(); -// let ptr2 = store1.intern_syntax(y); -// assert!(store1.ptr_eq(&ptr1, &ptr2).unwrap()); -// } -// } -// } + #[test] + fn syntax_empty_keyword_roundtrip() { + let mut store1 = Store::::default(); + let ptr1 = store1.intern_syntax(Syntax::Symbol(Pos::No, Symbol::key(&[""]).into())); + let (z_store, z_ptr) = store1.to_z_store_with_ptr(&ptr1).unwrap(); + let (store2, ptr2) = z_store.to_store_with_z_ptr(&z_ptr).unwrap(); + let y = store2.fetch_syntax(ptr2).unwrap(); + let ptr2 = store1.intern_syntax(y); + assert!(store1.ptr_eq(&ptr1, &ptr2).unwrap()); + } + + proptest! { + // TODO: Proptest the Store/ZStore roundtrip with two distinct syntaxes + #[test] + fn syntax_full_roundtrip(x in any::>()) { + let mut store1 = Store::::default(); + let ptr1 = store1.intern_syntax(x); + let (z_store, z_ptr) = store1.to_z_store_with_ptr(&ptr1).unwrap(); + let (store2, ptr2) = z_store.to_store_with_z_ptr(&z_ptr).unwrap(); + let y = store2.fetch_syntax(ptr2).unwrap(); + let ptr2 = store1.intern_syntax(y); + assert!(store1.ptr_eq(&ptr1, &ptr2).unwrap()); + } + } +} diff --git a/src/syntax_macros.rs b/src/syntax_macros.rs index 413adbecfd..1fce7f1e94 100644 --- a/src/syntax_macros.rs +++ b/src/syntax_macros.rs @@ -46,13 +46,13 @@ macro_rules! symbol { ( [$( $x:expr ),*] ) => { { let temp_vec = vec![ $( $x.to_string() ),* ]; - $crate::syntax::Syntax::Symbol(Pos::No, $crate::symbol::Symbol::sym_of_vec(temp_vec).into()) + $crate::syntax::Syntax::Symbol(Pos::No, $crate::symbol::Symbol::sym_from_vec(temp_vec).into()) } }; ( $f:ty, [$( $x:expr ),*] ) => { { let temp_vec = vec![ $( $x.to_owned() ),* ]; - $crate::syntax::Syntax::<$f>::Symbol(Pos::No, $crate::symbol::Symbol::sym_of_vec(temp_vec).into()) + $crate::syntax::Syntax::<$f>::Symbol(Pos::No, $crate::symbol::Symbol::sym_from_vec(temp_vec).into()) } }; } @@ -62,13 +62,13 @@ macro_rules! keyword { ( [$( $x:expr ),*] ) => { { let temp_vec = vec![ $( $x.to_string() ),* ]; - $crate::syntax::Syntax::Symbol(Pos::No, $crate::symbol::Symbol::key_of_vec(temp_vec).into()) + $crate::syntax::Syntax::Symbol(Pos::No, $crate::symbol::Symbol::key_from_vec(temp_vec).into()) } }; ( $f:ty, [$( $x:expr ),*] ) => { { let temp_vec = vec![ $( $x.to_owned() ),* ]; - $crate::syntax::Syntax::<$f>::Path(Pos::No, $crate::symbol::Symbol::key_of_vec(temp_vec).into()) + $crate::syntax::Syntax::<$f>::Path(Pos::No, $crate::symbol::Symbol::key_from_vec(temp_vec).into()) } }; } diff --git a/src/writer.rs b/src/writer.rs index f7e6972ec0..9569796375 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -66,6 +66,7 @@ impl Write for Expression { match self { Nil => write!(w, "nil"), RootSym => write_symbol(w, Symbol::root_sym(), state), + RootKey => write_symbol(w, Symbol::root_key(), state), Sym(car, cdr) => { let head = store.fetch_string(car).expect("missing symbol head"); let tail = store.fetch_sym(cdr).expect("missing symbol tail"); @@ -73,7 +74,7 @@ impl Write for Expression { } Key(car, cdr) => { let head = store.fetch_string(car).expect("missing keyword head"); - let tail = store.fetch_sym(cdr).expect("missing keyword tail"); + let tail = store.fetch_key(cdr).expect("missing keyword tail"); write_symbol(w, tail.extend(&[head]), state) } EmptyStr => write!(w, "\"\""), diff --git a/src/z_data/z_expr.rs b/src/z_data/z_expr.rs index 6c29cf016f..d2555e82ec 100644 --- a/src/z_data/z_expr.rs +++ b/src/z_data/z_expr.rs @@ -31,6 +31,7 @@ pub enum ZExpr { /// A commitment, which contains an opaque value and a pointer to the hidden data in the `ZStore` Comm(F, ZExprPtr), RootSym, + RootKey, /// Contains a symbol (a list of strings) and a pointer to the tail. Sym(ZExprPtr, ZExprPtr), Key(ZExprPtr, ZExprPtr), @@ -63,6 +64,7 @@ impl std::fmt::Display for ZExpr { } ZExpr::EmptyStr => write!(f, "emptystr"), ZExpr::RootSym => write!(f, "rootsym"), + ZExpr::RootKey => write!(f, "rootkey"), ZExpr::Thunk(val, cont) => write!(f, "(thunk {} {})", val, cont), ZExpr::Fun { arg, @@ -88,6 +90,7 @@ impl ZExpr { ), ZExpr::Comm(f, x) => ZPtr(ExprTag::Comm, cache.hash3(&[*f, x.0.to_field(), x.1])), ZExpr::RootSym => ZPtr(ExprTag::Sym, F::ZERO), + ZExpr::RootKey => ZPtr(ExprTag::Key, F::ZERO), ZExpr::Sym(x, y) => ZPtr( ExprTag::Sym, cache.hash4(&[x.0.to_field(), x.1, y.0.to_field(), y.1]), diff --git a/src/z_data/z_store.rs b/src/z_data/z_store.rs index 943feb49ce..d7cf599eca 100644 --- a/src/z_data/z_store.rs +++ b/src/z_data/z_store.rs @@ -107,6 +107,7 @@ impl ZStore { ZPtr(ExprTag::Num, val) => Some(ZExpr::Num(*val)), ZPtr(ExprTag::Str, val) if *val == F::ZERO => Some(ZExpr::EmptyStr), ZPtr(ExprTag::Sym, val) if *val == F::ZERO => Some(ZExpr::RootSym), + ZPtr(ExprTag::Key, val) if *val == F::ZERO => Some(ZExpr::RootSym), _ => None, } }