import { Option, none, some } from 'fp-ts/lib/Option.js'; import Prando from 'prando'; // a shrinkable generator needs to maintain some state, // namely the sequence of generated values in order to be // able to backtrack and shrink. type GenMode = 'gen' | 'shrink'; export interface Gen { generate(): A; generateN(n: number): A[]; } export interface GenBytes { generate(n: number): number[]; } export class BytesGen implements GenBytes { prng: Prando; bytes: number[][]; // the internal sequence of generated values constructor(prng: Prando) { this.prng = prng; this.bytes = []; } generate(n: number): number[] { if (n <= 0) { throw new Error('n must be greater than 0'); } return this.generateBytes(n); } generateBytes(n: number): number[] { const gens = []; for (let i = 0; i < n; i++) { gens.push(this.prng.nextInt(0, 255)); } this.bytes.push([...gens]); return gens; } replay(): GenBytes { return new ReplayBytes(this.bytes); } } export class ReplayBytes implements GenBytes { bytes: number[][]; // the internal sequence of generated values index: number = 0; // the current index in the sequence of generated values constructor(bytes: number[][]) { this.bytes = bytes; this.index = 0; } generate(n: number): number[] { if (this.index >= this.bytes.length) { throw new Error('No more bytes to shrink'); } const chunk = [...this.bytes[this.index++]]; if (chunk.length < n) { const zeros = new Array(n - chunk.length).fill(0); chunk.unshift(...zeros); } return chunk; } } class GenInt implements Gen { private bytes: BytesGen; constructor(seed: number | undefined = undefined) { this.bytes = new BytesGen(new Prando(seed ? seed : Math.random() * 1000)); } generate(): number { const bytes = this.bytes.generate(1); return bytes[0]; } generateN(n: number): number[] { const result = []; for (let i = 0; i < n; i++) { result.push(this.bytes.generate(1)[0]); } return result; } } class GenIntWithin implements Gen { private bytes: BytesGen; constructor(private min: number, private max: number, seed: number | undefined = undefined) { this.bytes = new BytesGen(new Prando(seed ? seed : Math.random() * 1000000000)); this.min = min; this.max = max; } generate(): number { const range = this.max - this.min; const numBytes = Math.ceil(Math.log2(range) / 8); const bytes = this.bytes.generate(numBytes); let value = 0; for (let i = 0; i < bytes.length; i++) { value = (value << 8) | bytes[i]; } return this.min + (value % range); } generateN(n: number): number[] { const result = []; for (let i = 0; i < n; i++) { result.push(this.bytes.generate(1)[0]); } return result; } } export const genInt = (seed : number | undefined = undefined) => new GenInt(seed); export const genIntWithin = (min: number, max: number, seed : number | undefined = undefined) => new GenIntWithin(min, max, seed);