diff --git a/src/coprocessor/circom.rs b/src/coprocessor/circom.rs index fc09649adf..8e59e17c82 100644 --- a/src/coprocessor/circom.rs +++ b/src/coprocessor/circom.rs @@ -118,9 +118,7 @@ Then run `lurk coprocessor --name {name} <{}_FOLDER>` to instantiate a new gadge SynthesisError::Unsatisfiable })?; let output = circom_scotia::synthesize(cs, self.config.r1cs.clone(), Some(witness))?; - let num_tag = g - .get_tag(&crate::tag::ExprTag::Num) - .expect("Num tag should have been allocated"); + let num_tag = g.get_tag(&crate::tag::ExprTag::Num)?; let res = AllocatedPtr::from_parts(num_tag.clone(), output); Ok(res) diff --git a/src/coprocessor/gadgets.rs b/src/coprocessor/gadgets.rs index 6585be265c..68377d679a 100644 --- a/src/coprocessor/gadgets.rs +++ b/src/coprocessor/gadgets.rs @@ -1,16 +1,23 @@ //! Helper gadgets for synthesis -//! -//! TODO: deconstructing gadgets from `circuit_frame.rs` -use bellpepper_core::{ConstraintSystem, SynthesisError}; +use bellpepper_core::{boolean::Boolean, num::AllocatedNum, ConstraintSystem, SynthesisError}; use crate::{ - circuit::gadgets::{data::hash_poseidon, pointer::AllocatedPtr}, + circuit::gadgets::{ + constraints::{boolean_to_num, implies_equal}, + data::hash_poseidon, + pointer::AllocatedPtr, + }, field::LurkField, - lem::{circuit::GlobalAllocator, store::Store}, + lem::{ + circuit::GlobalAllocator, + pointers::{Ptr, ZPtr}, + store::Store, + }, tag::{ExprTag, Tag}, }; +/// Constructs an `AllocatedPtr` compound by two others pub fn construct_tuple2, T: Tag>( cs: CS, g: &GlobalAllocator, @@ -19,7 +26,7 @@ pub fn construct_tuple2, T: Tag>( a: &AllocatedPtr, b: &AllocatedPtr, ) -> Result, SynthesisError> { - let tag = g.get_tag_cloned(tag).expect("Tag not allocated"); + let tag = g.get_tag_cloned(tag)?; let hash = hash_poseidon( cs, @@ -35,6 +42,7 @@ pub fn construct_tuple2, T: Tag>( Ok(AllocatedPtr::from_parts(tag, hash)) } +/// Constructs an `AllocatedPtr` compound by three others pub fn construct_tuple3, T: Tag>( cs: CS, g: &GlobalAllocator, @@ -44,7 +52,7 @@ pub fn construct_tuple3, T: Tag>( b: &AllocatedPtr, c: &AllocatedPtr, ) -> Result, SynthesisError> { - let tag = g.get_tag_cloned(tag).expect("Tag not allocated"); + let tag = g.get_tag_cloned(tag)?; let hash = hash_poseidon( cs, @@ -62,6 +70,7 @@ pub fn construct_tuple3, T: Tag>( Ok(AllocatedPtr::from_parts(tag, hash)) } +/// Constructs an `AllocatedPtr` compound by four others pub fn construct_tuple4, T: Tag>( cs: CS, g: &GlobalAllocator, @@ -72,7 +81,7 @@ pub fn construct_tuple4, T: Tag>( c: &AllocatedPtr, d: &AllocatedPtr, ) -> Result, SynthesisError> { - let tag = g.get_tag_cloned(tag).expect("Tag not allocated"); + let tag = g.get_tag_cloned(tag)?; let hash = hash_poseidon( cs, @@ -92,6 +101,7 @@ pub fn construct_tuple4, T: Tag>( Ok(AllocatedPtr::from_parts(tag, hash)) } +/// Constructs a `Cons` pointer #[inline] pub fn construct_cons>( cs: CS, @@ -104,7 +114,7 @@ pub fn construct_cons>( } /// Constructs a cons-list with the provided `elts`. The terminating value defaults -/// to the `nil` allocated pointer when `last` is `None` +/// to `nil` when `last` is `None` pub fn construct_list>( cs: &mut CS, g: &GlobalAllocator, @@ -112,42 +122,426 @@ pub fn construct_list>( elts: &[&AllocatedPtr], last: Option>, ) -> Result, SynthesisError> { - let init = last.unwrap_or_else(|| { - g.get_allocated_ptr_from_ptr(&store.intern_nil(), store) - .expect("nil pointer not allocated") - }); + let init = match last { + Some(last) => last, + None => g.get_allocated_ptr_from_ptr(&store.intern_nil(), store)?, + }; elts.iter() .rev() .enumerate() .try_fold(init, |acc, (i, ptr)| { - construct_cons(cs.namespace(|| format!("Cons {i}")), g, store, ptr, &acc) + construct_cons(cs.namespace(|| format!("cons {i}")), g, store, ptr, &acc) + }) +} + +/// Retrieves the `Ptr` that corresponds to `a_ptr` by using the `Store` as the +/// hint provider +fn get_ptr( + a_ptr: &AllocatedPtr, + store: &Store, +) -> Result, SynthesisError> { + let z_ptr = ZPtr::from_parts( + Tag::from_field( + &a_ptr + .tag() + .get_value() + .ok_or_else(|| SynthesisError::AssignmentMissing)?, + ) + .ok_or_else(|| SynthesisError::Unsatisfiable)?, + a_ptr + .hash() + .get_value() + .ok_or_else(|| SynthesisError::AssignmentMissing)?, + ); + Ok(store.to_ptr(&z_ptr)) +} + +/// Deconstructs `tuple`, assumed to be a composition of two others. +/// +/// # Panics +/// Panics if the store can't deconstruct the tuple pointer +pub fn deconstruct_tuple2>( + cs: &mut CS, + store: &Store, + not_dummy: &Boolean, + tuple: &AllocatedPtr, +) -> Result<(AllocatedPtr, AllocatedPtr), SynthesisError> { + let (a, b) = if not_dummy.get_value() == Some(true) { + let idx = get_ptr(tuple, store)?.get_index2().expect("invalid Ptr"); + let (a, b) = store.expect_2_ptrs(idx); + (store.hash_ptr(a), store.hash_ptr(b)) + } else { + (ZPtr::dummy(), ZPtr::dummy()) + }; + + let a = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "a"), || a); + let b = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "b"), || b); + + let hash = hash_poseidon( + &mut cs.namespace(|| "hash"), + vec![ + a.tag().clone(), + a.hash().clone(), + b.tag().clone(), + b.hash().clone(), + ], + store.poseidon_cache.constants.c4(), + )?; + + implies_equal( + &mut cs.namespace(|| "hash equality"), + not_dummy, + tuple.hash(), + &hash, + ); + + Ok((a, b)) +} + +/// Deconstructs `tuple`, assumed to be a composition of three others. +/// +/// # Panics +/// Panics if the store can't deconstruct the tuple pointer +pub fn deconstruct_tuple3>( + cs: &mut CS, + store: &Store, + not_dummy: &Boolean, + tuple: &AllocatedPtr, +) -> Result<(AllocatedPtr, AllocatedPtr, AllocatedPtr), SynthesisError> { + let (a, b, c) = if not_dummy.get_value() == Some(true) { + let idx = get_ptr(tuple, store)?.get_index3().expect("invalid Ptr"); + let (a, b, c) = store.expect_3_ptrs(idx); + (store.hash_ptr(a), store.hash_ptr(b), store.hash_ptr(c)) + } else { + (ZPtr::dummy(), ZPtr::dummy(), ZPtr::dummy()) + }; + + let a = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "a"), || a); + let b = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "b"), || b); + let c = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "c"), || c); + + let hash = hash_poseidon( + &mut cs.namespace(|| "hash"), + vec![ + a.tag().clone(), + a.hash().clone(), + b.tag().clone(), + b.hash().clone(), + c.tag().clone(), + c.hash().clone(), + ], + store.poseidon_cache.constants.c6(), + )?; + + implies_equal( + &mut cs.namespace(|| "hash equality"), + not_dummy, + tuple.hash(), + &hash, + ); + + Ok((a, b, c)) +} + +/// Deconstructs `tuple`, assumed to be a composition of four others. +/// +/// # Panics +/// Panics if the store can't deconstruct the tuple pointer +pub fn deconstruct_tuple4>( + cs: &mut CS, + store: &Store, + not_dummy: &Boolean, + tuple: &AllocatedPtr, +) -> Result< + ( + AllocatedPtr, + AllocatedPtr, + AllocatedPtr, + AllocatedPtr, + ), + SynthesisError, +> { + let (a, b, c, d) = if not_dummy.get_value() == Some(true) { + let idx = get_ptr(tuple, store)?.get_index4().expect("invalid Ptr"); + let (a, b, c, d) = store.expect_4_ptrs(idx); + ( + store.hash_ptr(a), + store.hash_ptr(b), + store.hash_ptr(c), + store.hash_ptr(d), + ) + } else { + (ZPtr::dummy(), ZPtr::dummy(), ZPtr::dummy(), ZPtr::dummy()) + }; + + let a = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "a"), || a); + let b = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "b"), || b); + let c = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "c"), || c); + let d = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "d"), || d); + + let hash = hash_poseidon( + &mut cs.namespace(|| "hash"), + vec![ + a.tag().clone(), + a.hash().clone(), + b.tag().clone(), + b.hash().clone(), + c.tag().clone(), + c.hash().clone(), + d.tag().clone(), + d.hash().clone(), + ], + store.poseidon_cache.constants.c8(), + )?; + + implies_equal( + &mut cs.namespace(|| "hash equality"), + not_dummy, + tuple.hash(), + &hash, + ); + + Ok((a, b, c, d)) +} + +/// Deconstructs `data` with `car_cdr` semantics. +/// +/// # Panics +/// Panics if the store can't deconstruct `data` with `car_cdr` +pub fn car_cdr>( + cs: &mut CS, + g: &GlobalAllocator, + store: &Store, + not_dummy: &Boolean, + data: &AllocatedPtr, +) -> Result<(AllocatedPtr, AllocatedPtr, Boolean), SynthesisError> { + let (car, cdr) = if not_dummy.get_value() == Some(true) { + let (car, cdr) = store.car_cdr(&get_ptr(data, store)?).expect("invalid Ptr"); + (store.hash_ptr(&car), store.hash_ptr(&cdr)) + } else { + (ZPtr::dummy(), ZPtr::dummy()) + }; + + let nil = g.get_allocated_ptr_from_ptr(&store.intern_nil(), store)?; + let empty_str = g.get_allocated_ptr_from_ptr(&store.intern_string(""), store)?; + + let car = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "car"), || car); + let cdr = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "cdr"), || cdr); + + let data_is_nil = data.alloc_equal(&mut cs.namespace(|| "data is nil"), &nil)?; + + let data_is_empty_str = + data.alloc_equal(&mut cs.namespace(|| "data is empty str"), &empty_str)?; + + { + // when data is nil, we enforce that car and cdr are both nil + let not_dummy_and_data_is_nil = Boolean::and( + &mut cs.namespace(|| "not dummy and data is nil"), + not_dummy, + &data_is_nil, + )?; + + car.implies_ptr_equal( + &mut cs.namespace(|| "data is nil implies car is nil"), + ¬_dummy_and_data_is_nil, + &nil, + ); + cdr.implies_ptr_equal( + &mut cs.namespace(|| "data is nil implies cdr is nil"), + ¬_dummy_and_data_is_nil, + &nil, + ); + } + + { + // when data is the empty string, we enforce that car is nil and cdr is + // the empty string + let not_dummy_and_data_is_empty_str = Boolean::and( + &mut cs.namespace(|| "not dummy and data is empty str"), + not_dummy, + &data_is_empty_str, + )?; + + car.implies_ptr_equal( + &mut cs.namespace(|| "data is empty str implies car is nil"), + ¬_dummy_and_data_is_empty_str, + &nil, + ); + cdr.implies_ptr_equal( + &mut cs.namespace(|| "data is empty str implies cdr is empty str"), + ¬_dummy_and_data_is_empty_str, + &empty_str, + ); + } + + // data is not empty: it's not nil and it's not the empty string + let data_is_not_empty = Boolean::and( + &mut cs.namespace(|| "data is not empty"), + &data_is_nil.not(), + &data_is_empty_str.not(), + )?; + + { + // when data is not empty, we enforce hash equality + let not_dumy_and_data_is_not_empty = Boolean::and( + &mut cs.namespace(|| "not dummy and data is not empty"), + not_dummy, + &data_is_not_empty, + )?; + + let hash = hash_poseidon( + &mut cs.namespace(|| "hash"), + vec![ + car.tag().clone(), + car.hash().clone(), + cdr.tag().clone(), + cdr.hash().clone(), + ], + store.poseidon_cache.constants.c4(), + )?; + + implies_equal( + &mut cs.namespace(|| "data is not empty implies hash equality"), + ¬_dumy_and_data_is_not_empty, + data.hash(), + &hash, + ); + } + + Ok((car, cdr, data_is_not_empty)) +} + +/// Chains `car_cdr` calls `n` times, returning the accumulated `car`s, the final +/// `cdr` and the (explored) actual length (`<= n`) of the cons-like `data`. For +/// example, calling `chain_car_cdr` on "ab" with `n = 4` should return the full +/// actual length `2` of such string. But if `n` is set to `1`, it will return +/// `1` as the (explored) length. +/// +/// It can be used to deconstruct cons-lists into their elements or strings into +/// their characters. +/// +/// # Panics +/// Panics if any of the reached elements can't be deconstructed with `car_cdr` +/// +/// ``` +/// # use bellpepper_core::{boolean::Boolean, test_cs::TestConstraintSystem, ConstraintSystem}; +/// # use pasta_curves::Fq; +/// +/// use lurk::{ +/// # circuit::gadgets::pointer::AllocatedPtr, +/// # field::LurkField, +/// # lem::{circuit::GlobalAllocator, pointers::Ptr, store::Store}, +/// coprocessor::gadgets::{a_ptr_as_z_ptr, chain_car_cdr}, +/// }; +/// +/// # let mut cs = TestConstraintSystem::new(); +/// # let mut g = GlobalAllocator::default(); +/// let store = Store::::default(); +/// let nil = store.intern_nil(); +/// let z_nil = store.hash_ptr(&nil); +/// let empty_str = store.intern_string(""); +/// let z_empty_str = store.hash_ptr(&empty_str); +/// # g.new_consts_from_z_ptr(&mut cs, &z_nil); +/// # g.new_consts_from_z_ptr(&mut cs, &z_empty_str); +/// let not_dummy = Boolean::Constant(true); +/// +/// let ab = store.intern_string("ab"); +/// let z_ab = store.hash_ptr(&ab); +/// let a = Ptr::char('a'); +/// let b = Ptr::char('b'); +/// let z_a = store.hash_ptr(&a); +/// let z_b = store.hash_ptr(&b); +/// let a_ab = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "ab"), || z_ab); +/// let (cars, cdr, length) = chain_car_cdr( +/// &mut cs.namespace(|| "chain_car_cdr on ab"), +/// &g, +/// &store, +/// ¬_dummy, +/// &a_ab, +/// 4, +/// ) +/// .unwrap(); +/// assert_eq!(cars.len(), 4); +/// assert_eq!(a_ptr_as_z_ptr(&cars[0]), Some(z_a)); +/// assert_eq!(a_ptr_as_z_ptr(&cars[1]), Some(z_b)); +/// assert_eq!(a_ptr_as_z_ptr(&cars[2]), Some(z_nil)); +/// assert_eq!(a_ptr_as_z_ptr(&cars[3]), Some(z_nil)); +/// assert_eq!(a_ptr_as_z_ptr(&cdr), Some(z_empty_str)); +/// assert_eq!(length.get_value(), Some(Fq::from_u64(2))); +/// ``` +pub fn chain_car_cdr>( + cs: &mut CS, + g: &GlobalAllocator, + store: &Store, + not_dummy: &Boolean, + data: &AllocatedPtr, + n: usize, +) -> Result<(Vec>, AllocatedPtr, AllocatedNum), SynthesisError> { + let mut cars = Vec::with_capacity(n); + let mut cdr = data.clone(); + let mut length = g.get_const_cloned(F::ZERO)?; + for i in 0..n { + let (car, new_cdr, not_empty) = car_cdr( + &mut cs.namespace(|| format!("car_cdr {i}")), + g, + store, + not_dummy, + &cdr, + )?; + cars.push(car); + cdr = new_cdr; + let not_empty_num = boolean_to_num( + &mut cs.namespace(|| format!("not_empty_num {i}")), + ¬_empty, + )?; + length = length.add(&mut cs.namespace(|| format!("length {i}")), ¬_empty_num)?; + } + Ok((cars, cdr, length)) +} + +#[inline] +#[allow(dead_code)] +pub fn a_ptr_as_z_ptr( + a: &AllocatedPtr, +) -> Option> { + a.tag() + .get_value() + .and_then(|t| Tag::from_field(&t)) + .and_then(|tag| { + a.hash() + .get_value() + .map(|hash| crate::z_ptr::ZPtr::from_parts(tag, hash)) }) } #[cfg(test)] mod test { use bellpepper::util_cs::witness_cs::WitnessCS; - use bellpepper_core::ConstraintSystem; + use bellpepper_core::{boolean::Boolean, test_cs::TestConstraintSystem, ConstraintSystem}; use pasta_curves::Fq; use crate::{ - coprocessor::gadgets::{construct_tuple2, construct_tuple3, construct_tuple4}, + circuit::gadgets::pointer::AllocatedPtr, + coprocessor::gadgets::{ + car_cdr, construct_tuple2, construct_tuple3, construct_tuple4, deconstruct_tuple3, + deconstruct_tuple4, + }, + field::LurkField, lem::{circuit::GlobalAllocator, pointers::Ptr, store::Store}, - tag::{ExprTag, Tag}, + tag::ExprTag, }; - use super::construct_list; + use super::{a_ptr_as_z_ptr, chain_car_cdr, construct_list, deconstruct_tuple2}; #[test] - fn test_tuples() { + fn test_construct_tuples() { let mut cs = WitnessCS::new(); let mut g = GlobalAllocator::default(); let store = Store::::default(); let nil = store.intern_nil(); let z_nil = store.hash_ptr(&nil); let nil_tag = z_nil.tag(); - g.new_const(&mut cs, nil_tag.to_field()); - g.new_const(&mut cs, *z_nil.value()); + g.new_consts_from_z_ptr(&mut cs, &z_nil); let a_nil = g.get_allocated_ptr(nil_tag, *z_nil.value()).unwrap(); let nil2 = construct_tuple2( @@ -161,8 +555,7 @@ mod test { .unwrap(); let nil2_ptr = store.intern_2_ptrs(*nil_tag, nil, nil); let z_nil2_ptr = store.hash_ptr(&nil2_ptr); - assert_eq!(nil2.tag().get_value(), Some(z_nil2_ptr.tag_field())); - assert_eq!(nil2.hash().get_value(), Some(*z_nil2_ptr.value())); + assert_eq!(a_ptr_as_z_ptr(&nil2), Some(z_nil2_ptr)); let nil3 = construct_tuple3( &mut cs.namespace(|| "nil3"), @@ -176,8 +569,7 @@ mod test { .unwrap(); let nil3_ptr = store.intern_3_ptrs(*nil_tag, nil, nil, nil); let z_nil3_ptr = store.hash_ptr(&nil3_ptr); - assert_eq!(nil3.tag().get_value(), Some(z_nil3_ptr.tag_field())); - assert_eq!(nil3.hash().get_value(), Some(*z_nil3_ptr.value())); + assert_eq!(a_ptr_as_z_ptr(&nil3), Some(z_nil3_ptr)); let nil4 = construct_tuple4( &mut cs.namespace(|| "nil4"), @@ -192,12 +584,11 @@ mod test { .unwrap(); let nil4_ptr = store.intern_4_ptrs(*nil_tag, nil, nil, nil, nil); let z_nil4_ptr = store.hash_ptr(&nil4_ptr); - assert_eq!(nil4.tag().get_value(), Some(z_nil4_ptr.tag_field())); - assert_eq!(nil4.hash().get_value(), Some(*z_nil4_ptr.value())); + assert_eq!(a_ptr_as_z_ptr(&nil4), Some(z_nil4_ptr)); } #[test] - fn test_list() { + fn test_construct_list() { let mut cs = WitnessCS::new(); let mut g = GlobalAllocator::default(); let store = Store::::default(); @@ -205,37 +596,217 @@ mod test { let nil = store.intern_nil(); let z_one = store.hash_ptr(&one); let z_nil = store.hash_ptr(&nil); - g.new_const(&mut cs, z_nil.tag_field()); - g.new_const(&mut cs, *z_nil.value()); - g.new_const(&mut cs, z_one.tag_field()); - g.new_const(&mut cs, *z_one.value()); - g.new_const(&mut cs, ExprTag::Cons.to_field()); + g.new_consts_from_z_ptr(&mut cs, &z_nil); + g.new_consts_from_z_ptr(&mut cs, &z_one); + g.new_const_from_tag(&mut cs, &ExprTag::Cons); let a_one = g.get_allocated_ptr(z_one.tag(), *z_one.value()).unwrap(); // proper list - let a_list = construct_list( - &mut cs.namespace(|| "proper list"), + let a_list = construct_list(&mut cs, &g, &store, &[&a_one, &a_one], None).unwrap(); + let z_list = store.hash_ptr(&store.list(vec![one, one])); + assert_eq!(a_ptr_as_z_ptr(&a_list), Some(z_list)); + + // improper list + let a_list = + construct_list(&mut cs, &g, &store, &[&a_one, &a_one], Some(a_one.clone())).unwrap(); + let z_list = store.hash_ptr(&store.improper_list(vec![one, one], one)); + assert_eq!(a_ptr_as_z_ptr(&a_list), Some(z_list)); + } + + #[test] + fn deconstruct_tuples() { + let mut cs = TestConstraintSystem::new(); + let store = Store::::default(); + let nil = store.intern_nil(); + let z_nil = store.hash_ptr(&nil); + let nil_tag = *nil.tag(); + let not_dummy = Boolean::Constant(true); + + let tuple2 = store.intern_2_ptrs(nil_tag, nil, nil); + let z_tuple2 = store.hash_ptr(&tuple2); + let a_tuple2 = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "tuple2"), || z_tuple2); + let (a, b) = deconstruct_tuple2( + &mut cs.namespace(|| "deconstruct tuple2"), + &store, + ¬_dummy, + &a_tuple2, + ) + .unwrap(); + assert_eq!(a_ptr_as_z_ptr(&a), Some(z_nil)); + assert_eq!(a_ptr_as_z_ptr(&b), Some(z_nil)); + + let tuple3 = store.intern_3_ptrs(nil_tag, nil, nil, nil); + let z_tuple3 = store.hash_ptr(&tuple3); + let a_tuple3 = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "tuple3"), || z_tuple3); + let (a, b, c) = deconstruct_tuple3( + &mut cs.namespace(|| "deconstruct tuple3"), + &store, + ¬_dummy, + &a_tuple3, + ) + .unwrap(); + assert_eq!(a_ptr_as_z_ptr(&a), Some(z_nil)); + assert_eq!(a_ptr_as_z_ptr(&b), Some(z_nil)); + assert_eq!(a_ptr_as_z_ptr(&c), Some(z_nil)); + + let tuple4 = store.intern_4_ptrs(nil_tag, nil, nil, nil, nil); + let z_tuple4 = store.hash_ptr(&tuple4); + let a_tuple4 = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "tuple4"), || z_tuple4); + let (a, b, c, d) = deconstruct_tuple4( + &mut cs.namespace(|| "deconstruct tuple4"), + &store, + ¬_dummy, + &a_tuple4, + ) + .unwrap(); + assert_eq!(a_ptr_as_z_ptr(&a), Some(z_nil)); + assert_eq!(a_ptr_as_z_ptr(&b), Some(z_nil)); + assert_eq!(a_ptr_as_z_ptr(&c), Some(z_nil)); + assert_eq!(a_ptr_as_z_ptr(&d), Some(z_nil)); + + assert!(cs.is_satisfied()); + } + + #[test] + fn test_car_cdr() { + let mut cs = TestConstraintSystem::new(); + let mut g = GlobalAllocator::default(); + let store = Store::::default(); + let nil = store.intern_nil(); + let z_nil = store.hash_ptr(&nil); + let empty_str = store.intern_string(""); + let z_empty_str = store.hash_ptr(&empty_str); + let not_dummy = Boolean::Constant(true); + g.new_consts_from_z_ptr(&mut cs, &z_nil); + g.new_consts_from_z_ptr(&mut cs, &z_empty_str); + + // nil + let a_nil = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "nil"), || z_nil); + let (car, cdr, not_empty) = car_cdr( + &mut cs.namespace(|| "car_cdr of nil"), &g, &store, - &[&a_one, &a_one], - None, + ¬_dummy, + &a_nil, ) .unwrap(); - let z_list = store.hash_ptr(&store.list(vec![one, one])); - assert_eq!(a_list.tag().get_value(), Some(z_list.tag_field())); - assert_eq!(a_list.hash().get_value(), Some(*z_list.value())); + assert_eq!(a_ptr_as_z_ptr(&car), Some(z_nil)); + assert_eq!(a_ptr_as_z_ptr(&cdr), Some(z_nil)); + assert_eq!(not_empty.get_value(), Some(false)); - // improper list - let a_list = construct_list( - &mut cs.namespace(|| "improper list"), + // cons + let one = Ptr::num_u64(1); + let z_one = store.hash_ptr(&one); + let cons = store.cons(one, one); + let z_cons = store.hash_ptr(&cons); + let a_cons = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "cons"), || z_cons); + let (car, cdr, not_empty) = car_cdr( + &mut cs.namespace(|| "car_cdr of cons"), &g, &store, - &[&a_one, &a_one], - Some(a_one.clone()), + ¬_dummy, + &a_cons, ) .unwrap(); - let z_list = store.hash_ptr(&store.improper_list(vec![one, one], one)); - assert_eq!(a_list.tag().get_value(), Some(z_list.tag_field())); - assert_eq!(a_list.hash().get_value(), Some(*z_list.value())); + assert_eq!(a_ptr_as_z_ptr(&car), Some(z_one)); + assert_eq!(a_ptr_as_z_ptr(&cdr), Some(z_one)); + assert_eq!(not_empty.get_value(), Some(true)); + + // empty string + let a_empty_str = + AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "empty str"), || z_empty_str); + let (car, cdr, not_empty) = car_cdr( + &mut cs.namespace(|| "car_cdr of empty str"), + &g, + &store, + ¬_dummy, + &a_empty_str, + ) + .unwrap(); + assert_eq!(a_ptr_as_z_ptr(&car), Some(z_nil)); + assert_eq!(a_ptr_as_z_ptr(&cdr), Some(z_empty_str)); + assert_eq!(not_empty.get_value(), Some(false)); + + // non-empty string + let abc = store.intern_string("abc"); + let bc = store.intern_string("bc"); + let a = Ptr::char('a'); + let z_abc = store.hash_ptr(&abc); + let z_bc = store.hash_ptr(&bc); + let z_a = store.hash_ptr(&a); + let a_abc = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "abc"), || z_abc); + let (car, cdr, not_empty) = car_cdr( + &mut cs.namespace(|| "car_cdr of abc"), + &g, + &store, + ¬_dummy, + &a_abc, + ) + .unwrap(); + assert_eq!(a_ptr_as_z_ptr(&car), Some(z_a)); + assert_eq!(a_ptr_as_z_ptr(&cdr), Some(z_bc)); + assert_eq!(not_empty.get_value(), Some(true)); + + assert!(cs.is_satisfied()); + } + + #[test] + fn test_chain_car_cdr() { + let mut cs = TestConstraintSystem::new(); + let mut g = GlobalAllocator::default(); + let store = Store::::default(); + let nil = store.intern_nil(); + let z_nil = store.hash_ptr(&nil); + let empty_str = store.intern_string(""); + let z_empty_str = store.hash_ptr(&empty_str); + g.new_consts_from_z_ptr(&mut cs, &z_nil); + g.new_consts_from_z_ptr(&mut cs, &z_empty_str); + let not_dummy = Boolean::Constant(true); + + // string + let ab = store.intern_string("ab"); + let z_ab = store.hash_ptr(&ab); + let a = Ptr::char('a'); + let b = Ptr::char('b'); + let z_a = store.hash_ptr(&a); + let z_b = store.hash_ptr(&b); + let a_ab = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "ab"), || z_ab); + let (cars, cdr, length) = chain_car_cdr( + &mut cs.namespace(|| "chain_car_cdr on ab"), + &g, + &store, + ¬_dummy, + &a_ab, + 4, + ) + .unwrap(); + assert_eq!(cars.len(), 4); + assert_eq!(a_ptr_as_z_ptr(&cars[0]), Some(z_a)); + assert_eq!(a_ptr_as_z_ptr(&cars[1]), Some(z_b)); + assert_eq!(a_ptr_as_z_ptr(&cars[2]), Some(z_nil)); + assert_eq!(a_ptr_as_z_ptr(&cars[3]), Some(z_nil)); + assert_eq!(a_ptr_as_z_ptr(&cdr), Some(z_empty_str)); + assert_eq!(length.get_value(), Some(Fq::from_u64(2))); + + // list + let list = store.list(vec![ab, ab]); + let z_list = store.hash_ptr(&list); + let a_list = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "list"), || z_list); + let (cars, cdr, length) = chain_car_cdr( + &mut cs.namespace(|| "chain_car_cdr on list"), + &g, + &store, + ¬_dummy, + &a_list, + 4, + ) + .unwrap(); + assert_eq!(cars.len(), 4); + assert_eq!(a_ptr_as_z_ptr(&cars[0]), Some(z_ab)); + assert_eq!(a_ptr_as_z_ptr(&cars[1]), Some(z_ab)); + assert_eq!(a_ptr_as_z_ptr(&cars[2]), Some(z_nil)); + assert_eq!(a_ptr_as_z_ptr(&cars[3]), Some(z_nil)); + assert_eq!(a_ptr_as_z_ptr(&cdr), Some(z_nil)); + assert_eq!(length.get_value(), Some(Fq::from_u64(2))); } } diff --git a/src/coprocessor/mod.rs b/src/coprocessor/mod.rs index db126cac8c..e9a7b7aa18 100644 --- a/src/coprocessor/mod.rs +++ b/src/coprocessor/mod.rs @@ -187,13 +187,9 @@ pub(crate) mod test { input_env: &AllocatedPtr, input_cont: &AllocatedPtr, ) -> Result>, SynthesisError> { - let num_tag = g - .get_tag(&ExprTag::Num) - .expect("Num tag should have been allocated"); + let num_tag = g.get_tag(&ExprTag::Num)?; - let cont_err = g - .get_allocated_ptr_from_ptr(&s.cont_error(), s) - .expect("Error pointer should have been allocated"); + let cont_err = g.get_allocated_ptr_from_ptr(&s.cont_error(), s)?; let (expr, env, cont) = self.synthesize_aux(cs, input_exprs, input_env, input_cont, num_tag, &cont_err)?; @@ -262,12 +258,8 @@ pub(crate) mod test { env: &AllocatedPtr, _cont: &AllocatedPtr, ) -> Result>, SynthesisError> { - let nil = g - .get_allocated_ptr_from_ptr(&s.intern_nil(), s) - .expect("nil pointer not allocated"); - let term = g - .get_allocated_ptr_from_ptr(&s.cont_terminal(), s) - .expect("terminal pointer not allocated"); + let nil = g.get_allocated_ptr_from_ptr(&s.intern_nil(), s)?; + let term = g.get_allocated_ptr_from_ptr(&s.cont_terminal(), s)?; Ok(vec![nil, env.clone(), term]) } } diff --git a/src/coprocessor/trie/mod.rs b/src/coprocessor/trie/mod.rs index ed8d88e9fc..7c73e1ad6b 100644 --- a/src/coprocessor/trie/mod.rs +++ b/src/coprocessor/trie/mod.rs @@ -198,9 +198,7 @@ impl CoCircuit for LookupCoprocessor { &s.inverse_poseidon_cache, )?; - let comm_tag = g - .get_tag(&ExprTag::Comm) - .expect("Comm tag should have been allocated"); + let comm_tag = g.get_tag(&ExprTag::Comm)?; Ok(AllocatedPtr::from_parts( comm_tag.clone(), @@ -311,9 +309,7 @@ impl CoCircuit for InsertCoprocessor { &s.inverse_poseidon_cache, )?; - let num_tag = g - .get_tag(&ExprTag::Num) - .expect("Num tag should have been allocated"); + let num_tag = g.get_tag(&ExprTag::Num)?; Ok(AllocatedPtr::from_parts(num_tag.clone(), new_root_val)) } } diff --git a/src/lem/circuit.rs b/src/lem/circuit.rs index 0efb3dd317..7de35510b0 100644 --- a/src/lem/circuit.rs +++ b/src/lem/circuit.rs @@ -98,29 +98,44 @@ impl GlobalAllocator { } #[inline] - pub fn get_const(&self, f: F) -> Result<&AllocatedNum> { + pub fn new_const_from_tag, T: Tag>(&mut self, cs: &mut CS, tag: &T) { + self.new_const(cs, tag.to_field()); + } + + #[inline] + pub fn new_consts_from_z_ptr>(&mut self, cs: &mut CS, z_ptr: &ZPtr) { + self.new_const_from_tag(cs, z_ptr.tag()); + self.new_const(cs, *z_ptr.value()); + } + + #[inline] + pub fn get_const(&self, f: F) -> Result<&AllocatedNum, SynthesisError> { self.0 .get(&FWrap(f)) - .ok_or_else(|| anyhow!("Global allocation not found for {}", f.hex_digits())) + .ok_or_else(|| SynthesisError::AssignmentMissing) } #[inline] - fn get_const_cloned(&self, f: F) -> Result> { + pub fn get_const_cloned(&self, f: F) -> Result, SynthesisError> { self.get_const(f).cloned() } #[inline] - pub fn get_tag(&self, tag: &T) -> Result<&AllocatedNum> { + pub fn get_tag(&self, tag: &T) -> Result<&AllocatedNum, SynthesisError> { self.get_const(tag.to_field()) } #[inline] - pub fn get_tag_cloned(&self, tag: &T) -> Result> { + pub fn get_tag_cloned(&self, tag: &T) -> Result, SynthesisError> { self.get_tag(tag).cloned() } #[inline] - pub fn get_allocated_ptr(&self, tag: &T, hash: F) -> Result> { + pub fn get_allocated_ptr( + &self, + tag: &T, + hash: F, + ) -> Result, SynthesisError> { Ok(AllocatedPtr::from_parts( self.get_tag_cloned(tag)?, self.get_const_cloned(hash)?, @@ -131,7 +146,7 @@ impl GlobalAllocator { &self, ptr: &Ptr, store: &Store, - ) -> Result> { + ) -> Result, SynthesisError> { let crate::z_ptr::ZPtr(tag, hash) = store.hash_ptr(ptr); self.get_allocated_ptr(&tag, hash) } @@ -386,32 +401,31 @@ impl Block { | Op::Cons3(_, tag, _) | Op::Cons4(_, tag, _) | Op::Cast(_, tag, _) => { - g.new_const(cs, tag.to_field()); + g.new_const_from_tag(cs, tag); } Op::Lit(_, lit) => { let lit_ptr = lit.to_ptr(store); let lit_z_ptr = store.hash_ptr(&lit_ptr); - g.new_const(cs, lit_z_ptr.tag_field()); - g.new_const(cs, *lit_z_ptr.value()); + g.new_consts_from_z_ptr(cs, &lit_z_ptr); } Op::Zero(_, tag) => { - g.new_const(cs, tag.to_field()); + g.new_const_from_tag(cs, tag); g.new_const(cs, F::ZERO); } Op::Hash3Zeros(_, tag) => { - g.new_const(cs, tag.to_field()); + g.new_const_from_tag(cs, tag); g.new_const(cs, store.hash3zeros); } Op::Hash4Zeros(_, tag) => { - g.new_const(cs, tag.to_field()); + g.new_const_from_tag(cs, tag); g.new_const(cs, store.hash4zeros); } Op::Hash6Zeros(_, tag) => { - g.new_const(cs, tag.to_field()); + g.new_const_from_tag(cs, tag); g.new_const(cs, store.hash6zeros); } Op::Hash8Zeros(_, tag) => { - g.new_const(cs, tag.to_field()); + g.new_const_from_tag(cs, tag); g.new_const(cs, store.hash8zeros); } Op::Not(..) @@ -423,15 +437,15 @@ impl Block { | Op::Lt(..) | Op::Trunc(..) | Op::DivRem64(..) => { - g.new_const(cs, Num.to_field()); + g.new_const_from_tag(cs, &Num); } Op::Div(..) => { - g.new_const(cs, Num.to_field()); + g.new_const_from_tag(cs, &Num); g.new_const(cs, F::ONE); } Op::Hide(..) | Op::Open(..) => { - g.new_const(cs, Num.to_field()); - g.new_const(cs, Comm.to_field()); + g.new_const_from_tag(cs, &Num); + g.new_const_from_tag(cs, &Comm); } _ => (), } @@ -450,7 +464,7 @@ impl Block { } } Ctrl::MatchSymbol(_, cases, def) => { - g.new_const(cs, Sym.to_field()); + g.new_const_from_tag(cs, &Sym); for block in cases.values() { block.alloc_globals(cs, store, g)?; } diff --git a/src/lem/store.rs b/src/lem/store.rs index 45b0aa0797..0d21f9baf4 100644 --- a/src/lem/store.rs +++ b/src/lem/store.rs @@ -22,7 +22,6 @@ use crate::{ Tail, Terminal, Unop, }, tag::ExprTag::{Char, Comm, Cons, Cproc, Fun, Key, Nil, Num, Str, Sym, Thunk, U64}, - uint::UInt, }; use super::pointers::{Ptr, ZPtr}; @@ -62,6 +61,7 @@ pub struct Store { dehydrated: ArcSwap>>>, z_cache: FrozenMap, Box>>, + inverse_z_cache: FrozenMap, Box>>, comms: FrozenMap, Box<(F, Ptr)>>, // hash -> (secret, src) @@ -91,6 +91,7 @@ impl Default for Store { inverse_poseidon_cache: Default::default(), dehydrated: Default::default(), z_cache: Default::default(), + inverse_z_cache: Default::default(), comms: Default::default(), hash3zeros, hash4zeros, @@ -118,6 +119,7 @@ impl Store { pub fn intern_2_ptrs_hydrated(&self, tag: Tag, a: Ptr, b: Ptr, z: ZPtr) -> Ptr { let ptr = Ptr::Tuple2(tag, self.tuple2.insert_probe(Box::new((a, b))).0); self.z_cache.insert(ptr, Box::new(z)); + self.inverse_z_cache.insert(z, Box::new(ptr)); ptr } @@ -145,6 +147,7 @@ impl Store { ) -> Ptr { let ptr = Ptr::Tuple3(tag, self.tuple3.insert_probe(Box::new((a, b, c))).0); self.z_cache.insert(ptr, Box::new(z)); + self.inverse_z_cache.insert(z, Box::new(ptr)); ptr } @@ -173,6 +176,7 @@ impl Store { ) -> Ptr { let ptr = Ptr::Tuple4(tag, self.tuple4.insert_probe(Box::new((a, b, c, d))).0); self.z_cache.insert(ptr, Box::new(z)); + self.inverse_z_cache.insert(z, Box::new(ptr)); ptr } @@ -224,7 +228,7 @@ impl Store { let (car, cdr) = self.fetch_2_ptrs(idx)?; match car { Ptr::Atom(Tag::Expr(Char), c) => { - string.push(c.to_char().expect("char pointers are well formed")); + string.push(c.to_char().expect("malformed char pointer")); ptr = *cdr } _ => return None, @@ -261,7 +265,8 @@ impl Store { } } - pub fn fetch_symbol_path(&self, mut idx: usize) -> Option> { + /// Fetches a symbol path whose interning returned the provided `idx` + fn fetch_symbol_path(&self, mut idx: usize) -> Option> { let mut path = vec![]; loop { let (car, cdr) = self.fetch_2_ptrs(idx)?; @@ -497,27 +502,22 @@ impl Store { pub fn intern_syntax(&self, syn: Syntax) -> Ptr { match syn { - Syntax::Num(_, x) => Ptr::Atom(Tag::Expr(Num), x.into_scalar()), - Syntax::UInt(_, UInt::U64(x)) => Ptr::Atom(Tag::Expr(U64), x.into()), - Syntax::Char(_, x) => Ptr::Atom(Tag::Expr(Char), (x as u64).into()), - Syntax::Symbol(_, symbol) => self.intern_symbol(&symbol), + Syntax::Num(_, x) => Ptr::num(x.into_scalar()), + Syntax::UInt(_, x) => Ptr::u64(x.into()), + Syntax::Char(_, x) => Ptr::char(x), + Syntax::Symbol(_, x) => self.intern_symbol(&x), Syntax::String(_, x) => self.intern_string(&x), - Syntax::Quote(pos, x) => { - let xs = vec![Syntax::Symbol(pos, lurk_sym("quote").into()), *x]; - self.intern_syntax(Syntax::List(pos, xs)) - } - Syntax::List(_, xs) => xs.into_iter().rev().fold(self.intern_nil(), |acc, x| { - let car = self.intern_syntax(x); - self.intern_2_ptrs(Tag::Expr(Cons), car, acc) - }), - Syntax::Improper(_, xs, end) => { - xs.into_iter() - .rev() - .fold(self.intern_syntax(*end), |acc, x| { - let car = self.intern_syntax(x); - self.intern_2_ptrs(Tag::Expr(Cons), car, acc) - }) + Syntax::Quote(_, x) => self.list(vec![ + self.intern_symbol(&lurk_sym("quote")), + self.intern_syntax(*x), + ]), + Syntax::List(_, xs) => { + self.list(xs.into_iter().map(|x| self.intern_syntax(x)).collect()) } + Syntax::Improper(_, xs, y) => self.improper_list( + xs.into_iter().map(|x| self.intern_syntax(x)).collect(), + self.intern_syntax(*y), + ), } } @@ -601,6 +601,7 @@ impl Store { ]), ); self.z_cache.insert(*ptr, Box::new(z_ptr)); + self.inverse_z_cache.insert(z_ptr, Box::new(*ptr)); z_ptr } } @@ -624,6 +625,7 @@ impl Store { ]), ); self.z_cache.insert(*ptr, Box::new(z_ptr)); + self.inverse_z_cache.insert(z_ptr, Box::new(*ptr)); z_ptr } } @@ -650,6 +652,7 @@ impl Store { ]), ); self.z_cache.insert(*ptr, Box::new(z_ptr)); + self.inverse_z_cache.insert(z_ptr, Box::new(*ptr)); z_ptr } } @@ -758,57 +761,20 @@ impl Store { pub fn ptr_eq(&self, a: &Ptr, b: &Ptr) -> bool { self.hash_ptr(a) == self.hash_ptr(b) } -} -impl Ptr { - pub fn dbg_display(&self, store: &Store) -> String { - if let Some(s) = store.fetch_string(self) { - return format!("\"{s}\""); - } - if let Some(s) = store.fetch_symbol(self) { - return format!("{s}"); - } - match self { - Ptr::Atom(tag, f) => { - if let Some(x) = f.to_u64() { - format!("{tag}{x}") - } else { - format!("{tag}{f:?}") - } - } - Ptr::Tuple2(tag, x) => { - let (p1, p2) = store.fetch_2_ptrs(*x).unwrap(); - format!( - "({} {} {})", - tag, - p1.dbg_display(store), - p2.dbg_display(store) - ) - } - Ptr::Tuple3(tag, x) => { - let (p1, p2, p3) = store.fetch_3_ptrs(*x).unwrap(); - format!( - "({} {} {} {})", - tag, - p1.dbg_display(store), - p2.dbg_display(store), - p3.dbg_display(store) - ) - } - Ptr::Tuple4(tag, x) => { - let (p1, p2, p3, p4) = store.fetch_4_ptrs(*x).unwrap(); - format!( - "({} {} {} {} {})", - tag, - p1.dbg_display(store), - p2.dbg_display(store), - p3.dbg_display(store), - p4.dbg_display(store) - ) - } - } + /// Attempts to recover the `Ptr` that corresponds to `z_ptr` from + /// `inverse_z_cache`. If the mapping is not there, returns an atom pointer + /// with the same tag and value + #[inline] + pub fn to_ptr(&self, z_ptr: &ZPtr) -> Ptr { + self.inverse_z_cache + .get(z_ptr) + .cloned() + .unwrap_or_else(|| Ptr::opaque(*z_ptr)) } +} +impl Ptr { pub fn fmt_to_string(&self, store: &Store, state: &State) -> String { match self.tag() { Tag::Expr(t) => match t { diff --git a/src/parser.rs b/src/parser.rs index 8f12cff62e..1ff6f36921 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -73,538 +73,3 @@ impl Store { } } } - -//#[cfg(test)] -//mod test { -// use crate::writer::Write; -// use pasta_curves::pallas::Scalar as Fr; -// -// use super::*; -// -// #[test] -// fn read_sym() { -// let test = |input, expected: &str| { -// let mut store = Store::::default(); -// let ptr = &store.read(input).unwrap(); -// let expr = store.fetch(ptr).unwrap(); -// dbg!(&expected, &expr.as_sym_str()); -// assert_eq!(expr.as_sym_str().unwrap(), expected); -// }; -// -// test(".lurk.+", ".LURK.+"); -// test(".lurk.-", ".LURK.-"); -// test("-", ".LURK.-"); -// test("-xxx", ".LURK.-XXX"); -// test("asdf", ".LURK.ASDF"); -// test("asdf ", ".LURK.ASDF"); -// test("asdf(", ".LURK.ASDF"); -// test(" asdf", ".LURK.ASDF"); -// test(" asdf ", ".LURK.ASDF"); -// test( -// " -// asdf(", -// ".LURK.ASDF", -// ); -// test("foo-bar", ".LURK.FOO-BAR"); -// test("foo_bar", ".LURK.FOO_BAR"); -// -// test("|foo_BAR|", ".LURK.|foo_BAR|"); -// -// test(":asdf", ":ASDF"); -// test(":asdf.fdsa", ":ASDF.FDSA"); -// test("asdf.fdsa", ".LURK.ASDF.FDSA"); -// -// test( -// "|A quoted symbol: α, β, ∧, ∨, ∑|", -// ".LURK.|A quoted symbol: α, β, ∧, ∨, ∑|", -// ); -// test("|UNNECESSARY-QUOTING|", ".LURK.UNNECESSARY-QUOTING"); -// test( -// "|A quoted symbol with a dot.|", -// ".LURK.|A quoted symbol with a dot.|", -// ); -// test( -// r#"|Symbol with \|escaped pipes\| contained|"#, -// r#".LURK.|Symbol with \|escaped pipes\| contained|"#, -// ); -// -// test("|asdf|.fdsa", ".LURK.|asdf|.FDSA"); -// test("|ASDF|.fdsa", ".LURK.ASDF.FDSA"); -// test("|ASDF.FDSA|.xxx", ".LURK.|ASDF.FDSA|.XXX"); -// test("|ASDF.fdsa|", ".LURK.|ASDF.fdsa|"); -// test("|ASDF.FDSA|", ".LURK.|ASDF.FDSA|"); -// -// // TODO: Test that this is an error: "asdf:fdsa" -// } -// -// #[test] -// fn test_sym_path() { -// let test = |input, expected: Vec<&str>| { -// let mut store = Store::::default(); -// -// let ptr = &store.read(input).unwrap(); -// let expr = store.fetch(ptr).unwrap(); -// let sym = expr.as_sym().unwrap(); -// -// assert_eq!(sym.path(), &expected); -// }; -// -// test("asdf", vec!["", "LURK", "ASDF"]); -// test("asdf.fdsa", vec!["", "LURK", "ASDF", "FDSA"]); -// -// test(".asdf", vec!["", "ASDF"]); -// test(".asdf.fdsa", vec!["", "ASDF", "FDSA"]); -// test(".|APPLE.ORANGE|.pear", vec!["", "APPLE.ORANGE", "PEAR"]); -// test(".|asdf|.fdsa", vec!["", "asdf", "FDSA"]); -// -// test(".asdf.|fdsa|", vec!["", "ASDF", "fdsa"]); -// -// test(":asdf", vec!["", "ASDF"]); -// test(":asdf.|fdsa|", vec!["", "ASDF", "fdsa"]); -// -// test(".||", vec!["", ""]); -// test(".||.||", vec!["", "", ""]); -// test(":||", vec!["", ""]); -// test(":||.||", vec!["", "", ""]); -// test(".asdf.||.fdsa.||", vec!["", "ASDF", "", "FDSA", ""]); -// } -// -// #[test] -// fn symbol_in_list() { -// let mut store = Store::::default(); -// let expr = store.read("'(+)").unwrap(); -// let expr2 = store.read("'(.lurk.+)").unwrap(); -// -// assert!(store.ptr_eq(&expr, &expr2).unwrap()); -// } -// -// #[test] -// fn naked_dot_in_list() { -// let mut store = Store::::default(); -// -// let expected_error = match store.read("(.)").err().unwrap() { -// Error::Syntax(s) => s == *"Misplaced dot", -// _ => false, -// }; -// -// assert!(expected_error); -// } -// -// #[test] -// fn absolute_symbol_in_list() { -// let mut store = Store::::default(); -// -// let expr = store.read("(a .b)").unwrap(); -// let expr2 = store.read("(.b)").unwrap(); -// let a = store.read("a").unwrap(); -// let b = store.read(".b").unwrap(); -// let nil = store.nil(); -// -// let b_list = store.cons(b, nil); -// let a_b_list = store.cons(a, b_list); -// -// assert!(store.ptr_eq(&a_b_list, &expr).unwrap()); -// assert!(store.ptr_eq(&b_list, &expr2).unwrap()); -// } -// -// #[test] -// fn naked_dot() { -// let mut store = Store::::default(); -// -// let expected_error = match store.read(".").err().unwrap() { -// Error::Syntax(s) => s == *"Misplaced dot", -// _ => false, -// }; -// -// assert!(expected_error); -// } -// -// #[test] -// fn list_tail() { -// let mut store = Store::::default(); -// let expr = store.read("'(+)").unwrap(); -// let expr2 = store.read("'(.lurk.+)").unwrap(); -// -// assert!(store.ptr_eq(&expr, &expr2).unwrap()); -// } -// -// #[test] -// fn read_nil() { -// let mut store = Store::::default(); -// let expr = store.read(".lurk.nil").unwrap(); -// let expr2 = store.read("nil").unwrap(); -// assert!(expr.is_nil()); -// assert!(expr2.is_nil()); -// } -// -// #[test] -// fn read_num() { -// let test = |input, expected: u64| { -// let mut store = Store::::default(); -// let expr = store.read(input).unwrap(); -// let expected = store.intern_num(expected); -// assert!(store.ptr_eq(&expected, &expr).unwrap()); -// }; -// test("123", 123); -// test("0987654321", 987654321); -// test("123)", 123); -// test("123 ", 123); -// test("123z", 123); -// test(" 123", 123); -// test( -// " -//0987654321", -// 987654321, -// ); -// -// let test = |input, expected: Fr| { -// let mut store = Store::::default(); -// let expr = store.read(input).unwrap(); -// let expected = store.intern_num(crate::num::Num::from_scalar(expected)); -// assert!(store.ptr_eq(&expected, &expr).unwrap()); -// }; -// test("0x10", Fr::from(16)); -// test("0x22", Fr::from(34)); -// test("0x0010", Fr::from(16)); -// test("0x0022", Fr::from(34)); -// -// test("0X10", Fr::from(16)); -// test("0X22", Fr::from(34)); -// -// test("0x000e", Fr::from(14)); -// -// test( -// "0x000000000000000000000000000000000000000000000000000000000000000f", -// Fr::from(15), -// ); -// -// test("0x000F", Fr::from(15)); -// -// test( -// "0x000000000000000000000000000000000000000000000000000000000000000F", -// Fr::from(15), -// ); -// -// { -// let x = 18446744073709551615; -// assert_eq!(u64::MAX, x); -// test("18446744073709551616", Fr::from(x) + Fr::from(1)); -// } -// -// test("1230xa", Fr::from(1230)); -// -// let test = |a, b| { -// let mut store = Store::::default(); -// let a_num = store.read(a).unwrap(); -// let b_num = store.read(b).unwrap(); -// dbg!(a_num.fmt_to_string(&store), b_num.fmt_to_string(&store)); -// assert!(store.ptr_eq(&a_num, &b_num).unwrap()); -// }; -// -// test("18446744073709551616", "0x10000000000000000"); -// test("123456789123456789123", "0x6B14E9F9B0DF36A83"); -// -// // > (- 0 1) -// // [3 iterations] => 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000 -// // (format nil "~x" (mod (expt 2 256) (1+ #x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000))) -// // "1824B159ACC5056F998C4FEFECBC4FF55884B7FA0003480200000001FFFFFFFE" -// test( -// "0x10000000000000000000000000000000000000000000000000000000000000000", -// "0x1824B159ACC5056F998C4FEFECBC4FF55884B7FA0003480200000001FFFFFFFE", -// ); -// -// test( -// "-1", -// "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000", -// ); -// test( -// "1/2", -// "0x39f6d3a994cebea4199cec0404d0ec02a9ded2017fff2dff7fffffff80000001", -// ); -// test( -// "-1/2", -// "0x39f6d3a994cebea4199cec0404d0ec02a9ded2017fff2dff7fffffff80000000", -// ); -// test("0xe/2", "7"); -// test("-0xf/2", "-15/2"); -// } -// -// #[test] -// fn read_u64() { -// let test = |input, expected: u64| { -// let mut store = Store::::default(); -// let expr = store.read(input).unwrap(); -// let expected = store.get_u64(expected); -// assert!(store.ptr_eq(&expected, &expr).unwrap()); -// }; -// -// test("123u64", 123); -// test("0xffu64", 255); -// test("123U64", 123); -// test("0XFFU64", 255); -// -// // This is the largest U64. -// test("0xffffffffffffffffu64", 18446744073709551615); -// } -// -// #[test] -// fn read_u64_overflows() { -// let test = |input| { -// let mut store = Store::::default(); -// let expr = store.read(input); -// assert!(expr.is_err()); -// }; -// -// test("0xffffffffffffffffffu64"); -// test("999999999999999999999999999999999999u64"); -// } -// -// #[test] -// fn read_list() { -// let mut s = Store::::default(); -// let test = |store: &mut Store, input, expected| { -// let expr = store.read(input).unwrap(); -// assert_eq!(expected, &expr); -// }; -// -// let a = s.num(123); -// let b = s.nil(); -// let expected = s.cons(a, b); -// test(&mut s, "(123)", &expected); -// -// let a = s.num(321); -// let expected2 = s.cons(a, expected); -// test(&mut s, "(321 123)", &expected2); -// -// let a = s.sym("pumpkin"); -// let expected3 = s.cons(a, expected2); -// test(&mut s, "(pumpkin 321 123)", &expected3); -// -// let expected4 = s.cons(expected, s.get_nil()); -// test(&mut s, "((123))", &expected4); -// -// let (a, b) = (s.num(321), s.nil()); -// let alt = s.cons(a, b); -// let expected5 = s.cons(alt, expected4); -// test(&mut s, "((321) (123))", &expected5); -// -// let expected6 = s.cons(expected2, expected3); -// test(&mut s, "((321 123) pumpkin 321 123)", &expected6); -// -// let (a, b) = (s.num(1), s.num(2)); -// let pair = s.cons(a, b); -// let list = [pair, s.num(3)]; -// let expected7 = s.intern_list(&list); -// test(&mut s, "((1 . 2) 3)", &expected7); -// } -// -// #[test] -// fn read_improper_list() { -// let mut s = Store::::default(); -// let test = |store: &mut Store, input, expected| { -// let expr = store.read(input).unwrap(); -// assert_eq!(expected, &expr); -// }; -// -// let (a, b) = (s.num(123), s.num(321)); -// let expected = s.cons(a, b); -// test(&mut s, "(123 . 321)", &expected); -// -// assert_eq!( -// s.read("(123 321)").unwrap(), -// s.read("(123 . ( 321 ))").unwrap() -// ) -// } -// #[test] -// fn read_print_expr() { -// let mut s = Store::::default(); -// let test = |store: &mut Store, input| { -// let expr = store.read(input).unwrap(); -// let output = expr.fmt_to_string(store); -// assert_eq!(input, output); -// }; -// -// test(&mut s, "|α|"); -// test(&mut s, "A"); -// test(&mut s, "(A . B)"); -// test(&mut s, "(A B C)"); -// test(&mut s, "(A (B) C)"); -// test(&mut s, "(A (B . C) (D E (F)) G)"); -// // TODO: Writer should replace (quote a) with 'a. -// // test(&mut s, "'A"); -// // test(&mut s, "'(A B)"); -// } -// -// #[test] -// fn read_maybe_meta() { -// let mut s = Store::::default(); -// let package = Package::default(); -// let test = -// |store: &mut Store, input: &str, expected_ptr: Ptr, expected_meta: bool| { -// let mut chars = input.chars().peekmore(); -// -// let (ptr, meta) = store.read_maybe_meta(&mut chars, &package).unwrap(); -// { -// assert_eq!(expected_ptr, ptr); -// assert_eq!(expected_meta, meta); -// }; -// }; -// -// let num = s.num(123); -// test(&mut s, "123", num, false); -// -// { -// let list = [s.num(123), s.num(321)]; -// let l = s.list(&list); -// test(&mut s, " (123 321)", l, false); -// } -// { -// let list = [s.num(123), s.num(321)]; -// let l = s.list(&list); -// test(&mut s, " !(123 321)", l, true); -// } -// { -// let list = [s.num(123), s.num(321)]; -// let l = s.list(&list); -// test(&mut s, " ! (123 321)", l, true); -// } -// { -// let sym = s.sym("asdf"); -// test(&mut s, "!asdf", sym, true); -// } -// { -// let sym = s.sym(":assert"); -// let l = s.list(&[sym]); -// test(&mut s, "!(:assert)", l, true); -// } -// { -// let sym = s.sym("asdf"); -// test( -// &mut s, -// ";; comment -// !asdf", -// sym, -// true, -// ); -// } -// } -// #[test] -// fn is_keyword() { -// let mut s = Store::::default(); -// let kw = s.read(":uiop").unwrap(); -// let kw2 = s.sym(":UIOP"); -// let not_kw = s.sym(".UIOP"); -// -// let kw_fetched = s.fetch(&kw).unwrap(); -// let kw2_fetched = s.fetch(&kw2).unwrap(); -// let not_kw_fetched = s.fetch(¬_kw).unwrap(); -// -// assert!(kw_fetched.is_keyword_sym()); -// assert!(kw2_fetched.is_keyword_sym()); -// assert!(!not_kw_fetched.is_keyword_sym()); -// } -// -// #[test] -// fn read_string() { -// let mut s = Store::::default(); -// -// let test = -// |store: &mut Store, input: &str, expected: Option>, expr: Option<&str>| { -// let maybe_string = store.read_string(&mut input.chars().peekmore()).ok(); -// assert_eq!(expected, maybe_string); -// if let Some(ptr) = maybe_string { -// let res = store -// .fetch(&ptr) -// .unwrap_or_else(|| panic!("failed to fetch: {:?}", input)); -// assert_eq!(res.as_str(), expr); -// } -// }; -// -// { -// let str = s.intern_str("asdf"); -// test(&mut s, "\"asdf\"", Some(str), Some("asdf")); -// test(&mut s, "\"asdf", None, None); -// test(&mut s, "asdf", None, None); -// } -// { -// let input = "\"foo/bar/baz\""; -// let ptr = s.read_string(&mut input.chars().peekmore()).unwrap(); -// let res = s -// .fetch(&ptr) -// .unwrap_or_else(|| panic!("failed to fetch: {:?}", input)); -// assert_eq!(res.as_str().unwrap(), "foo/bar/baz"); -// } -// -// { -// let str = s.intern_str(r#"Bob "Bugs" Murphy"#); -// test( -// &mut s, -// r#""Bob \"Bugs\" Murphy""#, -// Some(str), -// Some(r#"Bob "Bugs" Murphy"#), -// ); -// } -// } -// -// #[test] -// fn read_write_char() { -// let s = &mut Store::::default(); -// -// let a = 'a'; -// let char = s.get_char(a); -// let input = r#"#\a"#; -// let ptr = s.read(input).unwrap(); -// let res = s.fetch(&ptr).unwrap(); -// match res { -// crate::expr::Expression::Char(c) => assert_eq!(a, c), -// _ => panic!("not a Char"), -// }; -// let printed = res.fmt_to_string(s); -// -// assert_eq!(char, ptr); -// assert_eq!(input, printed); -// } -// -// #[test] -// fn read_with_comments() { -// let mut s = Store::::default(); -// -// let test = |store: &mut Store, input: &str, expected: Option>| { -// let res = store.read(input).ok(); -// assert_eq!(expected, res); -// }; -// -// let num = s.num(321); -// test( -// &mut s, -// ";123 -//321", -// Some(num), -// ); -// } -// -// #[test] -// fn read_non_fractions() { -// let mut s = Store::::default(); -// -// let test = |store: &mut Store, a: &str, b: &str| { -// let res_a = store.read(a).unwrap(); -// let res_b = store.read(b).unwrap(); -// assert!(store.ptr_eq(&res_a, &res_b).unwrap()); -// }; -// // These tests demonstrate that '/' behaves like other arithmetic operators -// // when a fraction is not being parsed. -// -// test(&mut s, "'(1+ 2)", "'(1 + 2)"); -// test(&mut s, "'(1/ 2)", "'(1 / 2)"); -// -// test(&mut s, "'(0xa+ 2)", "'(0xa + 2)"); -// test(&mut s, "'(0xa/ 2)", "'(0xa / 2)"); -// -// test(&mut s, "1+", "1"); -// test(&mut s, "1/", "1"); -// -// test(&mut s, "0xa+", "0xa"); -// test(&mut s, "0xa/", "0xa"); -// } -//} diff --git a/src/parser/syntax.rs b/src/parser/syntax.rs index 72a427cd98..2af8f64b5e 100644 --- a/src/parser/syntax.rs +++ b/src/parser/syntax.rs @@ -433,12 +433,7 @@ pub mod tests { { match (expected, p.parse(Span::<'a>::new(i))) { (Some(expected), Ok((_, x))) if x == expected => true, - (Some(_) | None, Ok(..)) | (Some(..), Err(_)) => { - // println!("input: {:?}", i); - // println!("expected parse error"); - // println!("detected: {:?}", x); - false - } + (Some(_) | None, Ok(..)) | (Some(..), Err(_)) => false, (None, Err(_e)) => true, } } @@ -831,6 +826,7 @@ pub mod tests { assert!(test(parse_num(), "0d0", Some(num!(0)))); assert!(test(parse_num(), "0x0", Some(num!(0)))); assert!(test(parse_num(), "0xf", Some(num!(15)))); + assert!(test(parse_num(), "0xF", Some(num!(15)))); assert!(test(parse_num(), "0x0f", Some(num!(15)))); assert!(test( parse_num(),