Skip to content

Commit

Permalink
migrate roundtrip proptests; add a test for ZDag filtering (argumentc…
Browse files Browse the repository at this point in the history
  • Loading branch information
arthurpaulino authored Nov 15, 2023
1 parent 6ec7f3a commit aecef57
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 208 deletions.
7 changes: 0 additions & 7 deletions proptest-regressions/parser/syntax.txt

This file was deleted.

104 changes: 103 additions & 1 deletion src/cli/zstore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ impl<F: LurkField> ZDag<F> {
}

/// Populates a `ZDag` with data from self
pub(crate) fn populate_z_dag(
fn populate_z_dag(
&self,
z_ptr: &ZPtr<F>,
z_dag: &mut ZDag<F>,
Expand Down Expand Up @@ -258,3 +258,105 @@ impl<F: LurkField> ZStore<F> {
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<Fp>) -> Ptr<Fp> {
let rnd = rng.gen::<u64>();
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::<Fp>::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());
}
}
54 changes: 52 additions & 2 deletions src/lem/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1061,13 +1061,16 @@ impl<F: LurkField> Ptr<F> {
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};
Expand Down Expand Up @@ -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<Fr>, store: &Store<Fr>) -> Syntax<Fr> {
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::<Vec<_>>();
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::<Syntax<Fr>>()) {
let store = Store::<Fr>::default();
let ptr1 = store.intern_syntax(x);
let y = fetch_syntax(ptr1, &store);
let ptr2 = store.intern_syntax(y);
assert_eq!(ptr1, ptr2);
}
}
}
151 changes: 0 additions & 151 deletions src/syntax.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::fmt;

use crate::expr::Expression;
use crate::field::LurkField;
use crate::lurk_sym_ptr;
use crate::num::Num;
Expand All @@ -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"))]
Expand Down Expand Up @@ -153,153 +151,4 @@ impl<F: LurkField> Store<F> {
}
}
}

/// 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<F>) -> Option<Syntax<F>> {
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<F>) -> Option<Syntax<F>> {
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::<Fr>::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::<Fr>::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::<Fr>::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::<Syntax<Fr>>()) {
let store1 = Store::<Fr>::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());
}
}
}
Loading

0 comments on commit aecef57

Please sign in to comment.