mirror of
https://github.com/urbit/ares.git
synced 2024-11-22 06:32:47 +03:00
serialization: consolidate cue functionality, docs (#266)
* serialization: consolidate cue functionality, docs - Merged cue_bitslice into a single cue function - Handles both atom/byteslice inputs. - Improved comments for clarity on jam/cue algo. - Added a bunch of tests. - Fix parse tests - Size check - Trying to cue with too small of a stack can cause a panic * add the hoon sources for reference * remove nonsense, fix cue_pill * formatting
This commit is contained in:
parent
7f03e1a53d
commit
d37c4d991f
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -1,5 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.22.0"
|
||||
@ -1257,6 +1259,7 @@ dependencies = [
|
||||
"murmur3",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"rand 0.8.5",
|
||||
"signal-hook",
|
||||
"static_assertions",
|
||||
"sword_crypto",
|
||||
|
@ -26,6 +26,7 @@ libc = "0.2.126"
|
||||
memmap = "0.7.0"
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
rand = "0.8.5"
|
||||
signal-hook = "0.3"
|
||||
static_assertions = "1.1.0"
|
||||
|
||||
|
@ -33,7 +33,7 @@ fn main() -> io::Result<()> {
|
||||
break;
|
||||
};
|
||||
i += 1;
|
||||
input = cue(&mut stack, jammed_input);
|
||||
input = cue(&mut stack, jammed_input).unwrap();
|
||||
}
|
||||
|
||||
match now.elapsed() {
|
||||
|
@ -695,21 +695,21 @@ mod tests {
|
||||
let ans_jam = A(&mut c.stack, &ubig!(1720922644868600060465749189))
|
||||
.as_atom()
|
||||
.unwrap();
|
||||
let ans = cue(&mut c.stack, ans_jam);
|
||||
let ans = cue(&mut c.stack, ans_jam).unwrap();
|
||||
let ctx = T(&mut c.stack, &[D(0), D(97), D(0)]);
|
||||
assert_jet_door(c, jet_easy, sam, ctx, ans);
|
||||
assert_jet_door(c, jet_easy, sam.unwrap(), ctx, ans);
|
||||
|
||||
// ((easy %foo) [[1 1] "abc"])
|
||||
// [[1 1] "abc"]
|
||||
let sam_jam = A(&mut c.stack, &ubig!(3205468216717221061))
|
||||
.as_atom()
|
||||
.unwrap();
|
||||
let sam = cue(&mut c.stack, sam_jam);
|
||||
let sam = cue(&mut c.stack, sam_jam).unwrap();
|
||||
// [p=[p=1 q=1] q=[~ [p=%foo q=[p=[p=1 q=1] q="abc"]]]]
|
||||
let ans_jam = A(&mut c.stack, &ubig!(3609036366588910247778413036281029))
|
||||
.as_atom()
|
||||
.unwrap();
|
||||
let ans = cue(&mut c.stack, ans_jam);
|
||||
let ans = cue(&mut c.stack, ans_jam).unwrap();
|
||||
let ctx = T(&mut c.stack, &[D(0), D(0x6f6f66), D(0)]);
|
||||
assert_jet_door(c, jet_easy, sam, ctx, ans);
|
||||
}
|
||||
|
@ -1,29 +1,26 @@
|
||||
use crate::hamt::MutHamt;
|
||||
use crate::interpreter::Error::{self,*};
|
||||
use crate::interpreter::Error::{self, *};
|
||||
use crate::interpreter::Mote::*;
|
||||
use crate::mem::NockStack;
|
||||
use crate::noun::{Atom, Cell, D, DirectAtom, IndirectAtom, Noun};
|
||||
use crate::noun::{Atom, Cell, DirectAtom, IndirectAtom, Noun, D};
|
||||
use bitvec::prelude::{BitSlice, Lsb0};
|
||||
use either::Either::{Left, Right};
|
||||
|
||||
crate::gdb!();
|
||||
|
||||
/// Calculate the number of bits needed to represent an atom
|
||||
pub fn met0_usize(atom: Atom) -> usize {
|
||||
let atom_bitslice = atom.as_bitslice();
|
||||
match atom_bitslice.last_one() {
|
||||
Some(last_one) => last_one + 1,
|
||||
None => 0,
|
||||
}
|
||||
atom_bitslice.last_one().map_or(0, |last_one| last_one + 1)
|
||||
}
|
||||
|
||||
/// Calculate the number of bits needed to represent a u64 as a usize
|
||||
pub fn met0_u64_to_usize(x: u64) -> usize {
|
||||
let usize_bitslice = BitSlice::<u64, Lsb0>::from_element(&x);
|
||||
match usize_bitslice.last_one() {
|
||||
Some(last_one) => last_one + 1,
|
||||
None => 0,
|
||||
}
|
||||
usize_bitslice.last_one().map_or(0, |last_one| last_one + 1)
|
||||
}
|
||||
|
||||
/// Read the next bit from the bitslice and advance the cursor
|
||||
pub fn next_bit(cursor: &mut usize, slice: &BitSlice<u64, Lsb0>) -> bool {
|
||||
if (*slice).len() > *cursor {
|
||||
let res = slice[*cursor];
|
||||
@ -34,19 +31,24 @@ pub fn next_bit(cursor: &mut usize, slice: &BitSlice<u64, Lsb0>) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_n_bits<'a>(cursor: &mut usize, slice: &'a BitSlice<u64, Lsb0>, n: usize) -> &'a BitSlice<u64, Lsb0> {
|
||||
let res =
|
||||
if (slice).len() >= *cursor + n {
|
||||
&slice[*cursor..*cursor + n]
|
||||
} else if slice.len() > *cursor {
|
||||
&slice[*cursor..]
|
||||
} else {
|
||||
BitSlice::<u64, Lsb0>::empty()
|
||||
};
|
||||
/// Read the next n bits from the bitslice and advance the cursor
|
||||
pub fn next_n_bits<'a>(
|
||||
cursor: &mut usize,
|
||||
slice: &'a BitSlice<u64, Lsb0>,
|
||||
n: usize,
|
||||
) -> &'a BitSlice<u64, Lsb0> {
|
||||
let res = if (slice).len() >= *cursor + n {
|
||||
&slice[*cursor..*cursor + n]
|
||||
} else if slice.len() > *cursor {
|
||||
&slice[*cursor..]
|
||||
} else {
|
||||
BitSlice::<u64, Lsb0>::empty()
|
||||
};
|
||||
*cursor += n;
|
||||
res
|
||||
}
|
||||
|
||||
/// Get the remaining bits from the cursor position
|
||||
pub fn rest_bits(cursor: usize, slice: &BitSlice<u64, Lsb0>) -> &BitSlice<u64, Lsb0> {
|
||||
if slice.len() > cursor {
|
||||
&slice[cursor..]
|
||||
@ -55,28 +57,78 @@ pub fn rest_bits(cursor: usize, slice: &BitSlice<u64, Lsb0>) -> &BitSlice<u64, L
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: What is this function doing? I gather that it's deserializing a noun from a buffer, but I don't understand the details.
|
||||
// It seems like this is dealing with parsing arbitrarily nested structures and scalar values from a buffer.
|
||||
/// Deserialize a noun from a BitSlice
|
||||
///
|
||||
/// This function implements the inverse of jam, unpacking a serialized noun.
|
||||
///
|
||||
/// Corresponds to `++cue` in the hoon stdlib, but uses a stack-based approach instead of recursion:
|
||||
///
|
||||
/// ```hoon
|
||||
/// ++ cue :: unpack
|
||||
/// ~/ %cue
|
||||
/// |= a=@
|
||||
/// ^- *
|
||||
/// =+ b=0
|
||||
/// =+ m=`(map @ *)`~
|
||||
/// =< q
|
||||
/// |- ^- [p=@ q=* r=(map @ *)]
|
||||
/// ?: =(0 (cut 0 [b 1] a))
|
||||
/// =+ c=(rub +(b) a)
|
||||
/// [+(p.c) q.c (~(put by m) b q.c)]
|
||||
/// =+ c=(add 2 b)
|
||||
/// ?: =(0 (cut 0 [+(b) 1] a))
|
||||
/// =+ u=$(b c)
|
||||
/// =+ v=$(b (add p.u c), m r.u)
|
||||
/// =+ w=[q.u q.v]
|
||||
/// [(add 2 (add p.u p.v)) w (~(put by r.v) b w)]
|
||||
/// =+ d=(rub c a)
|
||||
/// [(add 2 p.d) (need (~(get by m) q.d)) m]
|
||||
/// ```
|
||||
///
|
||||
/// The deserialization process works as follows:
|
||||
/// - 0 bit: Indicates an atom follows
|
||||
/// - 10 bits: Indicates a cell follows
|
||||
/// - 11 bits: Indicates a backreference follows
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `stack` - A mutable reference to the NockStack
|
||||
/// * `buffer` - A reference to a BitSlice containing the serialized noun
|
||||
///
|
||||
/// # Returns
|
||||
/// A Result containing either the deserialized Noun or an Error
|
||||
pub fn cue_bitslice(stack: &mut NockStack, buffer: &BitSlice<u64, Lsb0>) -> Result<Noun, Error> {
|
||||
let backref_map = MutHamt::<Noun>::new(stack);
|
||||
let mut result = D(0);
|
||||
let mut cursor = 0;
|
||||
|
||||
let stack_size = stack.size();
|
||||
let input_size = buffer.len();
|
||||
if stack_size < input_size {
|
||||
eprintln!("stack too small: {} < {}", stack_size, input_size);
|
||||
return Err(Error::NonDeterministic(Fail, D(0)));
|
||||
}
|
||||
|
||||
unsafe {
|
||||
stack.with_frame(0, |stack: &mut NockStack| {
|
||||
// TODO: Pushing initial noun onto the stack to be used as a destination pointer? Why?
|
||||
*(stack.push::<*mut Noun>()) = &mut result as *mut Noun;
|
||||
loop {
|
||||
if stack.stack_is_empty() {
|
||||
break Ok(result);
|
||||
};
|
||||
// We capture the destination pointer and then pop it off the stack.
|
||||
}
|
||||
// Capture the destination pointer and pop it off the stack
|
||||
let dest_ptr: *mut Noun = *(stack.top::<*mut Noun>());
|
||||
stack.pop::<*mut Noun>();
|
||||
if next_bit(&mut cursor, buffer) { // 1 bit
|
||||
if next_bit(&mut cursor, buffer) { // 11 tag: backref
|
||||
let mut backref_noun = Atom::new(stack, rub_backref(&mut cursor, buffer)?).as_noun();
|
||||
*dest_ptr = backref_map.lookup(stack, &mut backref_noun).ok_or(Deterministic(Exit, D(0)))?;
|
||||
} else { // 10 tag: cell
|
||||
// 1 bit
|
||||
if next_bit(&mut cursor, buffer) {
|
||||
// 11 tag: backref
|
||||
if next_bit(&mut cursor, buffer) {
|
||||
let mut backref_noun =
|
||||
Atom::new(stack, rub_backref(&mut cursor, buffer)?).as_noun();
|
||||
*dest_ptr = backref_map
|
||||
.lookup(stack, &mut backref_noun)
|
||||
.ok_or(Deterministic(Exit, D(0)))?;
|
||||
} else {
|
||||
// 10 tag: cell
|
||||
let (cell, cell_mem_ptr) = Cell::new_raw_mut(stack);
|
||||
*dest_ptr = cell.as_noun();
|
||||
let mut backref_atom = Atom::new(stack, (cursor - 2) as u64).as_noun();
|
||||
@ -84,7 +136,8 @@ pub fn cue_bitslice(stack: &mut NockStack, buffer: &BitSlice<u64, Lsb0>) -> Resu
|
||||
*(stack.push()) = &mut (*cell_mem_ptr).tail;
|
||||
*(stack.push()) = &mut (*cell_mem_ptr).head;
|
||||
}
|
||||
} else { // 0 tag: atom
|
||||
} else {
|
||||
// 0 tag: atom
|
||||
let backref: u64 = (cursor - 1) as u64;
|
||||
*dest_ptr = rub_atom(stack, &mut cursor, buffer)?.as_noun();
|
||||
let mut backref_atom = Atom::new(stack, backref).as_noun();
|
||||
@ -95,12 +148,23 @@ pub fn cue_bitslice(stack: &mut NockStack, buffer: &BitSlice<u64, Lsb0>) -> Resu
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cue(stack: &mut NockStack, buffer: Atom) -> Result<Noun,Error> {
|
||||
/// Deserialize a noun from an Atom
|
||||
///
|
||||
/// This function is a wrapper around cue_bitslice that takes an Atom as input.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `stack` - A mutable reference to the NockStack
|
||||
/// * `buffer` - An Atom containing the serialized noun
|
||||
///
|
||||
/// # Returns
|
||||
/// A Result containing either the deserialized Noun or an Error
|
||||
pub fn cue(stack: &mut NockStack, buffer: Atom) -> Result<Noun, Error> {
|
||||
let buffer_bitslice = buffer.as_bitslice();
|
||||
cue_bitslice(stack, buffer_bitslice)
|
||||
}
|
||||
|
||||
// TODO: use first_zero() on a slice of the buffer
|
||||
/// Get the size in bits of an encoded atom or backref
|
||||
/// TODO: use first_zero() on a slice of the buffer
|
||||
fn get_size(cursor: &mut usize, buffer: &BitSlice<u64, Lsb0>) -> Result<usize, Error> {
|
||||
let buff_at_cursor = rest_bits(*cursor, buffer);
|
||||
let bitsize = buff_at_cursor
|
||||
@ -113,35 +177,57 @@ fn get_size(cursor: &mut usize, buffer: &BitSlice<u64, Lsb0>) -> Result<usize, E
|
||||
let mut size: u64 = 0;
|
||||
*cursor += bitsize + 1;
|
||||
let size_bits = next_n_bits(cursor, buffer, bitsize - 1);
|
||||
BitSlice::from_element_mut(&mut size)[0..bitsize - 1]
|
||||
.copy_from_bitslice(size_bits);
|
||||
BitSlice::from_element_mut(&mut size)[0..bitsize - 1].copy_from_bitslice(size_bits);
|
||||
Ok((size as usize) + (1 << (bitsize - 1)))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: rub_atom needs explanation. It's not clear what it's doing. It seems to be deserializing an atom from a buffer.
|
||||
fn rub_atom(stack: &mut NockStack, cursor: &mut usize, buffer: &BitSlice<u64, Lsb0>) -> Result<Atom,Error> {
|
||||
/// Length-decode an atom from the buffer
|
||||
///
|
||||
/// Corresponds to `++rub` in the hoon stdlib.
|
||||
///
|
||||
/// ```hoon
|
||||
/// ++ rub :: length-decode
|
||||
/// ~/ %rub
|
||||
/// |= [a=@ b=@]
|
||||
/// ^- [p=@ q=@]
|
||||
/// =+ ^= c
|
||||
/// =+ [c=0 m=(met 0 b)]
|
||||
/// |- ?< (gth c m)
|
||||
/// ?. =(0 (cut 0 [(add a c) 1] b))
|
||||
/// c
|
||||
/// $(c +(c))
|
||||
/// ?: =(0 c)
|
||||
/// [1 0]
|
||||
/// =+ d=(add a +(c))
|
||||
/// =+ e=(add (bex (dec c)) (cut 0 [d (dec c)] b))
|
||||
/// [(add (add c c) e) (cut 0 [(add d (dec c)) e] b)]
|
||||
/// ```
|
||||
fn rub_atom(
|
||||
stack: &mut NockStack,
|
||||
cursor: &mut usize,
|
||||
buffer: &BitSlice<u64, Lsb0>,
|
||||
) -> Result<Atom, Error> {
|
||||
let size = get_size(cursor, buffer)?;
|
||||
let bits = next_n_bits(cursor, buffer, size);
|
||||
if size == 0 {
|
||||
unsafe { Ok(DirectAtom::new_unchecked(0).as_atom()) }
|
||||
} else if size < 64 {
|
||||
// fits in a direct atom
|
||||
// Fits in a direct atom
|
||||
let mut direct_raw = 0;
|
||||
BitSlice::from_element_mut(&mut direct_raw)[0..bits.len()]
|
||||
.copy_from_bitslice(bits);
|
||||
BitSlice::from_element_mut(&mut direct_raw)[0..bits.len()].copy_from_bitslice(bits);
|
||||
unsafe { Ok(DirectAtom::new_unchecked(direct_raw).as_atom()) }
|
||||
} else {
|
||||
// need an indirect atom
|
||||
// Need an indirect atom
|
||||
let wordsize = (size + 63) >> 6;
|
||||
let (mut atom, slice) = unsafe { IndirectAtom::new_raw_mut_bitslice(stack, wordsize) }; // fast round to wordsize
|
||||
let (mut atom, slice) = unsafe { IndirectAtom::new_raw_mut_bitslice(stack, wordsize) };
|
||||
slice[0..bits.len()].copy_from_bitslice(bits);
|
||||
debug_assert!(atom.size() > 0);
|
||||
unsafe { Ok(atom.normalize_as_atom()) }
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: rub_backref needs explanation. It's not clear what it's doing. It seems to be deserializing a backreference from a buffer.
|
||||
/// Deserialize a backreference from the buffer
|
||||
fn rub_backref(cursor: &mut usize, buffer: &BitSlice<u64, Lsb0>) -> Result<u64, Error> {
|
||||
// TODO: What's size here usually?
|
||||
let size = get_size(cursor, buffer)?;
|
||||
@ -166,6 +252,11 @@ struct JamState<'a> {
|
||||
slice: &'a mut BitSlice<u64, Lsb0>,
|
||||
}
|
||||
|
||||
/// Serialize a noun into an atom
|
||||
///
|
||||
/// Corresponds to ++jam in the hoon stdlib.
|
||||
///
|
||||
/// Implements a compact encoding scheme for nouns, with backreferences for shared structures.
|
||||
pub fn jam(stack: &mut NockStack, noun: Noun) -> Atom {
|
||||
let backref_map = MutHamt::new(stack);
|
||||
let size = 8;
|
||||
@ -234,6 +325,7 @@ pub fn jam(stack: &mut NockStack, noun: Noun) -> Atom {
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize an atom into the jam state
|
||||
fn jam_atom(traversal: &mut NockStack, state: &mut JamState, atom: Atom) {
|
||||
loop {
|
||||
if state.cursor + 1 > state.slice.len() {
|
||||
@ -242,7 +334,7 @@ fn jam_atom(traversal: &mut NockStack, state: &mut JamState, atom: Atom) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
state.slice.set(state.cursor, false);
|
||||
state.slice.set(state.cursor, false); // 0 tag for atom
|
||||
state.cursor += 1;
|
||||
loop {
|
||||
if let Ok(()) = mat(traversal, state, atom) {
|
||||
@ -253,6 +345,7 @@ fn jam_atom(traversal: &mut NockStack, state: &mut JamState, atom: Atom) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize a cell into the jam state
|
||||
fn jam_cell(traversal: &mut NockStack, state: &mut JamState) {
|
||||
loop {
|
||||
if state.cursor + 2 > state.slice.len() {
|
||||
@ -261,11 +354,12 @@ fn jam_cell(traversal: &mut NockStack, state: &mut JamState) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
state.slice.set(state.cursor, true);
|
||||
state.slice.set(state.cursor + 1, false);
|
||||
state.slice.set(state.cursor, true); // 1 bit
|
||||
state.slice.set(state.cursor + 1, false); // 0 bit, forming 10 tag for cell
|
||||
state.cursor += 2;
|
||||
}
|
||||
|
||||
/// Serialize a backreference into the jam state
|
||||
fn jam_backref(traversal: &mut NockStack, state: &mut JamState, backref: u64) {
|
||||
loop {
|
||||
if state.cursor + 2 > state.slice.len() {
|
||||
@ -274,8 +368,8 @@ fn jam_backref(traversal: &mut NockStack, state: &mut JamState, backref: u64) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
state.slice.set(state.cursor, true);
|
||||
state.slice.set(state.cursor + 1, true);
|
||||
state.slice.set(state.cursor, true); // 1 bit
|
||||
state.slice.set(state.cursor + 1, true); // 1 bit, forming 11 tag for backref
|
||||
state.cursor += 2;
|
||||
let backref_atom = Atom::new(traversal, backref);
|
||||
loop {
|
||||
@ -287,6 +381,7 @@ fn jam_backref(traversal: &mut NockStack, state: &mut JamState, backref: u64) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Double the size of the atom in the jam state
|
||||
fn double_atom_size(traversal: &mut NockStack, state: &mut JamState) {
|
||||
let new_size = state.size + state.size;
|
||||
let (new_atom, new_slice) = unsafe { IndirectAtom::new_raw_mut_bitslice(traversal, new_size) };
|
||||
@ -296,7 +391,9 @@ fn double_atom_size(traversal: &mut NockStack, state: &mut JamState) {
|
||||
state.slice = new_slice;
|
||||
}
|
||||
|
||||
// INVARIANT: mat must not modify state.cursor unless it will also return `Ok(())`
|
||||
/// Encode an atom's size and value into the jam state
|
||||
///
|
||||
/// INVARIANT: mat must not modify state.cursor unless it will also return `Ok(())`
|
||||
fn mat(traversal: &mut NockStack, state: &mut JamState, atom: Atom) -> Result<(), ()> {
|
||||
let b_atom_size = met0_usize(atom);
|
||||
let b_atom_size_atom = Atom::new(traversal, b_atom_size as u64);
|
||||
@ -319,9 +416,258 @@ fn mat(traversal: &mut NockStack, state: &mut JamState, atom: Atom) -> Result<()
|
||||
.copy_from_bitslice(&b_atom_size_atom.as_bitslice()[0..c_b_size - 1]); // the atom size excepting the most significant 1 (since we know where that is from the size-of-the-size)
|
||||
state.slice[state.cursor + c_b_size + c_b_size
|
||||
..state.cursor + c_b_size + c_b_size + b_atom_size]
|
||||
.copy_from_bitslice(&atom.as_bitslice()[0..b_atom_size]); // the atom itself
|
||||
.copy_from_bitslice(&atom.as_bitslice()[0..b_atom_size]);
|
||||
state.cursor += c_b_size + c_b_size + b_atom_size;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use rand::prelude::*;
|
||||
|
||||
use super::*;
|
||||
use crate::jets::util::test::assert_noun_eq;
|
||||
use crate::mem::NockStack;
|
||||
use crate::noun::{Atom, Cell, Noun};
|
||||
use crate::persist::Persist;
|
||||
fn setup_stack() -> NockStack {
|
||||
NockStack::new(1 << 30, 0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jam_cue_atom() {
|
||||
let mut stack = setup_stack();
|
||||
let atom = Atom::new(&mut stack, 42);
|
||||
let jammed = jam(&mut stack, atom.as_noun());
|
||||
let cued = cue(&mut stack, jammed).unwrap();
|
||||
assert_noun_eq(&mut stack, cued, atom.as_noun());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jam_cue_cell() {
|
||||
let mut stack = setup_stack();
|
||||
let n1 = Atom::new(&mut stack, 1).as_noun();
|
||||
let n2 = Atom::new(&mut stack, 2).as_noun();
|
||||
let cell = Cell::new(&mut stack, n1, n2).as_noun();
|
||||
let jammed = jam(&mut stack, cell);
|
||||
let cued = cue(&mut stack, jammed).unwrap();
|
||||
assert_noun_eq(&mut stack, cued, cell);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jam_cue_nested_cell() {
|
||||
let mut stack = setup_stack();
|
||||
let n3 = Atom::new(&mut stack, 3).as_noun();
|
||||
let n4 = Atom::new(&mut stack, 4).as_noun();
|
||||
let inner_cell = Cell::new(&mut stack, n3, n4);
|
||||
let n1 = Atom::new(&mut stack, 1).as_noun();
|
||||
let outer_cell = Cell::new(&mut stack, n1, inner_cell.as_noun());
|
||||
let jammed = jam(&mut stack, outer_cell.as_noun());
|
||||
let cued = cue(&mut stack, jammed).unwrap();
|
||||
assert_noun_eq(&mut stack, cued, outer_cell.as_noun());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jam_cue_shared_structure() {
|
||||
let mut stack = setup_stack();
|
||||
let shared_atom = Atom::new(&mut stack, 42);
|
||||
let cell = Cell::new(&mut stack, shared_atom.as_noun(), shared_atom.as_noun());
|
||||
let jammed = jam(&mut stack, cell.as_noun());
|
||||
let cued = cue(&mut stack, jammed).unwrap();
|
||||
assert_noun_eq(&mut stack, cued, cell.as_noun());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jam_cue_large_atom() {
|
||||
let mut stack = setup_stack();
|
||||
let large_atom = Atom::new(&mut stack, u64::MAX);
|
||||
let jammed = jam(&mut stack, large_atom.as_noun());
|
||||
let cued = cue(&mut stack, jammed).unwrap();
|
||||
assert_noun_eq(&mut stack, cued, large_atom.as_noun());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jam_cue_empty_atom() {
|
||||
let mut stack = setup_stack();
|
||||
let empty_atom = Atom::new(&mut stack, 0);
|
||||
let jammed = jam(&mut stack, empty_atom.as_noun());
|
||||
let cued = cue(&mut stack, jammed).unwrap();
|
||||
assert_noun_eq(&mut stack, cued, empty_atom.as_noun());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jam_cue_complex_structure() {
|
||||
let mut stack = setup_stack();
|
||||
let atom1 = Atom::new(&mut stack, 1);
|
||||
let atom2 = Atom::new(&mut stack, 2);
|
||||
let cell1 = Cell::new(&mut stack, atom1.as_noun(), atom2.as_noun());
|
||||
let cell2 = Cell::new(&mut stack, cell1.as_noun(), atom2.as_noun());
|
||||
let cell3 = Cell::new(&mut stack, cell2.as_noun(), cell1.as_noun());
|
||||
let jammed = jam(&mut stack, cell3.as_noun());
|
||||
let cued = cue(&mut stack, jammed).unwrap();
|
||||
assert_noun_eq(&mut stack, cued, cell3.as_noun());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cue_invalid_input() {
|
||||
let mut stack = setup_stack();
|
||||
let invalid_atom = Atom::new(&mut stack, 0b11); // Invalid tag
|
||||
let result = cue(&mut stack, invalid_atom);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jam_cue_roundtrip_property() {
|
||||
let rng = StdRng::seed_from_u64(1);
|
||||
let depth = 9;
|
||||
println!("Testing noun with depth: {}", depth);
|
||||
|
||||
let mut stack = setup_stack();
|
||||
let mut rng_clone = rng.clone();
|
||||
let (original, total_size) = generate_deeply_nested_noun(&mut stack, depth, &mut rng_clone);
|
||||
|
||||
println!(
|
||||
"Total size of all generated nouns: {:.2} KB",
|
||||
total_size as f64 / 1024.0
|
||||
);
|
||||
println!("Original size: {:.2} KB", original.mass() as f64 / 1024.0);
|
||||
let jammed = jam(&mut stack, original.clone());
|
||||
println!(
|
||||
"Jammed size: {:.2} KB",
|
||||
jammed.as_noun().mass() as f64 / 1024.0
|
||||
);
|
||||
let cued = cue(&mut stack, jammed).unwrap();
|
||||
println!("Cued size: {:.2} KB", cued.mass() as f64 / 1024.0);
|
||||
|
||||
assert_noun_eq(&mut stack, cued, original);
|
||||
}
|
||||
|
||||
fn generate_random_noun(stack: &mut NockStack, bits: usize, rng: &mut StdRng) -> (Noun, usize) {
|
||||
const MAX_DEPTH: usize = 100; // Adjust this value as needed
|
||||
fn inner(
|
||||
stack: &mut NockStack,
|
||||
bits: usize,
|
||||
rng: &mut StdRng,
|
||||
depth: usize,
|
||||
accumulated_size: usize,
|
||||
) -> (Noun, usize) {
|
||||
let mut done = false;
|
||||
if depth >= MAX_DEPTH || stack.size() < 1024 || accumulated_size > stack.size() - 1024 {
|
||||
// println!("Done at depth and size: {} {:.2} KB", depth, accumulated_size as f64 / 1024.0);
|
||||
done = true;
|
||||
}
|
||||
|
||||
let mut result = if rng.gen_bool(0.5) || done {
|
||||
let value = rng.gen::<u64>();
|
||||
let atom = Atom::new(stack, value);
|
||||
let noun = atom.as_noun();
|
||||
(noun, accumulated_size + noun.mass())
|
||||
} else {
|
||||
let (left, left_size) = inner(stack, bits / 2, rng, depth + 1, accumulated_size);
|
||||
let (right, _) = inner(stack, bits / 2, rng, depth + 1, left_size);
|
||||
|
||||
let cell = Cell::new(stack, left, right);
|
||||
let noun = cell.as_noun();
|
||||
(noun, noun.mass())
|
||||
};
|
||||
|
||||
if unsafe { result.0.space_needed(stack) } > stack.size() {
|
||||
eprintln!(
|
||||
"Stack size exceeded with noun size {:.2} KB",
|
||||
result.0.mass() as f64 / 1024.0
|
||||
);
|
||||
unsafe {
|
||||
let top_noun = *stack.top::<Noun>();
|
||||
(top_noun, result.1)
|
||||
}
|
||||
} else {
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
inner(stack, bits, rng, 0, 0)
|
||||
}
|
||||
|
||||
fn generate_deeply_nested_noun(
|
||||
stack: &mut NockStack,
|
||||
depth: usize,
|
||||
rng: &mut StdRng,
|
||||
) -> (Noun, usize) {
|
||||
if depth == 0 {
|
||||
let (noun, size) = generate_random_noun(stack, 100, rng);
|
||||
(noun, size)
|
||||
} else {
|
||||
let (left, left_size) = generate_deeply_nested_noun(stack, depth - 1, rng);
|
||||
let (right, right_size) = generate_deeply_nested_noun(stack, depth - 1, rng);
|
||||
let cell = Cell::new(stack, left, right);
|
||||
let mut noun = cell.as_noun();
|
||||
let total_size = left_size + right_size + noun.mass();
|
||||
|
||||
if unsafe { noun.space_needed(stack) } > stack.size() {
|
||||
eprintln!(
|
||||
"Stack size exceeded at depth {} with noun size {:.2} KB",
|
||||
depth,
|
||||
noun.mass() as f64 / 1024.0
|
||||
);
|
||||
unsafe {
|
||||
let top_noun = *stack.top::<Noun>();
|
||||
(top_noun, total_size)
|
||||
}
|
||||
} else {
|
||||
// println!("Size: {:.2} KB, depth: {}", noun.mass() as f64 / 1024.0, depth);
|
||||
(noun, total_size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cue_invalid_backreference() {
|
||||
std::env::set_var("RUST_BACKTRACE", "full");
|
||||
|
||||
let mut stack = setup_stack();
|
||||
let invalid_atom = Atom::new(&mut stack, 0b11); // Invalid atom representation
|
||||
let result = cue(&mut stack, invalid_atom);
|
||||
|
||||
assert!(result.is_err());
|
||||
if let Err(e) = result {
|
||||
println!("Error: {:?}", e);
|
||||
assert!(matches!(e, Error::Deterministic(_, _)));
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_cue_nondeterministic_error() {
|
||||
let mut big_stack = NockStack::new(1 << 30, 0);
|
||||
|
||||
let mut rng = StdRng::seed_from_u64(1);
|
||||
|
||||
// Create an atom with a very large value to potentially cause overflow
|
||||
let (large_atom, _) = generate_deeply_nested_noun(&mut big_stack, 5, &mut rng);
|
||||
|
||||
// Attempt to jam and then cue the large atom in the big stack
|
||||
let jammed = jam(&mut big_stack, large_atom);
|
||||
|
||||
// make a smaller stack to try to cause a nondeterministic error
|
||||
// NOTE: if the stack is big enough to fit the jammed atom, cue panics
|
||||
let mut stack = NockStack::new(jammed.as_noun().mass() / 2 as usize, 0);
|
||||
|
||||
// Attempt to cue the jammed noun with limited stack space
|
||||
let result: Result<_, Error> = match cue(&mut stack, jammed) {
|
||||
Ok(_res) => {
|
||||
assert!(false, "Unexpected success: cue operation did not fail");
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
|
||||
// Check if we got a nondeterministic error
|
||||
println!("Result: {:?}", result);
|
||||
assert!(result.is_err());
|
||||
if let Err(e) = result {
|
||||
assert!(matches!(e, Error::NonDeterministic(_, _)));
|
||||
println!("got expected error: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user