Skip to content

Commit f2ec8de

Browse files
committed
feat(patterns): M.has(el,n) to support want patterns
1 parent 895ea0a commit f2ec8de

File tree

3 files changed

+126
-0
lines changed

3 files changed

+126
-0
lines changed

packages/patterns/src/patterns/patternMatchers.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,6 +1258,65 @@ const makePatternKit = () => {
12581258
getRankCover: () => getPassStyleCover('tagged'),
12591259
});
12601260

1261+
/** @type {MatchHelper} */
1262+
const matchHasHelper = Far('match:has helper', {
1263+
checkMatches: (
1264+
specimen,
1265+
[elementPatt, countPatt, limits = undefined],
1266+
check,
1267+
) => {
1268+
let count = 0n;
1269+
const kind = kindOf(specimen, check);
1270+
switch (kind) {
1271+
case 'copyArray': {
1272+
for (const element of specimen) {
1273+
if (matches(element, elementPatt)) {
1274+
count += 1n;
1275+
}
1276+
}
1277+
break;
1278+
}
1279+
case 'copySet': {
1280+
for (const element of specimen.payload) {
1281+
if (matches(element, elementPatt)) {
1282+
count += 1n;
1283+
}
1284+
}
1285+
break;
1286+
}
1287+
case 'copyBag': {
1288+
for (const [element, num] of specimen.payload) {
1289+
if (matches(element, elementPatt)) {
1290+
count += num;
1291+
}
1292+
}
1293+
break;
1294+
}
1295+
default: {
1296+
return check(false, X`unexpected ${q(kind)}`);
1297+
}
1298+
}
1299+
const { decimalDigitsLimit } = limit(limits);
1300+
return (
1301+
applyLabelingError(
1302+
checkDecimalDigitsLimit,
1303+
[count, decimalDigitsLimit, check],
1304+
`${kind} matches`,
1305+
) && checkMatches(count, countPatt, check, `${kind} matches`)
1306+
);
1307+
},
1308+
1309+
checkIsWellFormed: (payload, check) =>
1310+
checkIsWellFormedWithLimit(
1311+
payload,
1312+
harden([MM.pattern(), MM.pattern()]),
1313+
check,
1314+
'match:has payload',
1315+
),
1316+
1317+
getRankCover: () => getPassStyleCover('tagged'),
1318+
});
1319+
12611320
/** @type {MatchHelper} */
12621321
const matchMapOfHelper = Far('match:mapOf helper', {
12631322
checkMatches: (
@@ -1548,6 +1607,7 @@ const makePatternKit = () => {
15481607
'match:recordOf': matchRecordOfHelper,
15491608
'match:setOf': matchSetOfHelper,
15501609
'match:bagOf': matchBagOfHelper,
1610+
'match:has': matchHasHelper,
15511611
'match:mapOf': matchMapOfHelper,
15521612
'match:splitArray': matchSplitArrayHelper,
15531613
'match:splitRecord': matchSplitRecordHelper,
@@ -1702,6 +1762,8 @@ const makePatternKit = () => {
17021762
makeLimitsMatcher('match:setOf', [keyPatt, limits]),
17031763
bagOf: (keyPatt = M.any(), countPatt = M.any(), limits = undefined) =>
17041764
makeLimitsMatcher('match:bagOf', [keyPatt, countPatt, limits]),
1765+
has: (elementPatt = M.any(), countPatt = MM.gte(1n), limits = undefined) =>
1766+
makeLimitsMatcher('match:has', [elementPatt, countPatt, limits]),
17051767
mapOf: (keyPatt = M.any(), valuePatt = M.any(), limits = undefined) =>
17061768
makeLimitsMatcher('match:mapOf', [keyPatt, valuePatt, limits]),
17071769
splitArray: (base, optional = undefined, rest = undefined) =>

packages/patterns/src/types.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,13 @@ export {};
380380
* `countPatt` is expected to rarely be useful,
381381
* but is provided to minimize surprise.
382382
*
383+
* @property {(elementPatt?: Pattern,
384+
* countPatt?: Pattern,
385+
* limits?: Limits
386+
* ) => Matcher} has
387+
* Matches any array, CopySet, or CopyBag in which the bigint number of
388+
* elements that match `elementPatt` is a number that matches `countPatt`.
389+
*
383390
* @property {(keyPatt?: Pattern,
384391
* valuePatt?: Pattern,
385392
* limits?: Limits

packages/patterns/test/patterns.test.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,12 @@ const runTests = (t, successCase, failCase) => {
199199

200200
successCase(specimen, M.arrayOf(M.number()));
201201

202+
successCase(specimen, M.has(3));
203+
successCase(specimen, M.has(3, 1n));
204+
successCase(specimen, M.has(M.number(), 2n));
205+
successCase(specimen, M.has('c', 0n));
206+
successCase(specimen, M.has('c', M.nat()));
207+
202208
failCase(specimen, [4, 3], '[3,4] - Must be: [4,3]');
203209
failCase(specimen, [3], '[3,4] - Must be: [3]');
204210
failCase(
@@ -227,6 +233,17 @@ const runTests = (t, successCase, failCase) => {
227233
M.arrayOf(M.string()),
228234
'[0]: number 3 - Must be a string',
229235
);
236+
237+
failCase(
238+
specimen,
239+
M.has(3, 1),
240+
'copyArray matches: "[1n]" - Must be: 1',
241+
);
242+
failCase(
243+
specimen,
244+
M.has('c'),
245+
'copyArray matches: "[0n]" - Must be >= "[1n]"',
246+
);
230247
}
231248
{
232249
const specimen = { foo: 3, bar: 4 };
@@ -419,6 +436,12 @@ const runTests = (t, successCase, failCase) => {
419436
successCase(specimen, M.lte(makeCopySet([3, 4, 5])));
420437
successCase(specimen, M.setOf(M.number()));
421438

439+
successCase(specimen, M.has(3));
440+
successCase(specimen, M.has(3, 1n));
441+
successCase(specimen, M.has(M.number(), 2n));
442+
successCase(specimen, M.has('c', 0n));
443+
successCase(specimen, M.has('c', M.nat()));
444+
422445
failCase(specimen, makeCopySet([]), '"[copySet]" - Must be: "[copySet]"');
423446
failCase(
424447
specimen,
@@ -440,6 +463,17 @@ const runTests = (t, successCase, failCase) => {
440463
M.setOf(M.string()),
441464
'set elements[0]: number 4 - Must be a string',
442465
);
466+
467+
failCase(
468+
specimen,
469+
M.has(3, 1),
470+
'copySet matches: "[1n]" - Must be: 1',
471+
);
472+
failCase(
473+
specimen,
474+
M.has('c'),
475+
'copySet matches: "[0n]" - Must be >= "[1n]"',
476+
);
443477
}
444478
{
445479
const specimen = makeCopyBag([
@@ -472,6 +506,13 @@ const runTests = (t, successCase, failCase) => {
472506
);
473507
successCase(specimen, M.bagOf(M.string()));
474508
successCase(specimen, M.bagOf(M.string(), M.lt(5n)));
509+
successCase(specimen, M.bagOf(M.string(), M.gte(2n)));
510+
511+
successCase(specimen, M.has('a'));
512+
successCase(specimen, M.has('a', 2n));
513+
successCase(specimen, M.has(M.string(), 5n));
514+
successCase(specimen, M.has('c', 0n));
515+
successCase(specimen, M.has('c', M.nat()));
475516

476517
failCase(
477518
specimen,
@@ -509,6 +550,22 @@ const runTests = (t, successCase, failCase) => {
509550
M.bagOf(M.any(), M.gt(2n)),
510551
'bag counts[1]: "[2n]" - Must be > "[2n]"',
511552
);
553+
554+
failCase(
555+
specimen,
556+
M.has('a', 1n),
557+
'copyBag matches: "[2n]" - Must be: "[1n]"',
558+
);
559+
failCase(
560+
specimen,
561+
M.has(M.string(), M.lte(4n)),
562+
'copyBag matches: "[5n]" - Must be <= "[4n]"',
563+
);
564+
failCase(
565+
specimen,
566+
M.has('c'),
567+
'copyBag matches: "[0n]" - Must be >= "[1n]"',
568+
);
512569
}
513570
{
514571
const specimen = makeCopyMap([

0 commit comments

Comments
 (0)