tape-six 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +11 -0
- package/README.md +9 -0
- package/bin/tape6-server.js +196 -0
- package/bin/tape6.js +187 -0
- package/index.js +74 -0
- package/package.json +57 -0
- package/src/State.js +93 -0
- package/src/TTYReporter.js +270 -0
- package/src/TapReporter.js +126 -0
- package/src/Tester.js +411 -0
- package/src/deep6/env.js +174 -0
- package/src/deep6/index.js +21 -0
- package/src/deep6/traverse/assemble.js +158 -0
- package/src/deep6/traverse/clone.js +109 -0
- package/src/deep6/traverse/deref.js +104 -0
- package/src/deep6/traverse/preprocess.js +112 -0
- package/src/deep6/traverse/walk.js +262 -0
- package/src/deep6/unifiers/matchCondition.js +16 -0
- package/src/deep6/unifiers/matchInstanceOf.js +16 -0
- package/src/deep6/unifiers/matchString.js +33 -0
- package/src/deep6/unifiers/matchTypeOf.js +16 -0
- package/src/deep6/unifiers/ref.js +21 -0
- package/src/deep6/unify.js +446 -0
- package/src/deep6/utils/replaceVars.js +24 -0
- package/src/node/TestWorker.js +37 -0
- package/src/node/config.js +56 -0
- package/src/node/listing.js +78 -0
- package/src/test.js +187 -0
- package/src/utils/Deferred.js +10 -0
- package/src/utils/EventServer.js +78 -0
- package/src/utils/box.js +101 -0
- package/src/utils/defer.js +33 -0
- package/src/utils/fileSets.js +113 -0
- package/src/utils/formatters.js +30 -0
- package/src/utils/timeout.js +3 -0
- package/src/utils/timer.js +24 -0
- package/src/utils/yamlFormatter.js +150 -0
package/src/Tester.js
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import {equal, match, any} from './deep6/index.js';
|
|
2
|
+
|
|
3
|
+
const throwHelper = fn => {
|
|
4
|
+
try {
|
|
5
|
+
fn();
|
|
6
|
+
} catch (error) {
|
|
7
|
+
return error;
|
|
8
|
+
}
|
|
9
|
+
return null;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
class Tester {
|
|
13
|
+
constructor(state, testNumber) {
|
|
14
|
+
this.state = state;
|
|
15
|
+
this.testNumber = testNumber;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
plan(n) {
|
|
19
|
+
this.state.setPlan(n);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
comment(msg) {
|
|
23
|
+
this.state.emit({type: 'comment', name: msg || 'comment', test: this.testNumber, marker: new Error(), time: this.state.timer.now()});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
skipTest(...args) {
|
|
27
|
+
let msg;
|
|
28
|
+
for (let i = args.length - 1; i >= 0; --i) {
|
|
29
|
+
if (typeof args[i] == 'string') {
|
|
30
|
+
msg = args[i];
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
this.state.emit({
|
|
35
|
+
name: msg || 'skipped test',
|
|
36
|
+
test: this.testNumber,
|
|
37
|
+
marker: new Error(),
|
|
38
|
+
time: this.state.timer.now(),
|
|
39
|
+
skip: true,
|
|
40
|
+
operator: 'skip'
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
bailOut(msg) {
|
|
45
|
+
this.state.emit({
|
|
46
|
+
type: 'bail-out',
|
|
47
|
+
name: msg || 'bail out',
|
|
48
|
+
test: this.testNumber,
|
|
49
|
+
marker: new Error(),
|
|
50
|
+
time: this.state.timer.now(),
|
|
51
|
+
operator: 'bailOut'
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// asserts
|
|
56
|
+
|
|
57
|
+
pass(msg) {
|
|
58
|
+
this.state.emit({
|
|
59
|
+
name: msg || 'pass',
|
|
60
|
+
test: this.testNumber,
|
|
61
|
+
marker: new Error(),
|
|
62
|
+
time: this.state.timer.now(),
|
|
63
|
+
operator: 'pass',
|
|
64
|
+
data: {expected: true, actual: true}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fail(msg) {
|
|
69
|
+
this.state.emit({
|
|
70
|
+
name: msg || 'fail',
|
|
71
|
+
test: this.testNumber,
|
|
72
|
+
marker: new Error(),
|
|
73
|
+
time: this.state.timer.now(),
|
|
74
|
+
operator: 'fail',
|
|
75
|
+
fail: true,
|
|
76
|
+
data: {expected: true, actual: false}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
ok(value, msg) {
|
|
81
|
+
this.state.emit({
|
|
82
|
+
name: msg || 'should be truthy',
|
|
83
|
+
test: this.testNumber,
|
|
84
|
+
marker: new Error(),
|
|
85
|
+
time: this.state.timer.now(),
|
|
86
|
+
operator: 'ok',
|
|
87
|
+
fail: !value,
|
|
88
|
+
data: {
|
|
89
|
+
expected: true,
|
|
90
|
+
actual: value
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
notOk(value, msg) {
|
|
96
|
+
this.state.emit({
|
|
97
|
+
name: msg || 'should be falsy',
|
|
98
|
+
test: this.testNumber,
|
|
99
|
+
marker: new Error(),
|
|
100
|
+
time: this.state.timer.now(),
|
|
101
|
+
operator: 'notOk',
|
|
102
|
+
fail: !!value,
|
|
103
|
+
data: {
|
|
104
|
+
expected: false,
|
|
105
|
+
actual: value
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
error(error, msg) {
|
|
111
|
+
this.state.emit({
|
|
112
|
+
name: msg || String(error),
|
|
113
|
+
test: this.testNumber,
|
|
114
|
+
marker: new Error(),
|
|
115
|
+
time: this.state.timer.now(),
|
|
116
|
+
operator: 'error',
|
|
117
|
+
fail: !!error,
|
|
118
|
+
data: {
|
|
119
|
+
expected: true,
|
|
120
|
+
actual: error
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
strictEqual(a, b, msg) {
|
|
126
|
+
this.state.emit({
|
|
127
|
+
name: msg || 'should be strictly equal',
|
|
128
|
+
test: this.testNumber,
|
|
129
|
+
marker: new Error(),
|
|
130
|
+
time: this.state.timer.now(),
|
|
131
|
+
operator: 'equal',
|
|
132
|
+
fail: typeof a == 'object' ? a !== b : !equal(a, b),
|
|
133
|
+
data: {
|
|
134
|
+
expected: b,
|
|
135
|
+
actual: a
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
notStrictEqual(a, b, msg) {
|
|
141
|
+
this.state.emit({
|
|
142
|
+
name: msg || 'should not be strictly equal',
|
|
143
|
+
test: this.testNumber,
|
|
144
|
+
marker: new Error(),
|
|
145
|
+
time: this.state.timer.now(),
|
|
146
|
+
operator: 'notEqual',
|
|
147
|
+
fail: typeof a == 'object' ? a === b : equal(a, b),
|
|
148
|
+
data: {
|
|
149
|
+
expected: b,
|
|
150
|
+
actual: a
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
looseEqual(a, b, msg) {
|
|
156
|
+
this.state.emit({
|
|
157
|
+
name: msg || 'should be loosely equal',
|
|
158
|
+
test: this.testNumber,
|
|
159
|
+
marker: new Error(),
|
|
160
|
+
time: this.state.timer.now(),
|
|
161
|
+
operator: 'looseEqual',
|
|
162
|
+
fail: a != b,
|
|
163
|
+
data: {
|
|
164
|
+
expected: b,
|
|
165
|
+
actual: a
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
notLooseEqual(a, b, msg) {
|
|
171
|
+
this.state.emit({
|
|
172
|
+
name: msg || 'should not be loosely equal',
|
|
173
|
+
test: this.testNumber,
|
|
174
|
+
marker: new Error(),
|
|
175
|
+
time: this.state.timer.now(),
|
|
176
|
+
operator: 'notLooseEqual',
|
|
177
|
+
fail: a == b,
|
|
178
|
+
data: {
|
|
179
|
+
expected: b,
|
|
180
|
+
actual: a
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
deepEqual(a, b, msg) {
|
|
186
|
+
this.state.emit({
|
|
187
|
+
name: msg || 'should be deeply equivalent',
|
|
188
|
+
test: this.testNumber,
|
|
189
|
+
marker: new Error(),
|
|
190
|
+
time: this.state.timer.now(),
|
|
191
|
+
operator: 'deepEqual',
|
|
192
|
+
fail: !equal(a, b),
|
|
193
|
+
data: {
|
|
194
|
+
expected: b,
|
|
195
|
+
actual: a
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
notDeepEqual(a, b, msg) {
|
|
201
|
+
this.state.emit({
|
|
202
|
+
name: msg || 'should not be deeply equivalent',
|
|
203
|
+
test: this.testNumber,
|
|
204
|
+
marker: new Error(),
|
|
205
|
+
time: this.state.timer.now(),
|
|
206
|
+
operator: 'notDeepEqual',
|
|
207
|
+
fail: equal(a, b),
|
|
208
|
+
data: {
|
|
209
|
+
expected: b,
|
|
210
|
+
actual: a
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
deepLooseEqual(a, b, msg) {
|
|
216
|
+
this.state.emit({
|
|
217
|
+
name: msg || 'should be loosely equal',
|
|
218
|
+
test: this.testNumber,
|
|
219
|
+
marker: new Error(),
|
|
220
|
+
time: this.state.timer.now(),
|
|
221
|
+
operator: 'deepLooseEqual',
|
|
222
|
+
fail: !equal(a, b, {circular: true, loose: true}),
|
|
223
|
+
data: {
|
|
224
|
+
expected: b,
|
|
225
|
+
actual: a
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
notDeepLooseEqual(a, b, msg) {
|
|
231
|
+
this.state.emit({
|
|
232
|
+
name: msg || 'should not be loosely equal',
|
|
233
|
+
test: this.testNumber,
|
|
234
|
+
marker: new Error(),
|
|
235
|
+
time: this.state.timer.now(),
|
|
236
|
+
operator: 'notDeepLooseEqual',
|
|
237
|
+
fail: equal(a, b, {circular: true, loose: true}),
|
|
238
|
+
data: {
|
|
239
|
+
expected: b,
|
|
240
|
+
actual: a
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
throws(fn, msg) {
|
|
246
|
+
if (typeof fn != 'function') throw new TypeError('the first argument should be a function');
|
|
247
|
+
const result = throwHelper(fn);
|
|
248
|
+
this.state.emit({
|
|
249
|
+
name: msg || 'should throw',
|
|
250
|
+
test: this.testNumber,
|
|
251
|
+
marker: new Error(),
|
|
252
|
+
time: this.state.timer.now(),
|
|
253
|
+
operator: 'throws',
|
|
254
|
+
fail: !result,
|
|
255
|
+
data: {
|
|
256
|
+
expected: null,
|
|
257
|
+
actual: result
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
doesNotThrow(fn, msg) {
|
|
263
|
+
if (typeof fn != 'function') throw new TypeError('the first argument should be a function');
|
|
264
|
+
const result = throwHelper(fn);
|
|
265
|
+
this.state.emit({
|
|
266
|
+
name: msg || 'should not throw',
|
|
267
|
+
test: this.testNumber,
|
|
268
|
+
marker: new Error(),
|
|
269
|
+
time: this.state.timer.now(),
|
|
270
|
+
operator: 'doesNotThrow',
|
|
271
|
+
fail: !!result,
|
|
272
|
+
data: {
|
|
273
|
+
expected: null,
|
|
274
|
+
actual: result
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
matchString(string, regexp, msg) {
|
|
280
|
+
if (typeof string != 'string') throw new TypeError('the first argument should be a string');
|
|
281
|
+
if (!regexp || typeof regexp != 'object' || typeof regexp.test != 'function')
|
|
282
|
+
throw new TypeError('the second argument should be a regular expression object');
|
|
283
|
+
this.state.emit({
|
|
284
|
+
name: msg || 'should match regular expression',
|
|
285
|
+
test: this.testNumber,
|
|
286
|
+
marker: new Error(),
|
|
287
|
+
time: this.state.timer.now(),
|
|
288
|
+
operator: 'matchString',
|
|
289
|
+
fail: !regexp.test(string),
|
|
290
|
+
data: {
|
|
291
|
+
expected: regexp,
|
|
292
|
+
actual: string
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
doesNotMatchString(string, regexp, msg) {
|
|
298
|
+
if (typeof string != 'string') throw new TypeError('the first argument should be a string');
|
|
299
|
+
if (!regexp || typeof regexp != 'object' || typeof regexp.test != 'function')
|
|
300
|
+
throw new TypeError('the second argument should be a regular expression object');
|
|
301
|
+
this.state.emit({
|
|
302
|
+
name: msg || 'should not match regular expression',
|
|
303
|
+
test: this.testNumber,
|
|
304
|
+
marker: new Error(),
|
|
305
|
+
time: this.state.timer.now(),
|
|
306
|
+
operator: 'doesNotMatch',
|
|
307
|
+
fail: regexp.test(string),
|
|
308
|
+
data: {
|
|
309
|
+
expected: regexp,
|
|
310
|
+
actual: string
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
match(a, b, msg) {
|
|
316
|
+
this.state.emit({
|
|
317
|
+
name: msg || 'should match object',
|
|
318
|
+
test: this.testNumber,
|
|
319
|
+
marker: new Error(),
|
|
320
|
+
time: this.state.timer.now(),
|
|
321
|
+
operator: 'match',
|
|
322
|
+
fail: !match(a, b),
|
|
323
|
+
data: {
|
|
324
|
+
expected: b,
|
|
325
|
+
actual: a
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
doesNotMatch(a, b, msg) {
|
|
331
|
+
this.state.emit({
|
|
332
|
+
name: msg || 'should not match object',
|
|
333
|
+
test: this.testNumber,
|
|
334
|
+
marker: new Error(),
|
|
335
|
+
time: this.state.timer.now(),
|
|
336
|
+
operator: 'doesNotMatchObject',
|
|
337
|
+
fail: match(a, b),
|
|
338
|
+
data: {
|
|
339
|
+
expected: b,
|
|
340
|
+
actual: a
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
rejects(promise, msg) {
|
|
346
|
+
if (!promise || typeof promise.then != 'function') throw new TypeError('the first argument should be a promise');
|
|
347
|
+
return promise
|
|
348
|
+
.then(
|
|
349
|
+
() => null,
|
|
350
|
+
error => error
|
|
351
|
+
)
|
|
352
|
+
.then(result => {
|
|
353
|
+
this.state.emit({
|
|
354
|
+
name: msg || 'should be rejected',
|
|
355
|
+
test: this.testNumber,
|
|
356
|
+
marker: new Error(),
|
|
357
|
+
time: this.state.timer.now(),
|
|
358
|
+
operator: 'rejects',
|
|
359
|
+
fail: !result,
|
|
360
|
+
data: {
|
|
361
|
+
expected: null,
|
|
362
|
+
actual: result
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
resolves(promise, msg) {
|
|
369
|
+
if (!promise || typeof promise.then != 'function') throw new TypeError('the first argument should be a promise');
|
|
370
|
+
return promise
|
|
371
|
+
.then(
|
|
372
|
+
() => null,
|
|
373
|
+
error => error
|
|
374
|
+
)
|
|
375
|
+
.then(result => {
|
|
376
|
+
this.state.emit({
|
|
377
|
+
name: msg || 'should not be rejected',
|
|
378
|
+
test: this.testNumber,
|
|
379
|
+
marker: new Error(),
|
|
380
|
+
time: this.state.timer.now(),
|
|
381
|
+
operator: 'resolves',
|
|
382
|
+
fail: !!result,
|
|
383
|
+
data: {
|
|
384
|
+
expected: null,
|
|
385
|
+
actual: result
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// missing: T (eval)
|
|
392
|
+
}
|
|
393
|
+
Tester.prototype.any = Tester.prototype._ = any;
|
|
394
|
+
|
|
395
|
+
const setAliases = (source, aliases) => aliases.split(', ').forEach(alias => (Tester.prototype[alias] = Tester.prototype[source]));
|
|
396
|
+
|
|
397
|
+
setAliases('ok', 'true, assert');
|
|
398
|
+
setAliases('notOk', 'false, notok');
|
|
399
|
+
setAliases('error', 'ifError, ifErr, iferror');
|
|
400
|
+
setAliases('strictEqual', 'is, equal, equals, isEqual, strictEquals');
|
|
401
|
+
setAliases('notStrictEqual', 'not, notEqual, notEquals, isNotEqual, doesNotEqual, isInequal, notStrictEquals, isNot');
|
|
402
|
+
setAliases('looseEqual', 'looseEquals');
|
|
403
|
+
setAliases('notLooseEqual', 'notLooseEquals');
|
|
404
|
+
setAliases('deepEqual', 'same, deepEquals, isEquivalent');
|
|
405
|
+
setAliases('notDeepEqual', 'notSame, notDeepEquals, notEquivalent, notDeeply, isNotDeepEqual, isNotDeepEqual, isNotEquivalent, isInequivalent');
|
|
406
|
+
setAliases('rejects', 'doesNotResolve');
|
|
407
|
+
setAliases('resolves', 'doesNotReject');
|
|
408
|
+
|
|
409
|
+
// test() (an embedded test runner) is added in ./test.js to avoid circular dependencies
|
|
410
|
+
|
|
411
|
+
export default Tester;
|
package/src/deep6/env.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// Env
|
|
2
|
+
|
|
3
|
+
const keyDepth = Symbol.for('deep6.env.depth');
|
|
4
|
+
|
|
5
|
+
const collectSymbols = object => {
|
|
6
|
+
const symbols = new Set();
|
|
7
|
+
while (object && typeof object == 'object') {
|
|
8
|
+
Object.getOwnPropertySymbols(object).forEach(symbol => symbols.add(symbol));
|
|
9
|
+
object = Object.getPrototypeOf(object);
|
|
10
|
+
}
|
|
11
|
+
symbols.delete(keyDepth);
|
|
12
|
+
return Array.from(symbols);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const ensure = (object, depth) => {
|
|
16
|
+
while (object[keyDepth] > depth) object = object.getPrototypeOf(object);
|
|
17
|
+
if (object[keyDepth] < depth) {
|
|
18
|
+
object = Object.create(object);
|
|
19
|
+
object[keyDepth] = depth;
|
|
20
|
+
}
|
|
21
|
+
return object;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
class Env {
|
|
25
|
+
constructor() {
|
|
26
|
+
this.variables = Object.create(null);
|
|
27
|
+
this.values = Object.create(null);
|
|
28
|
+
this.depth = this.variables[keyDepth] = this.values[keyDepth] = 0;
|
|
29
|
+
}
|
|
30
|
+
push() {
|
|
31
|
+
++this.depth;
|
|
32
|
+
}
|
|
33
|
+
pop() {
|
|
34
|
+
if (this.depth < 1) throw new Error('attempt to pop a frame with an empty stack');
|
|
35
|
+
--this.depth;
|
|
36
|
+
while (this.variables[keyDepth] > this.depth) this.variables = Object.getPrototypeOf(this.variables);
|
|
37
|
+
while (this.values[keyDepth] > this.depth) this.values = Object.getPrototypeOf(this.values);
|
|
38
|
+
}
|
|
39
|
+
revert(depth) {
|
|
40
|
+
if (this.depth < depth) throw new Error('attempt to revert a stack to a higher depth');
|
|
41
|
+
while (this.variables[keyDepth] > depth) this.variables = Object.getPrototypeOf(this.variables);
|
|
42
|
+
while (this.values[keyDepth] > depth) this.values = Object.getPrototypeOf(this.values);
|
|
43
|
+
this.depth = depth;
|
|
44
|
+
}
|
|
45
|
+
bindVar(name1, name2) {
|
|
46
|
+
const depth = this.depth,
|
|
47
|
+
vars = (this.variables = ensure(this.variables, depth));
|
|
48
|
+
let u1 = vars[name1],
|
|
49
|
+
u2 = vars[name2];
|
|
50
|
+
u1 && (u1 = vars[name1] = ensure(u1, depth));
|
|
51
|
+
u2 && (u2 = vars[name2] = ensure(u2, depth));
|
|
52
|
+
if (u1) {
|
|
53
|
+
if (u2) {
|
|
54
|
+
for (const k in u2) {
|
|
55
|
+
vars[k] = u1;
|
|
56
|
+
u1[k] = 1;
|
|
57
|
+
}
|
|
58
|
+
collectSymbols(u2).forEach(k => ((vars[k] = u1), (u1[k] = 1)));
|
|
59
|
+
} else {
|
|
60
|
+
vars[name2] = u1;
|
|
61
|
+
u1[name2] = 1;
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
if (u2) {
|
|
65
|
+
vars[name1] = u2;
|
|
66
|
+
u2[name1] = 1;
|
|
67
|
+
} else {
|
|
68
|
+
u2 = Object.create(null);
|
|
69
|
+
u2[keyDepth] = depth;
|
|
70
|
+
vars[name1] = vars[name2] = u2;
|
|
71
|
+
u2[name1] = u2[name2] = 1;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
bindVal(name, val) {
|
|
76
|
+
const depth = this.depth,
|
|
77
|
+
values = (this.values = ensure(this.values, depth)),
|
|
78
|
+
vars = (this.variables = ensure(this.variables, depth));
|
|
79
|
+
let u = vars[name];
|
|
80
|
+
u && (u = vars[name] = ensure(u, depth));
|
|
81
|
+
if (u) {
|
|
82
|
+
for (const k in u) {
|
|
83
|
+
values[k] = val;
|
|
84
|
+
vars[k] = null;
|
|
85
|
+
}
|
|
86
|
+
collectSymbols(u).forEach(k => ((values[k] = val), (vars[k] = null)));
|
|
87
|
+
} else {
|
|
88
|
+
values[name] = val;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// helpers
|
|
92
|
+
isBound(name) {
|
|
93
|
+
return name in env.values;
|
|
94
|
+
}
|
|
95
|
+
isAlias(name1, name2) {
|
|
96
|
+
const u = env.variables[name2];
|
|
97
|
+
return u && u[name1] === 1;
|
|
98
|
+
}
|
|
99
|
+
get(name) {
|
|
100
|
+
return env.values[name];
|
|
101
|
+
}
|
|
102
|
+
// debugging
|
|
103
|
+
getAllValues() {
|
|
104
|
+
const values = this.values,
|
|
105
|
+
result = collectSymbols(values).map(k => ({name: k, value: values[k]}));
|
|
106
|
+
for (const k in values) {
|
|
107
|
+
result.push({name: k, value: values[k]});
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Custom unifier
|
|
114
|
+
|
|
115
|
+
class Unifier {}
|
|
116
|
+
|
|
117
|
+
const isUnifier = x => x instanceof Unifier;
|
|
118
|
+
|
|
119
|
+
// Unifier should define a method:
|
|
120
|
+
// unify(val, ls, rs, env):
|
|
121
|
+
// val is a value we are unifying with
|
|
122
|
+
// ls is a stack of left arguments
|
|
123
|
+
// rs is a stack of right arguments corresponding to ls
|
|
124
|
+
// env is an environment
|
|
125
|
+
// the result should be true/false for success/failure
|
|
126
|
+
|
|
127
|
+
// AnyVar
|
|
128
|
+
|
|
129
|
+
const _ = Symbol.for('deep6.any'),
|
|
130
|
+
any = _;
|
|
131
|
+
|
|
132
|
+
// Variable
|
|
133
|
+
|
|
134
|
+
class Variable extends Unifier {
|
|
135
|
+
constructor(name) {
|
|
136
|
+
super();
|
|
137
|
+
this.name = name || Symbol();
|
|
138
|
+
}
|
|
139
|
+
isBound(env) {
|
|
140
|
+
return this.name in env.values;
|
|
141
|
+
}
|
|
142
|
+
isAlias(name, env) {
|
|
143
|
+
const u = env.variables[this.name];
|
|
144
|
+
return u && u[name instanceof Variable ? name.name : name] === 1;
|
|
145
|
+
}
|
|
146
|
+
get(env) {
|
|
147
|
+
return env.values[this.name];
|
|
148
|
+
}
|
|
149
|
+
unify(val, ls, rs, env) {
|
|
150
|
+
if (this.name in env.values) {
|
|
151
|
+
// isBound
|
|
152
|
+
ls.push(env.values[this.name]);
|
|
153
|
+
rs.push(val);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
if (val === _ || val === this) return true;
|
|
157
|
+
if (val instanceof Variable) {
|
|
158
|
+
if (val.name in env.values) {
|
|
159
|
+
// isBound
|
|
160
|
+
env.bindVal(this.name, env.values[val.name]);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
env.bindVar(this.name, val.name);
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
env.bindVal(this.name, val);
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const isVariable = x => x instanceof Variable,
|
|
172
|
+
variable = name => new Variable(name);
|
|
173
|
+
|
|
174
|
+
export {Env, Unifier, isUnifier, Variable, variable, isVariable, _, any};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {any, _} from './env.js';
|
|
2
|
+
import unify from './unify.js';
|
|
3
|
+
import originalClone from './traverse/clone.js';
|
|
4
|
+
import preprocess from './traverse/preprocess.js';
|
|
5
|
+
|
|
6
|
+
const defaultOptions = {circular: true};
|
|
7
|
+
const equal = (a, b, options = defaultOptions) => !!unify(a, b, null, options);
|
|
8
|
+
|
|
9
|
+
const defaultMatchOptions = {openObjects: true, openMaps: true, openSets: true, circular: true};
|
|
10
|
+
const match = (object, pattern, options = defaultMatchOptions) =>
|
|
11
|
+
!!unify(object, preprocess(pattern, options), null, {
|
|
12
|
+
circular: options.circular,
|
|
13
|
+
symbols: options.symbols,
|
|
14
|
+
loose: options.loose,
|
|
15
|
+
ignoreFunctions: options.ignoreFunctions
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const clone = (a, options = defaultOptions) => originalClone(a, null, options);
|
|
19
|
+
|
|
20
|
+
export {equal, clone, match, match as isShape, any, _};
|
|
21
|
+
export default equal;
|