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:
Jake Miller 2024-09-09 13:12:24 -07:00 committed by GitHub
parent 7f03e1a53d
commit d37c4d991f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 404 additions and 54 deletions

3
Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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() {

View File

@ -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);
}

View File

@ -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);
}
}
}