UNPKG

6.77 kBJavaScriptView Raw
1/* @flow weak */
2"use strict";
3
4var assert = require("assert");
5var either = require("./either.js");
6var random = require("./random.js");
7var sum = require("./sum.js");
8var utils = require("./utils.js");
9
10/**
11 ### Generator functions
12
13 A generator function, `generator a`, is a function `(size: nat) -> a`, which generates a value of given size.
14
15 Generator combinators are auto-curried:
16
17 ```js
18 var xs = jsc.generator.array(jsc.nat.generator, 1); // ≡
19 var ys = jsc.generator.array(jsc.nat.generator)(1);
20 ```
21
22 In purely functional approach `generator a` would be explicitly stateful computation:
23 `(size: nat, rng: randomstate) -> (a, randomstate)`.
24 *JSVerify* uses an implicit random number generator state,
25 but the value generation is deterministic (tests are reproducible),
26 if the primitives from *random* module are used.
27*/
28
29// Blessing: i.e adding prototype
30/* eslint-disable no-use-before-define */
31function generatorProtoMap(f) {
32 /* jshint validthis:true */
33 var generator = this; // eslint-disable-line no-invalid-this
34 generatorAssert(generator);
35 return generatorBless(function (size) {
36 return f(generator(size));
37 });
38}
39
40function generatorProtoFlatMap(f) {
41 /* jshint validthis:true */
42 var generator = this; // eslint-disable-line no-invalid-this
43 generatorAssert(generator);
44 return generatorBless(function (size) {
45 return f(generator(size))(size);
46 });
47}
48/* eslint-enable no-use-before-define */
49
50function generatorAssert(generator) {
51 assert(typeof generator === "function", "generator should be a function");
52 assert(generator.map === generatorProtoMap, "generator.map should be a function");
53 assert(generator.flatmap === generatorProtoFlatMap, "generator.flatmap should be a function");
54 assert(generator.flatMap === generatorProtoFlatMap, "generator.flatMap should be a function");
55}
56
57/**
58 - `generator.bless(f: nat -> a): generator a`
59
60 Bless function with `.map` and `.flatmap` properties.
61
62 - `.map(f: a -> b): generator b`
63
64 Map `generator a` into `generator b`. For example:
65
66 ```js
67 positiveIntegersGenerator = nat.generator.map(
68 function (x) { return x + 1; });
69 ```
70
71 - `.flatmap(f: a -> generator b): generator b`
72
73 Monadic bind for generators. Also `flatMap` version is supported.
74*/
75function generatorBless(generator) {
76 generator.map = generatorProtoMap;
77 generator.flatmap = generatorProtoFlatMap;
78 generator.flatMap = generatorProtoFlatMap;
79 return generator;
80}
81
82/**
83 - `generator.constant(x: a): generator a`
84*/
85function generateConstant(x) {
86 return generatorBless(function () {
87 return x;
88 });
89}
90
91/**
92 - `generator.combine(gen: generator a..., f: a... -> b): generator b`
93*/
94function generatorCombine() {
95 var generators = Array.prototype.slice.call(arguments, 0, -1);
96 var f = arguments[arguments.length - 1];
97
98 return generatorBless(function (size) {
99 var values = generators.map(function (gen) {
100 return gen(size);
101 });
102
103 return f.apply(undefined, values);
104 });
105}
106
107/**
108 - `generator.oneof(gens: list (generator a)): generator a`
109*/
110function generateOneof(generators) {
111 // TODO: generator
112 generators.forEach(function (gen) {
113 assert(typeof gen === "function");
114 });
115
116 var result = generatorBless(function (size) {
117 var idx = random(0, generators.length - 1);
118 var gen = generators[idx];
119 return gen(size);
120 });
121
122 return utils.curried2(result, arguments);
123}
124
125// Helper, essentially: log2(size + 1)
126function logsize(size) {
127 return Math.max(Math.round(Math.log(size + 1) / Math.log(2), 0));
128}
129
130/**
131 - `generator.recursive(genZ: generator a, genS: generator a -> generator a): generator a`
132*/
133function generatorRecursive(genZ, genS) {
134 return generatorBless(function (size) {
135 function rec(n, sizep) {
136 if (n <= 0 || random(0, 3) === 0) {
137 return genZ(sizep);
138 } else {
139 return genS(generatorBless(function (sizeq) {
140 return rec(n - 1, sizeq);
141 }))(sizep);
142 }
143 }
144
145 return rec(logsize(size), size);
146 });
147}
148
149/**
150 - `generator.pair(genA: generator a, genB: generator b): generator (a, b)`
151*/
152function generatePair(genA, genB) {
153 var result = generatorBless(function (size) {
154 return [genA(size), genB(size)];
155 });
156
157 return utils.curried3(result, arguments);
158}
159
160/**
161 - `generator.either(genA: generator a, genB: generator b): generator (either a b)`
162*/
163function generateEither(genA, genB) {
164 var result = generatorBless(function (size) { // eslint-disable-line consistent-return
165 var n = random(0, 1);
166 switch (n) {
167 case 0: return either.left(genA(size));
168 case 1: return either.right(genB(size));
169 // no default
170 }
171 });
172
173 return utils.curried3(result, arguments);
174}
175/**
176 - `generator.unit: generator ()`
177
178 `unit` is an empty tuple, i.e. empty array in JavaScript representation. This is useful as a building block.
179*/
180var generateUnit = generatorBless(function () {
181 return [];
182});
183
184/**
185 - `generator.tuple(gens: (generator a, generator b...)): generator (a, b...)`
186*/
187function generateTuple(gens) {
188 var len = gens.length;
189 var result = generatorBless(function (size) {
190 var r = [];
191 for (var i = 0; i < len; i++) {
192 r[i] = gens[i](size);
193 }
194 return r;
195 });
196
197 return utils.curried2(result, arguments);
198}
199
200/**
201 - `generator.sum(gens: (generator a, generator b...)): generator (a | b...)`
202*/
203function generateSum(gens) {
204 var len = gens.length;
205 var result = generatorBless(function (size) {
206 var idx = random(0, len - 1);
207 return sum.addend(idx, len, gens[idx](size));
208 });
209
210 return utils.curried2(result, arguments);
211}
212
213/**
214 - `generator.array(gen: generator a): generator (array a)`
215*/
216function generateArray(gen) {
217 var result = generatorBless(function (size) {
218 var arrsize = random(0, logsize(size));
219 var arr = new Array(arrsize);
220 for (var i = 0; i < arrsize; i++) {
221 arr[i] = gen(size);
222 }
223 return arr;
224 });
225
226 return utils.curried2(result, arguments);
227}
228
229/**
230 - `generator.nearray(gen: generator a): generator (array a)`
231*/
232function generateNEArray(gen) {
233 var result = generatorBless(function (size) {
234 var arrsize = random(1, Math.max(logsize(size), 1));
235 var arr = new Array(arrsize);
236 for (var i = 0; i < arrsize; i++) {
237 arr[i] = gen(size);
238 }
239 return arr;
240 });
241
242 return utils.curried2(result, arguments);
243}
244
245/**
246 - `generator.dict(gen: generator a): generator (dict a)`
247*/
248
249module.exports = {
250 pair: generatePair,
251 either: generateEither,
252 unit: generateUnit,
253 tuple: generateTuple,
254 sum: generateSum,
255 array: generateArray,
256 nearray: generateNEArray,
257 oneof: generateOneof,
258 constant: generateConstant,
259 bless: generatorBless,
260 combine: generatorCombine,
261 recursive: generatorRecursive,
262};