///| Arbitrary and Shrink trait implementations for RLE types
///|
/// Arbitrary instance for Runs[String] - generates random run-length encoded strings
pub impl @quickcheck.Arbitrary for Runs[String] with arbitrary(
size,
random_state,
) {
let chars : Array[Char] = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', ' ',
]
let runs : Runs[String] = Runs::new()
// Generate 1 to size+1 runs
let num_runs = random_state.split().next_uint64().to_int().abs() % (size + 1) +
1
for _ in 0.. earlier chars)
pub impl @qc.Shrink for Runs[String] with shrink(runs) {
let results : Array[Runs[String]] = []
let items = runs.to_array()
let len = items.length()
// Empty case
if len == 0 {
return results.iter()
}
// Strategy 1: Try empty runs
results.push(Runs::new())
// Strategy 2: Remove one run at a time
if len > 1 {
for i = 0; i < len; i = i + 1 {
let smaller : Runs[String] = Runs::new()
for j = 0; j < len; j = j + 1 {
if i != j {
let _ = smaller.append(items[j])
}
}
results.push(smaller)
}
}
// Strategy 3: Take first half
if len > 1 {
let half = len / 2
let first_half : Runs[String] = Runs::new()
for i = 0; i < half; i = i + 1 {
let _ = first_half.append(items[i])
}
results.push(first_half)
}
// Strategy 4: Shorten individual runs
for i = 0; i < len; i = i + 1 {
let item = items[i]
let item_len = Spanning::span(item)
if item_len > 1 {
// Try with shortened run
let shortened : Runs[String] = Runs::new()
for j = 0; j < len; j = j + 1 {
if i == j {
let half = item_len / 2
if half > 0 {
match Sliceable::slice(item, start=0, end=half) {
Ok(slice) => {
let _ = shortened.append(slice)
}
Err(_) => ()
}
}
} else {
let _ = shortened.append(items[j])
}
}
if !shortened.is_empty() {
results.push(shortened)
}
}
}
results.iter()
}
///|
/// Arbitrary instance for Rle[String] - generates random cached RLE strings
pub impl @quickcheck.Arbitrary for Rle[String] with arbitrary(
size,
random_state,
) {
let runs : Runs[String] = @quickcheck.Arbitrary::arbitrary(size, random_state)
Rle::from_runs(runs)
}
///|
/// Shrink instance for Rle[String] - delegates to Runs shrinking
pub impl @qc.Shrink for Rle[String] with shrink(rle) {
@qc.Shrink::shrink(rle.to_runs()).map(Rle::from_runs)
}
///|
/// Arbitrary instance for RunPos - generates valid run positions
pub impl @quickcheck.Arbitrary for RunPos with arbitrary(size, random_state) {
let run = random_state.split().next_uint64().to_int().abs() % (size + 1)
let offset = random_state.split().next_uint64().to_int().abs() % (size + 1)
{ run, offset }
}
///|
/// Shrink instance for RunPos - shrinks both run and offset toward 0
pub impl @qc.Shrink for RunPos with shrink(pos) {
let results : Array[RunPos] = []
// Try (0, 0)
if pos.run > 0 || pos.offset > 0 {
results.push({ run: 0, offset: 0 })
}
// Shrink run
if pos.run > 0 {
results.push({ run: pos.run - 1, offset: pos.offset })
results.push({ run: pos.run / 2, offset: pos.offset })
}
// Shrink offset
if pos.offset > 0 {
results.push({ run: pos.run, offset: pos.offset - 1 })
results.push({ run: pos.run, offset: pos.offset / 2 })
}
results.iter()
}
///|
/// Arbitrary instance for PrefixSums - generates valid prefix sum arrays
pub impl @quickcheck.Arbitrary for PrefixSums with arbitrary(size, random_state) {
let num_runs = random_state.split().next_uint64().to_int().abs() % (size + 1) +
1
let spans : Array[Int] = []
let content : Array[Int] = []
let mut span_sum = 0
let mut content_sum = 0
for _ in 0.. 0 {
results.push({ spans: [], content: [] })
}
// Remove last entry
if len > 1 {
results.push({
spans: sums.spans[:len - 1].to_owned(),
content: sums.content[:len - 1].to_owned(),
})
}
// Take first half
if len > 2 {
let half = len / 2
results.push({
spans: sums.spans[:half].to_owned(),
content: sums.content[:half].to_owned(),
})
}
results.iter()
}
///|
/// Arbitrary instance for Slice[String] - generates valid string slices
pub impl @quickcheck.Arbitrary for Slice[String] with arbitrary(
size,
random_state,
) {
let chars : Array[Char] = ['a', 'b', 'c', 'd', 'e']
let len = random_state.split().next_uint64().to_int().abs() % (size + 1) + 1
let sb = StringBuilder::new()
for _ in 0.. slice.start + 1 {
results.push({ ..slice, end: slice.start + 1 })
results.push({ ..slice, end: (slice.start + slice.end) / 2 + 1 })
}
// Shrink start toward 0
if slice.start > 0 {
results.push({ ..slice, start: 0 })
}
// Shrink value (keep range valid)
if value_len > slice.end {
match Sliceable::slice(slice.value, start=0, end=slice.end) {
Ok(new_value) =>
results.push({ value: new_value, start: slice.start, end: slice.end })
Err(_) => ()
}
}
results.iter()
}