UNPKG

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