diff --git a/proptest-regressions/parser/syntax.txt b/proptest-regressions/parser/syntax.txt deleted file mode 100644 index def093d905..0000000000 --- a/proptest-regressions/parser/syntax.txt +++ /dev/null @@ -1,7 +0,0 @@ -# Seeds for failure cases proptest has generated in the past. It is -# automatically read and these particular cases re-run before any -# novel cases are generated. -# -# It is recommended to check this file in to source control so that -# everyone who runs the test benefits from these saved cases. -cc 72dda53aa591d94f5466c82b7f57c179c5b7e39a1fa3d88058bbe1fc5b05a501 # shrinks to x = Quote(No, List(No, [Path(No, ["."], false)])) diff --git a/src/cli/zstore.rs b/src/cli/zstore.rs index 2cff87f023..0db6b414f1 100644 --- a/src/cli/zstore.rs +++ b/src/cli/zstore.rs @@ -153,7 +153,7 @@ impl ZDag { } /// Populates a `ZDag` with data from self - pub(crate) fn populate_z_dag( + fn populate_z_dag( &self, z_ptr: &ZPtr, z_dag: &mut ZDag, @@ -258,3 +258,105 @@ impl ZStore { self.z_dag.populate_store(z_ptr, store, cache) } } + +#[cfg(test)] +mod tests { + use pasta_curves::Fp; + use rand::{rngs::StdRng, Rng}; + use rand_core::SeedableRng; + use rayon::prelude::{IntoParallelIterator, ParallelIterator}; + use std::collections::HashMap; + + use crate::{ + field::LurkField, + lem::{pointers::Ptr, store::Store, Tag}, + tag::{ContTag, ExprTag, Op1, Op2}, + }; + + use super::{ZDag, ZStore}; + + /// helper function that interns random data into a store + fn rng_interner(rng: &mut StdRng, max_depth: usize, store: &Store) -> Ptr { + let rnd = rng.gen::(); + let tag = match rnd % 4 { + 0 => Tag::Expr(ExprTag::try_from((rnd % 11) as u16).unwrap()), + 1 => Tag::Cont(ContTag::try_from((rnd % 17) as u16 + 4096).unwrap()), + 2 => Tag::Op1(Op1::try_from((rnd % 12) as u16 + 8192).unwrap()), + 3 => Tag::Op2(Op2::try_from((rnd % 16) as u16 + 12288).unwrap()), + _ => unreachable!(), + }; + if max_depth == 0 { + Ptr::Atom(tag, Fp::from_u64(rnd)) + } else { + match rnd % 4 { + 0 => Ptr::Atom(tag, Fp::from_u64(rnd)), + 1 => store.intern_2_ptrs( + tag, + rng_interner(rng, max_depth - 1, store), + rng_interner(rng, max_depth - 1, store), + ), + 2 => store.intern_3_ptrs( + tag, + rng_interner(rng, max_depth - 1, store), + rng_interner(rng, max_depth - 1, store), + rng_interner(rng, max_depth - 1, store), + ), + 3 => store.intern_4_ptrs( + tag, + rng_interner(rng, max_depth - 1, store), + rng_interner(rng, max_depth - 1, store), + rng_interner(rng, max_depth - 1, store), + rng_interner(rng, max_depth - 1, store), + ), + _ => unreachable!(), + } + } + } + + #[test] + fn test_z_store_roundtrip() { + const NUM_TESTS: u64 = 192; + const MAX_DEPTH: usize = 10; + + (0..NUM_TESTS).into_par_iter().for_each(|seed| { + let mut rng = StdRng::seed_from_u64(seed); + let store1 = Store::default(); + let ptr1 = rng_interner(&mut rng, MAX_DEPTH, &store1); + + let mut z_store = ZStore::default(); + let mut cache_into = HashMap::default(); + let z_ptr = z_store.populate_with(&ptr1, &store1, &mut cache_into); + + let mut cache_from = HashMap::default(); + let store2 = Store::default(); + let ptr2 = z_store + .populate_store(&z_ptr, &store2, &mut cache_from) + .unwrap(); + + assert_eq!(store1.hash_ptr(&ptr1), store2.hash_ptr(&ptr2)) + }); + } + + #[test] + fn test_filtered_dag() { + let store = Store::::default(); + let one = Ptr::num_u64(1); + let two = Ptr::num_u64(2); + let thr = Ptr::num_u64(3); + let one_two = store.cons(one, two); + let two_thr = store.cons(two, thr); + let mut z_dag = ZDag::default(); + let mut cache = HashMap::default(); + z_dag.populate_with(&one_two, &store, &mut cache); + z_dag.populate_with(&two_thr, &store, &mut cache); + + let z_one_two = store.hash_ptr(&one_two); + let z_two_thr = store.hash_ptr(&two_thr); + let z_dag_new = z_dag.filtered(&[&z_one_two]).unwrap(); + + // data for `z_two_thr` exists in `z_dag` + assert!(z_dag.get_type(&z_two_thr).is_some()); + // but not in `z_dag_new` + assert!(z_dag_new.get_type(&z_two_thr).is_none()); + } +} diff --git a/src/lem/store.rs b/src/lem/store.rs index 28a3747d75..9a3dc0896e 100644 --- a/src/lem/store.rs +++ b/src/lem/store.rs @@ -1061,13 +1061,16 @@ impl Ptr { mod tests { use ff::Field; use pasta_curves::pallas::Scalar as Fr; + use proptest::prelude::*; use crate::{ field::LurkField, lem::Tag, - state::initial_lurk_state, + parser::position::Pos, + state::{initial_lurk_state, lurk_sym}, + syntax::Syntax, tag::{ExprTag, Tag as TagTrait}, - Symbol, + Num, Symbol, }; use super::{Ptr, Store}; @@ -1261,4 +1264,51 @@ mod tests { let foo_bar_hash = s.hash_ptr(&foo_bar_ptr).1; assert_eq!(foo_bar_hash, foo_bar_hash_manual); } + + // helper function to test syntax interning roundtrip + fn fetch_syntax(ptr: Ptr, store: &Store) -> Syntax { + match ptr { + Ptr::Atom(Tag::Expr(ExprTag::Num), f) => Syntax::Num(Pos::No, Num::Scalar(f)), + Ptr::Atom(Tag::Expr(ExprTag::Char), f) => Syntax::Char(Pos::No, f.to_char().unwrap()), + Ptr::Atom(Tag::Expr(ExprTag::U64), f) => { + Syntax::UInt(Pos::No, crate::UInt::U64(f.to_u64_unchecked())) + } + Ptr::Atom(Tag::Expr(ExprTag::Sym), _) + | Ptr::Atom(Tag::Expr(ExprTag::Key), _) + | Ptr::Tuple2(Tag::Expr(ExprTag::Sym), _) + | Ptr::Tuple2(Tag::Expr(ExprTag::Key), _) => { + Syntax::Symbol(Pos::No, store.fetch_symbol(&ptr).unwrap().into()) + } + Ptr::Atom(Tag::Expr(ExprTag::Str), _) | Ptr::Tuple2(Tag::Expr(ExprTag::Str), _) => { + Syntax::String(Pos::No, store.fetch_string(&ptr).unwrap()) + } + Ptr::Tuple2(Tag::Expr(ExprTag::Cons), _) => { + let (elts, last) = store.fetch_list(&ptr).unwrap(); + let elts = elts + .into_iter() + .map(|e| fetch_syntax(e, store)) + .collect::>(); + if let Some(last) = last { + Syntax::Improper(Pos::No, elts, fetch_syntax(last, store).into()) + } else { + Syntax::List(Pos::No, elts) + } + } + Ptr::Tuple2(Tag::Expr(ExprTag::Nil), _) => { + Syntax::Symbol(Pos::No, lurk_sym("nil").into()) + } + _ => unreachable!(), + } + } + + proptest! { + #[test] + fn syntax_roundtrip(x in any::>()) { + let store = Store::::default(); + let ptr1 = store.intern_syntax(x); + let y = fetch_syntax(ptr1, &store); + let ptr2 = store.intern_syntax(y); + assert_eq!(ptr1, ptr2); + } + } } diff --git a/src/syntax.rs b/src/syntax.rs index dd9f0b0942..702bee900f 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -1,6 +1,5 @@ use std::fmt; -use crate::expr::Expression; use crate::field::LurkField; use crate::lurk_sym_ptr; use crate::num::Num; @@ -9,7 +8,6 @@ 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; #[cfg(not(target_arch = "wasm32"))] @@ -153,153 +151,4 @@ 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` - #[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)); - } - } - } - } - } - - 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 crate::Symbol; - use pasta_curves::pallas::Scalar as Fr; - - #[test] - fn display_syntax() { - let 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! list { - ( $( $x:expr ),* ) => { - { - let mut vec = vec!($($x,)*); - let mut tmp = lurk_sym_ptr!(s, nil); - while let Some(x) = vec.pop() { - tmp = s.cons(x, tmp); - } - tmp - } - }; - } - - macro_rules! sym { - ( $sym:ident ) => {{ - s.sym(stringify!($sym)) - }}; - } - - // Quote tests - let expr = list!(lurk_sym_ptr!(s, quote), list!(sym!(f), sym!(x), sym!(y))); - let output = s.fetch_syntax(expr).unwrap(); - assert_eq!("(.lurk.quote (.f .x .y))", &format!("{output}")); - - let expr = list!(lurk_sym_ptr!(s, quote), list!(sym!(f), sym!(x), sym!(y))); - let output = s.fetch_syntax(expr).unwrap(); - assert_eq!("(.lurk.quote (.f .x .y))", &format!("{output}")); - - let expr = list!(lurk_sym_ptr!(s, quote), sym!(f), sym!(x), sym!(y)); - let output = s.fetch_syntax(expr).unwrap(); - assert_eq!("(.lurk.quote .f .x .y)", &format!("{output}")); - - // List tests - let expr = list!(); - let output = s.fetch_syntax(expr).unwrap(); - assert_eq!(".lurk.nil", &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), lurk_sym_ptr!(s, nil)); - let output = s.fetch_syntax(expr).unwrap(); - assert_eq!("(.x .y)", &format!("{output}")); - } - - #[test] - fn syntax_rootkey_roundtrip() { - let 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()); - } - - #[test] - fn syntax_empty_keyword_roundtrip() { - let 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 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/z_data/z_expr.rs b/src/z_data/z_expr.rs index 632c7e3342..2bdbce9305 100644 --- a/src/z_data/z_expr.rs +++ b/src/z_data/z_expr.rs @@ -206,50 +206,3 @@ impl Arbitrary for ZExpr { .boxed() } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::syntax::Syntax; - use pasta_curves::pallas::Scalar; - - proptest! { - #[test] - // TODO: Overflows stack in non-release mode - fn prop_expr_z_expr_roundtrip(x in any::>()) { - let store = Store::::default(); - let ptr = store.intern_syntax(x); - let expr = store.fetch(&ptr).unwrap(); - - let z_ptr = store.hash_expr(&ptr).unwrap(); - let ptr_new = store.fetch_z_expr_ptr(&z_ptr).unwrap(); - - assert_eq!(expr, store.fetch(&ptr_new).unwrap()); - assert_eq!(ptr, ptr_new); - } - } - - #[test] - fn unit_expr_z_expr() { - let store = Store::::default(); - let x = "(+ 1 1)"; - let ptr = store.read(x).unwrap(); - let expr = store.fetch(&ptr).unwrap(); - let z_expr = ZExpr::from_ptr(&store, &ptr).unwrap(); - let z_ptr = ZExpr::z_ptr(&z_expr, &PoseidonCache::default()); - store.z_expr_ptr_map.insert(z_ptr, Box::new(ptr)); - let ptr = store.fetch_z_expr_ptr(&z_ptr).unwrap(); - assert_eq!(expr, store.fetch(&ptr).unwrap()); - } - #[test] - fn unit_expr_z_expr_empty_string() { - let store = Store::::default(); - let ptr = store.strnil(); - let expr = store.fetch(&ptr).unwrap(); - let z_expr = ZExpr::from_ptr(&store, &ptr).unwrap(); - let z_ptr = ZExpr::z_ptr(&z_expr, &PoseidonCache::default()); - store.z_expr_ptr_map.insert(z_ptr, Box::new(ptr)); - let ptr = store.fetch_z_expr_ptr(&z_ptr).unwrap(); - assert_eq!(expr, store.fetch(&ptr).unwrap()); - } -}