summaryrefslogtreecommitdiff
path: root/pbt/ts/src
diff options
context:
space:
mode:
authorArnaud Bailly <arnaud@pankzsoft.com>2026-08-30 18:13:01 +0200
committerArnaud Bailly <arnaud@pankzsoft.com>2026-09-01 07:59:01 +0200
commit32f9be77d18d3ad0cb86e7290c6df7c51ab99ca5 (patch)
tree6633ba3302f8f294e4609aedf9ffdd3693b2ac11 /pbt/ts/src
parentb3285b3f5878d705fcbeb7165097b6623a02f553 (diff)
downloadlambda-nantes-32f9be77d18d3ad0cb86e7290c6df7c51ab99ca5.tar.gz
Flesh out simple integrated shrinker
Diffstat (limited to 'pbt/ts/src')
-rw-r--r--pbt/ts/src/gen.test.ts19
-rw-r--r--pbt/ts/src/gen.ts73
2 files changed, 92 insertions, 0 deletions
diff --git a/pbt/ts/src/gen.test.ts b/pbt/ts/src/gen.test.ts
new file mode 100644
index 0000000..eaba36f
--- /dev/null
+++ b/pbt/ts/src/gen.test.ts
@@ -0,0 +1,19 @@
+import Prando from 'prando';
+import {none} from 'fp-ts/lib/Option.js';
+import * as G from './gen.js';
+import {test, expect} from 'vitest';
+
+test('can generate and shrink bytes', async () => {
+ const prng = new Prando(Math.random() * 1000);
+ const gen = new G.BytesGen(prng);
+ const bytes: number[][] = [];
+ for (let i = 1; i < 10; i++) {
+ bytes.push(gen.generate(i));
+ }
+ console.log('Generated bytes:', bytes);
+ const replay = gen.replay();
+ for (let i = 1; i < 10; i++) {
+ const replayed = replay.generate(i);
+ expect(replayed, `Replaying at ${i}`).toEqual(bytes[i - 1]);
+ }
+});
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;
+ }
+}