summaryrefslogtreecommitdiff
path: root/pbt/ts/src/gen.ts
diff options
context:
space:
mode:
Diffstat (limited to 'pbt/ts/src/gen.ts')
-rw-r--r--pbt/ts/src/gen.ts73
1 files changed, 73 insertions, 0 deletions
diff --git a/pbt/ts/src/gen.ts b/pbt/ts/src/gen.ts
new file mode 100644
index 0000000..531a118
--- /dev/null
+++ b/pbt/ts/src/gen.ts
@@ -0,0 +1,73 @@
+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<A> {
+ 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;
+ }
+}