use "random"
type _RandomnessMode is (_ModePlain | _ModeRecording | _ModeReplaying)
primitive _ModePlain
primitive _ModeRecording
primitive _ModeReplaying
class ref Randomness
"""
Draw methods are non-partial. During replay, if the recorded choice
sequence is exhausted or a type mismatch is detected, draw methods
fall back to fresh generation and the runner detects it.
Integer methods generate values in the closed interval [min, max].
Floating-point methods scale onto [min, max]; `min` is always reachable,
`max` is an approximate upper bound due to floating-point rounding.
"""
let _random: Random
var _mode: _RandomnessMode = _ModePlain
var _choices: Array[_Choice val] iso = recover iso Array[_Choice val] end
var _spans: Array[_Span val] iso = recover iso Array[_Span val] end
var _replay_seq: Array[_Choice val] val = recover val Array[_Choice val] end
var _replay_idx: USize = 0
var _replay_exhausted_flag: Bool = false
var _span_stack: Array[USize] ref = Array[USize]
var _span_labels: Array[SpanLabel] ref = Array[SpanLabel]
var _filter_discards: USize = 0
var _filter_accepts: USize = 0
new ref create(seed1: U64 = 42, seed2: U64 = 0) =>
_random = Rand(seed1, seed2)
// --- Public draw methods ---
fun ref u8(min: U8 = U8.min_value(), max: U8 = U8.max_value()): U8 =>
"""
Generate a U8 in closed interval [min, max].
"""
_draw_int(min.i128(), max.i128(), min.i128()).u8()
fun ref u16(min: U16 = U16.min_value(), max: U16 = U16.max_value()): U16 =>
"""
Generate a U16 in closed interval [min, max].
"""
_draw_int(min.i128(), max.i128(), min.i128()).u16()
fun ref u32(min: U32 = U32.min_value(), max: U32 = U32.max_value()): U32 =>
"""
Generate a U32 in closed interval [min, max].
"""
_draw_int(min.i128(), max.i128(), min.i128()).u32()
fun ref u64(min: U64 = U64.min_value(), max: U64 = U64.max_value()): U64 =>
"""
Generate a U64 in closed interval [min, max].
"""
_draw_int(min.i128(), max.i128(), min.i128()).u64()
fun ref u128(
min: U128 = U128.min_value(),
max: U128 = U128.max_value())
: U128
=>
"""
Generate a U128 in closed interval [min, max].
"""
_draw_u128(min, max, min)
fun ref ulong(
min: ULong = ULong.min_value(),
max: ULong = ULong.max_value())
: ULong
=>
"""
Generate a ULong in closed interval [min, max].
"""
_draw_int(min.i128(), max.i128(), min.i128()).ulong()
fun ref usize(
min: USize = USize.min_value(),
max: USize = USize.max_value())
: USize
=>
"""
Generate a USize in closed interval [min, max].
"""
_draw_int(min.i128(), max.i128(), min.i128()).usize()
fun ref i8(min: I8 = I8.min_value(), max: I8 = I8.max_value()): I8 =>
"""
Generate an I8 in closed interval [min, max].
"""
_draw_int(min.i128(), max.i128(), 0).i8()
fun ref i16(min: I16 = I16.min_value(), max: I16 = I16.max_value()): I16 =>
"""
Generate an I16 in closed interval [min, max].
"""
_draw_int(min.i128(), max.i128(), 0).i16()
fun ref i32(min: I32 = I32.min_value(), max: I32 = I32.max_value()): I32 =>
"""
Generate an I32 in closed interval [min, max].
"""
_draw_int(min.i128(), max.i128(), 0).i32()
fun ref i64(min: I64 = I64.min_value(), max: I64 = I64.max_value()): I64 =>
"""
Generate an I64 in closed interval [min, max].
"""
_draw_int(min.i128(), max.i128(), 0).i64()
fun ref i128(
min: I128 = I128.min_value(),
max: I128 = I128.max_value())
: I128
=>
"""
Generate an I128 in closed interval [min, max].
"""
_draw_int(min, max, 0)
fun ref ilong(
min: ILong = ILong.min_value(),
max: ILong = ILong.max_value())
: ILong
=>
"""
Generate an ILong in closed interval [min, max].
"""
_draw_int(min.i128(), max.i128(), 0).ilong()
fun ref isize(
min: ISize = ISize.min_value(),
max: ISize = ISize.max_value())
: ISize
=>
"""
Generate an ISize in closed interval [min, max].
"""
_draw_int(min.i128(), max.i128(), 0).isize()
fun ref f32(min: F32 = 0.0, max: F32 = 1.0): F32 =>
"""
Generate an F32 in the range from `min` to `max`.
"""
_draw_float(min.f64(), max.f64()).f32()
fun ref f64(min: F64 = 0.0, max: F64 = 1.0): F64 =>
"""
Generate an F64 in the range from `min` to `max`.
"""
_draw_float(min, max)
fun ref bool(): Bool =>
"""
Generate a random Bool value.
"""
match \exhaustive\ _mode
| _ModePlain =>
(_random.next() % 2) == 0
| _ModeRecording =>
let v = (_random.next() % 2) == 0
_choices.push(_BoolChoice(v))
v
| _ModeReplaying =>
if _replay_idx < _replay_seq.size() then
try
match _replay_seq(_replay_idx)?
| let bc: _BoolChoice =>
_replay_idx = _replay_idx + 1
return bc.value
end
else
_Unreachable()
end
end
_replay_exhausted_flag = true
let v = (_random.next() % 2) == 0
_choices.push(_BoolChoice(v))
v
end
fun ref forced_bool(value: Bool): Bool =>
"""
Record a predetermined Bool. This choice is excluded from
shrinking. Use when the outcome is fixed by the
generator's structure (e.g., elements below a collection minimum).
"""
match \exhaustive\ _mode
| _ModePlain =>
value
| _ModeRecording =>
_choices.push(_BoolChoice(value, true))
value
| _ModeReplaying =>
if _replay_idx < _replay_seq.size() then
try
match _replay_seq(_replay_idx)?
| let bc: _BoolChoice =>
_replay_idx = _replay_idx + 1
return value
end
else
_Unreachable()
end
end
_replay_exhausted_flag = true
_choices.push(_BoolChoice(value, true))
value
end
fun ref shuffle[T](array: Array[T] ref) ? =>
"""
Shuffle the array in place using Fisher-Yates, recording one integer
choice per element as the swap index.
"""
let n = array.size()
if n <= 1 then return end
start_span(SpanShuffle)
var i = n - 1
while i > 0 do
let j = usize(0, i)
try
array.swap_elements(i, j)?
else
end_span()?
error
end
i = i - 1
end
end_span()?
// --- Public span tracking ---
fun ref start_span(label: SpanLabel) =>
"""
Mark the beginning of a structural span in the choice sequence.
Spans let the shrinker understand generator structure — for example,
which choices belong to a single collection element.
"""
let pos =
match \exhaustive\ _mode
| _ModePlain => _choices.size()
| _ModeRecording => _choices.size()
| _ModeReplaying => _replay_idx
end
_span_stack.push(pos)
_span_labels.push(label)
fun ref end_span(discard: Bool = false) ? =>
"""
Close the most recently opened span. If `discard` is true, the span
is marked as discarded (e.g., a filter rejection).
Errors if no span is open (mismatched start_span/end_span calls).
"""
let start = _span_stack.pop()?
let label = _span_labels.pop()?
let end_pos =
match \exhaustive\ _mode
| _ModePlain => _choices.size()
| _ModeRecording => _choices.size()
| _ModeReplaying => _replay_idx
end
if label is SpanFilter then
if discard then
_filter_discards = _filter_discards + 1
else
_filter_accepts = _filter_accepts + 1
end
end
_spans.push(_Span(start, end_pos, label, discard))
// --- Package-private mode control ---
fun ref _start_recording() =>
_mode = _ModeRecording
_choices = recover iso Array[_Choice val] end
_spans = recover iso Array[_Span val] end
_span_stack = Array[USize]
_span_labels = Array[SpanLabel]
_filter_discards = 0
_filter_accepts = 0
fun ref _replay(choices: Array[_Choice val] val) =>
_mode = _ModeReplaying
_replay_seq = choices
_replay_idx = 0
_replay_exhausted_flag = false
_choices = recover iso Array[_Choice val] end
_spans = recover iso Array[_Span val] end
_span_stack = Array[USize]
_span_labels = Array[SpanLabel]
_filter_discards = 0
_filter_accepts = 0
fun ref _reset() =>
_mode = _ModePlain
_choices = recover iso Array[_Choice val] end
_spans = recover iso Array[_Span val] end
_span_stack = Array[USize]
_span_labels = Array[SpanLabel]
_replay_seq = recover val Array[_Choice val] end
_replay_idx = 0
_replay_exhausted_flag = false
_filter_discards = 0
_filter_accepts = 0
fun ref _get_choices(): Array[_Choice val] val =>
_choices =
recover iso Array[_Choice val] end
fun ref _get_spans(): Array[_Span val] val =>
_spans =
recover iso Array[_Span val] end
fun _consumed(): USize =>
_replay_idx
fun _replay_exhausted(): Bool =>
_replay_exhausted_flag
fun ref _choices_size(): USize =>
_choices.size()
fun ref _count_filter_spans(): (USize, USize) =>
let discards = _filter_discards
let accepts = _filter_accepts
_filter_discards = 0
_filter_accepts = 0
(discards, accepts)
// --- Internal draw helpers ---
fun ref _draw_int(min: I128, max: I128, shrink_towards: I128): I128 =>
match \exhaustive\ _mode
| _ModePlain =>
_raw_int(min, max)
| _ModeRecording =>
let v = _raw_int(min, max)
let towards = shrink_towards.max(min).min(max)
_choices.push(_IntChoice(v, min, max, towards))
v
| _ModeReplaying =>
if _replay_idx < _replay_seq.size() then
try
match _replay_seq(_replay_idx)?
| let ic: _IntChoice =>
_replay_idx = _replay_idx + 1
return ic.value.max(min).min(max)
end
else
_Unreachable()
end
end
_replay_exhausted_flag = true
let v = _raw_int(min, max)
let towards = shrink_towards.max(min).min(max)
_choices.push(_IntChoice(v, min, max, towards))
v
end
fun _float_in_range(real: F64, min: F64, max: F64): F64 =>
let span = max - min
if span.finite() then
(real * span) + min
else
let half = (max * 0.5) - (min * 0.5)
let mid = (min * 0.5) + (max * 0.5)
mid + (((real + real) - 1.0) * half)
end
fun ref _draw_float(min: F64, max: F64): F64 =>
match \exhaustive\ _mode
| _ModePlain =>
_float_in_range(_random.real(), min, max)
| _ModeRecording =>
let v = _float_in_range(_random.real(), min, max)
_choices.push(_FloatChoice(v, min, max))
v
| _ModeReplaying =>
if _replay_idx < _replay_seq.size() then
try
match _replay_seq(_replay_idx)?
| let fc: _FloatChoice =>
_replay_idx = _replay_idx + 1
return fc.value.max(min).min(max)
end
else
_Unreachable()
end
end
_replay_exhausted_flag = true
let v = _float_in_range(_random.real(), min, max)
_choices.push(_FloatChoice(v, min, max))
v
end
fun ref _draw_u128(min: U128, max: U128, shrink_towards: U128): U128 =>
match \exhaustive\ _mode
| _ModePlain =>
_raw_u128(min, max)
| _ModeRecording =>
let v = _raw_u128(min, max)
let towards = shrink_towards.max(min).min(max)
_choices.push(_U128Choice(v, min, max, towards))
v
| _ModeReplaying =>
if _replay_idx < _replay_seq.size() then
try
match _replay_seq(_replay_idx)?
| let uc: _U128Choice =>
_replay_idx = _replay_idx + 1
return uc.value.max(min).min(max)
end
else
_Unreachable()
end
end
_replay_exhausted_flag = true
let v = _raw_u128(min, max)
let towards = shrink_towards.max(min).min(max)
_choices.push(_U128Choice(v, min, max, towards))
v
end
fun ref _raw_u128(min: U128, max: U128): U128 =>
if min == max then return min end
let range: U128 = max - min
if range <= U64.max_value().u128() then
if range == U64.max_value().u128() then
min + _random.u64().u128()
else
min + _random.int(range.u64() + 1).u128()
end
else
let high = _random.u64()
let low = _random.u64()
let raw = (high.u128() << 64) or low.u128()
if range == U128.max_value() then
min + raw
else
min + (raw %% (range + 1))
end
end
fun ref _raw_int(min: I128, max: I128): I128 =>
"""
Generate a random I128 in [min, max] using the underlying PRNG.
"""
if min == max then return min end
let range: U128 = (max - min).u128()
if range <= U64.max_value().u128() then
if range == U64.max_value().u128() then
min + _random.u64().i128()
else
min + _random.int(range.u64() + 1).i128()
end
else
let high = _random.u64()
let low = _random.u64()
let raw = (high.u128() << 64) or low.u128()
if range == U128.max_value() then
min + raw.i128()
else
min + (raw %% (range + 1)).i128()
end
end