randomness.pony

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
use "random"

type _RandomnessMode is (_ModePlain | _ModeRecording | _ModeReplaying)

primitive _ModePlain
primitive _ModeRecording
primitive _ModeReplaying

class ref Randomness
  """
  All draw methods are partial: they error during replay when the recorded
  choice sequence is exhausted or when a type/range mismatch is detected.
  In plain mode (user-constructed Randomness), draws never error.

  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]

  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 =>
      let v = (_random.next() % 2) == 0
      v
    | _ModeRecording =>
      let v = (_random.next() % 2) == 0
      _choices.push(_BoolChoice(v))
      v
    | _ModeReplaying =>
      if _replay_idx >= _replay_seq.size() then
        _replay_exhausted_flag = true
        error
      end
      match _replay_seq(_replay_idx)?
      | let bc: _BoolChoice =>
        _replay_idx = _replay_idx + 1
        bc.value
      else
        error
      end
    end

  fun ref forced_bool(value: Bool): Bool ? =>
    """
    Record a predetermined Bool. The shrinker will not attempt to
    change this choice. 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
        _replay_exhausted_flag = true
        error
      end
      match _replay_seq(_replay_idx)?
      | let bc: _BoolChoice =>
        _replay_idx = _replay_idx + 1
        value
      else
        error
      end
    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
    _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]

  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]

  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

  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

  // --- 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
        _replay_exhausted_flag = true
        error
      end
      match _replay_seq(_replay_idx)?
      | let ic: _IntChoice =>
        _replay_idx = _replay_idx + 1
        ic.value.max(min).min(max)
      else
        error
      end
    end

  fun ref _draw_float(min: F64, max: F64): F64 ? =>
    match \exhaustive\ _mode
    | _ModePlain =>
      (_random.real() * (max - min)) + min
    | _ModeRecording =>
      let v = (_random.real() * (max - min)) + min
      _choices.push(_FloatChoice(v, min, max))
      v
    | _ModeReplaying =>
      if _replay_idx >= _replay_seq.size() then
        _replay_exhausted_flag = true
        error
      end
      match _replay_seq(_replay_idx)?
      | let fc: _FloatChoice =>
        _replay_idx = _replay_idx + 1
        fc.value.max(min).min(max)
      else
        error
      end
    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
        _replay_exhausted_flag = true
        error
      end
      match _replay_seq(_replay_idx)?
      | let uc: _U128Choice =>
        _replay_idx = _replay_idx + 1
        uc.value.max(min).min(max)
      else
        error
      end
    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