const prand = require('pure-rand');
class Random {
constructor(rng) {
this.rng = rng;
}
next(min, max) {
const g = prand.uniformIntDistribution(min, max, this.rng);
this.rng = g[1];
return g[0];
}
}
const map = (g, mapper, unmapper) => {
return {
generate(mrng) {
return mapper(g.generate(mrng));
},
*shrink(value) {
for (const shrunkValue of g.shrink(unmapper(value))) {
yield mapper(shrunkValue);
}
}
};
};
const miniFc = {}
miniFc.integer = (min, max) => {
return {
generate(mrng) {
return mrng.next(min, max);
},
*shrink(value) {
while (value !== min) {
value = min + Math.floor((value - min) / 2);
yield value;
}
}
}
}
miniFc.tuple = (...itemGenerators) => {
return {
generate(mrng) {
return itemGenerators.map(g => g.generate(mrng));
},
*shrink(value) {
for (let index = 0 ; index !== itemGenerators.length ; ++index) {
const currentGenerator = itemGenerators[index];
const currentValue = value[index];
for (const shrunkValue of currentGenerator.shrink(currentValue)) {
yield [...value.slice(0, index), shrunkValue, ...value.slice(index + 1)];
}
}
}
}
}
miniFc.array = (itemGenerator) => {
return {
generate(mrng) {
const size = mrng.next(0, 10);
const content = [];
for (let index = 0 ; index !== size ; ++index) {
content.push(itemGenerator.generate(mrng));
}
return content;
},
*shrink(value) {
// No shrink on empty arrays
if (value.length === 0) {
return;
}
// Step 1. Shrink on size first by keeping last items
let removedSize = Math.floor(value.length / 2);
while (removedSize > 0) {
yield value.slice(removedSize);
removedSize = Math.floor(removedSize / 2);
}
// Step 2. Shrink the first item alone
for (const shrunkItemValue of itemGenerator.shrink(value[0])) {
yield [shrunkItemValue, ...value.slice(1)];
}
// Step 3. Keep first item untouched
for (const shrunkValue of this.shrink(value.slice(1))) {
yield [value[0], ...shrunkValue];
}
}
}
}
miniFc.boolean = () => map(
miniFc.integer(0, 1),
Boolean,
b => b ? 1 : 0,
)
miniFc.character = () => map(
miniFc.integer(0, 25),
n => String.fromCharCode(97 + n),
c => c.codePointAt(0) - 97,
)
miniFc.string = () => map(
miniFc.array(miniFc.character()),
characters => characters.join(''),
s => s.split('')
)
miniFc.dictionary = (valueGenerator) => map(
miniFc.array(miniFc.tuple(miniFc.string(), valueGenerator)),
Object.fromEntries,
Object.entries,
)