UNPKG

4.81 kBJavaScriptView Raw
1/* @flow weak */
2"use strict";
3
4var assert = require("assert");
5var either = require("./either.js");
6var lazyseq = require("lazy-seq");
7var utils = require("./utils.js");
8
9/**
10 ### Shrink functions
11
12 A shrink function, `shrink a`, is a function `a -> [a]`, returning an array of *smaller* values.
13
14 Shrink combinators are auto-curried:
15
16 ```js
17 var xs = jsc.shrink.array(jsc.nat.shrink, [1]); // ≡
18 var ys = jsc.shrink.array(jsc.nat.shrink)([1]);
19 ```
20*/
21
22// Blessing: i.e adding prototype
23/* eslint-disable no-use-before-define */
24function shrinkProtoIsoMap(f, g) {
25 /* jshint validthis:true */
26 var shrink = this;
27 return shrinkBless(function (value) {
28 return shrink(g(value)).map(f);
29 });
30}
31/* eslint-enable no-use-before-define */
32
33/**
34 - `shrink.bless(f: a -> [a]): shrink a`
35
36 Bless function with `.smap` property.
37
38 - `.smap(f: a -> b, g: b -> a): shrink b`
39
40 Transform `shrink a` into `shrink b`. For example:
41
42 ```js
43 positiveIntegersShrink = nat.shrink.smap(
44 function (x) { return x + 1; },
45 function (x) { return x - 1; });
46 ```
47*/
48function shrinkBless(shrink) {
49 shrink.smap = shrinkProtoIsoMap;
50 return shrink;
51}
52
53/**
54 - `shrink.noop: shrink a`
55*/
56var shrinkNoop = shrinkBless(function shrinkNoop() {
57 return [];
58});
59
60/**
61 - `shrink.pair(shrA: shrink a, shrB: shrink b): shrink (a, b)`
62*/
63function shrinkPair(shrinkA, shrinkB) {
64 var result = shrinkBless(function (pair) {
65 assert(pair.length === 2, "shrinkPair: pair should be an Array of length 2");
66
67 var a = pair[0];
68 var b = pair[1];
69
70 var shrinkedA = shrinkA(a);
71 var shrinkedB = shrinkB(b);
72
73 var pairA = shrinkedA.map(function (ap) {
74 return [ap, b];
75 });
76
77 if (Array.isArray(pairA)) {
78 pairA = lazyseq.fromArray(pairA);
79 }
80
81 return pairA.append(function () {
82 var pairB = shrinkedB.map(function (bp) {
83 return [a, bp];
84 });
85 return pairB;
86 });
87 });
88
89 return utils.curried3(result, arguments);
90}
91
92/**
93 - `shrink.either(shrA: shrink a, shrB: shrink b): shrink (either a b)`
94*/
95function shrinkEither(shrinkA, shrinkB) {
96 function shrinkLeft(value) {
97 return shrinkA(value).map(either.left);
98 }
99
100 function shrinkRight(value) {
101 return shrinkB(value).map(either.right);
102 }
103
104 var result = shrinkBless(function (e) {
105 return e.either(shrinkLeft, shrinkRight);
106 });
107
108 return utils.curried3(result, arguments);
109}
110
111// We represent non-empty linked list as
112// singl x = [x]
113// cons h t = [h, t]
114function fromLinkedList(ll) {
115 assert(ll.length === 1 || ll.length === 2, "linked list must be either [] or [x, linkedlist]");
116 if (ll.length === 1) {
117 return [ll[0]];
118 } else {
119 return [ll[0]].concat(fromLinkedList(ll[1]));
120 }
121}
122
123function toLinkedList(arr) {
124 assert(Array.isArray(arr) && arr.length > 0, "toLinkedList expects non-empty array");
125 if (arr.length === 1) {
126 return [arr[0]];
127 } else {
128 return [arr[0], toLinkedList(arr.slice(1))];
129 }
130}
131
132function toSingleton(x) {
133 return [x];
134}
135
136// Vec a 1 → a
137function fromSingleton(a) {
138 return a[0];
139}
140
141function flattenShrink(shrinksLL) {
142 if (shrinksLL.length === 1) {
143 return shrinksLL[0].smap(toSingleton, fromSingleton);
144 } else {
145 var head = shrinksLL[0];
146 var tail = shrinksLL[1];
147 return shrinkPair(head, flattenShrink(tail));
148 }
149}
150
151/**
152 - `shrink.tuple(shrs: (shrink a, shrink b...)): shrink (a, b...)`
153*/
154function shrinkTuple(shrinks) {
155 assert(shrinks.length > 0, "shrinkTuple needs > 0 values");
156 var shrinksLL = toLinkedList(shrinks);
157 var shrink = flattenShrink(shrinksLL);
158 var result = function (tuple) {
159 var ll = toLinkedList(tuple);
160 return shrink(ll).map(fromLinkedList);
161 };
162
163 return utils.curried2(result, arguments);
164}
165
166function shrinkArrayWithMinimumSize(size) {
167 function shrinkArrayImpl(shrink) {
168 var result = shrinkBless(function (arr) {
169 assert(Array.isArray(arr), "shrinkArrayImpl() expects array, got: " + arr);
170 if (arr.length <= size) {
171 return lazyseq.nil;
172 } else {
173 var x = arr[0];
174 var xs = arr.slice(1);
175
176 return lazyseq.cons(xs, lazyseq.nil)
177 .append(shrink(x).map(function (xp) { return [xp].concat(xs); }))
178 .append(shrinkArrayImpl(shrink, xs).map(function (xsp) { return [x].concat(xsp); }));
179 }
180 });
181
182 return utils.curried2(result, arguments);
183 }
184
185 return shrinkArrayImpl;
186}
187
188/**
189 - `shrink.array(shr: shrink a): shrink (array a)`
190*/
191var shrinkArray = shrinkArrayWithMinimumSize(0);
192
193/**
194 - `shrink.nearray(shr: shrink a): shrink (nearray a)`
195*/
196var shrinkNEArray = shrinkArrayWithMinimumSize(1);
197
198module.exports = {
199 noop: shrinkNoop,
200 pair: shrinkPair,
201 either: shrinkEither,
202 tuple: shrinkTuple,
203 array: shrinkArray,
204 nearray: shrinkNEArray,
205 bless: shrinkBless,
206};