1+ /* eslint-disable @endo/no-optional-chaining */
12// @ts -nocheck So many errors that the suppressions hamper readability.
23// TODO parameterize MatchHelper which will solve most of them
34import {
@@ -37,8 +38,8 @@ import {
3738import { generateCollectionPairEntries } from '../keys/keycollection-operators.js' ;
3839
3940/**
40- * @import {Checker, CopyRecord, CopyTagged, Passable} from '@endo/pass-style'
41- * @import {ArgGuard, AwaitArgGuard, CheckPattern, GetRankCover, InterfaceGuard, MatcherNamespace, MethodGuard, MethodGuardMaker, Pattern, RawGuard, SyncValueGuard, Kind, Limits, AllLimits, Key, DefaultGuardType} from '../types.js'
41+ * @import {Checker, CopyArray, CopyRecord, CopyTagged, Passable} from '@endo/pass-style'
42+ * @import {CopySet, CopyBag, ArgGuard, AwaitArgGuard, CheckPattern, GetRankCover, InterfaceGuard, MatcherNamespace, MethodGuard, MethodGuardMaker, Pattern, RawGuard, SyncValueGuard, Kind, Limits, AllLimits, Key, DefaultGuardType} from '../types.js'
4243 * @import {MatchHelper, PatternKit} from './types.js'
4344 */
4445
@@ -1258,6 +1259,177 @@ const makePatternKit = () => {
12581259 getRankCover : ( ) => getPassStyleCover ( 'tagged' ) ,
12591260 } ) ;
12601261
1262+ /**
1263+ * @param {CopyArray } elements
1264+ * @param {Pattern } elementPatt
1265+ * @param {bigint } bound Must be >= 1n
1266+ * @param {CopyArray } [inResults]
1267+ * @param {CopyArray } [outResults]
1268+ * @param {Checker } [check]
1269+ * @returns {boolean }
1270+ */
1271+ const elementsHasSplit = (
1272+ elements ,
1273+ elementPatt ,
1274+ bound ,
1275+ inResults = undefined ,
1276+ outResults = undefined ,
1277+ check = identChecker ,
1278+ ) => {
1279+ let count = 0n ;
1280+ // Since this feature is motivated by ERTP's use on
1281+ // non-fungible (`set`, `copySet`) amounts,
1282+ // their arrays store their elements in decending lexicographic order.
1283+ // But this function has to make some choice amoung equally good minimal
1284+ // results. It is more intuitive for the choice to be the first `bound`
1285+ // matching elements in ascending lexicigraphic order, rather than
1286+ // decending. Thus we iterate `elements` in reverse order.
1287+ for ( let i = elements . length - 1 ; i >= 0 ; i -= 1 ) {
1288+ const element = elements [ i ] ;
1289+ if ( count < bound ) {
1290+ if ( matches ( element , elementPatt ) ) {
1291+ count += 1n ;
1292+ inResults ?. push ( element ) ;
1293+ } else {
1294+ outResults ?. push ( element ) ;
1295+ }
1296+ } else if ( outResults === undefined ) {
1297+ break ;
1298+ } else {
1299+ outResults . push ( element ) ;
1300+ }
1301+ }
1302+ return check (
1303+ count >= bound ,
1304+ X `Has only ${ q ( count ) } matches, but needs ${ q ( bound ) } ` ,
1305+ ) ;
1306+ } ;
1307+
1308+ /**
1309+ * @param {CopyArray<[Key, bigint]> } pairs
1310+ * @param {Pattern } elementPatt
1311+ * @param {bigint } bound Must be >= 1n
1312+ * @param {CopyArray<[Key, bigint]> } [inResults]
1313+ * @param {CopyArray<[Key, bigint]> } [outResults]
1314+ * @returns {boolean }
1315+ * @param {Checker } [check]
1316+ */
1317+ const pairsHasSplit = (
1318+ pairs ,
1319+ elementPatt ,
1320+ bound ,
1321+ inResults = undefined ,
1322+ outResults = undefined ,
1323+ check = identChecker ,
1324+ ) => {
1325+ let count = 0n ;
1326+ // Since this feature is motivated by ERTP's use on
1327+ // semi-fungible (`copyBag`) amounts,
1328+ // their arrays store their elements in decending lexicographic order.
1329+ // But this function has to make some choice amoung equally good minimal
1330+ // results. It is more intuitive for the choice to be the first `bound`
1331+ // matching elements in ascending lexicigraphic order, rather than
1332+ // decending. Thus we iterate `pairs` in reverse order.
1333+ for ( let i = pairs . length - 1 ; i >= 0n ; i -= 1 ) {
1334+ const [ element , num ] = pairs [ i ] ;
1335+ const numRest = bound - count ;
1336+ if ( numRest >= 1n ) {
1337+ if ( matches ( element , elementPatt ) ) {
1338+ if ( num <= numRest ) {
1339+ count += num ;
1340+ inResults ?. push ( [ element , num ] ) ;
1341+ } else {
1342+ const numIn = num - numRest ;
1343+ count += numIn ;
1344+ inResults ?. push ( [ element , numIn ] ) ;
1345+ outResults ?. push ( [ element , numRest ] ) ;
1346+ }
1347+ } else {
1348+ outResults ?. push ( [ element , num ] ) ;
1349+ }
1350+ } else if ( outResults === undefined ) {
1351+ break ;
1352+ } else {
1353+ outResults . push ( [ element , num ] ) ;
1354+ }
1355+ }
1356+ return check (
1357+ count >= bound ,
1358+ X `Has only ${ q ( count ) } matches, but needs ${ q ( bound ) } ` ,
1359+ ) ;
1360+ } ;
1361+
1362+ /** @type {MatchHelper } */
1363+ const matchHasHelper = Far ( 'match:has helper' , {
1364+ /**
1365+ * @param {CopyArray | CopySet | CopyBag } specimen
1366+ * @param {[Pattern, bigint, Limits?] } payload
1367+ * @param {Checker } check
1368+ */
1369+ checkMatches : (
1370+ specimen ,
1371+ [ elementPatt , bound , limits = undefined ] ,
1372+ check ,
1373+ ) => {
1374+ const kind = kindOf ( specimen , check ) ;
1375+ const { decimalDigitsLimit } = limit ( limits ) ;
1376+ if (
1377+ ! applyLabelingError (
1378+ checkDecimalDigitsLimit ,
1379+ [ bound , decimalDigitsLimit , check ] ,
1380+ `${ kind } matches` ,
1381+ )
1382+ ) {
1383+ return false ;
1384+ }
1385+ switch ( kind ) {
1386+ case 'copyArray' : {
1387+ return elementsHasSplit (
1388+ specimen ,
1389+ elementPatt ,
1390+ bound ,
1391+ undefined ,
1392+ undefined ,
1393+ check ,
1394+ ) ;
1395+ }
1396+ case 'copySet' : {
1397+ return elementsHasSplit (
1398+ specimen . payload ,
1399+ elementPatt ,
1400+ bound ,
1401+ undefined ,
1402+ undefined ,
1403+ check ,
1404+ ) ;
1405+ }
1406+ case 'copyBag' : {
1407+ return pairsHasSplit (
1408+ specimen . payload ,
1409+ elementPatt ,
1410+ bound ,
1411+ undefined ,
1412+ undefined ,
1413+ check ,
1414+ ) ;
1415+ }
1416+ default : {
1417+ return check ( false , X `unexpected ${ q ( kind ) } ` ) ;
1418+ }
1419+ }
1420+ } ,
1421+
1422+ checkIsWellFormed : ( payload , check ) =>
1423+ checkIsWellFormedWithLimit (
1424+ payload ,
1425+ harden ( [ MM . pattern ( ) , MM . gte ( 1n ) ] ) ,
1426+ check ,
1427+ 'match:has payload' ,
1428+ ) ,
1429+
1430+ getRankCover : ( ) => getPassStyleCover ( 'tagged' ) ,
1431+ } ) ;
1432+
12611433 /** @type {MatchHelper } */
12621434 const matchMapOfHelper = Far ( 'match:mapOf helper' , {
12631435 checkMatches : (
@@ -1548,6 +1720,7 @@ const makePatternKit = () => {
15481720 'match:recordOf' : matchRecordOfHelper ,
15491721 'match:setOf' : matchSetOfHelper ,
15501722 'match:bagOf' : matchBagOfHelper ,
1723+ 'match:has' : matchHasHelper ,
15511724 'match:mapOf' : matchMapOfHelper ,
15521725 'match:splitArray' : matchSplitArrayHelper ,
15531726 'match:splitRecord' : matchSplitRecordHelper ,
@@ -1702,6 +1875,8 @@ const makePatternKit = () => {
17021875 makeLimitsMatcher ( 'match:setOf' , [ keyPatt , limits ] ) ,
17031876 bagOf : ( keyPatt = M . any ( ) , countPatt = M . any ( ) , limits = undefined ) =>
17041877 makeLimitsMatcher ( 'match:bagOf' , [ keyPatt , countPatt , limits ] ) ,
1878+ has : ( elementPatt = M . any ( ) , countPatt = 1n , limits = undefined ) =>
1879+ makeLimitsMatcher ( 'match:has' , [ elementPatt , countPatt , limits ] ) ,
17051880 mapOf : ( keyPatt = M . any ( ) , valuePatt = M . any ( ) , limits = undefined ) =>
17061881 makeLimitsMatcher ( 'match:mapOf' , [ keyPatt , valuePatt , limits ] ) ,
17071882 splitArray : ( base , optional = undefined , rest = undefined ) =>
@@ -1763,6 +1938,8 @@ const makePatternKit = () => {
17631938 getRankCover,
17641939 M,
17651940 kindOf,
1941+ elementsHasSplit,
1942+ pairsHasSplit,
17661943 } ) ;
17671944} ;
17681945
@@ -1781,6 +1958,8 @@ export const {
17811958 getRankCover,
17821959 M,
17831960 kindOf,
1961+ elementsHasSplit,
1962+ pairsHasSplit,
17841963} = makePatternKit ( ) ;
17851964
17861965MM = M ;
0 commit comments