UNPKG

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