Skip to content

Commit

Permalink
fix: getting there
Browse files Browse the repository at this point in the history
  • Loading branch information
erights committed Aug 26, 2022
1 parent ac4584c commit 3363055
Show file tree
Hide file tree
Showing 2 changed files with 194 additions and 90 deletions.
274 changes: 189 additions & 85 deletions packages/store/src/patterns/patternMatchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {

const { quote: q, details: X } = assert;
const { entries, values } = Object;
const { ownKeys } = Reflect;

/** @type WeakSet<Pattern> */
const patternMemo = new WeakSet();
Expand Down Expand Up @@ -561,13 +562,43 @@ const makePatternKit = () => {

const checkIsLimitPayload = (payload, mainPayloadShape, check, label) => {
assert(Array.isArray(mainPayloadShape));
const payloadLimitShape = harden(
M.split(
mainPayloadShape,
M.partial(harden([M.recordOf(M.string(), M.number())]), harden([])),
),
if (!Array.isArray(payload)) {
return check(false, X`${q(label)} payload must be an array: ${payload}`);
}

// Was the following, but its overuse of patterns caused an infinite regress
// const payloadLimitShape = harden(
// M.split(
// mainPayloadShape,
// M.partial(harden([M.recordOf(M.string(), M.number())]), harden([])),
// ),
// );
// return checkMatches(payload, payloadLimitShape, check, label);

const mainLength = mainPayloadShape.length;
if (payload.length < mainLength || payload.length > mainLength + 1) {
return check(false, X`${q(label)} payload unexpected size: ${payload}`);
}
const limits = payload[mainLength];
payload = harden(payload.slice(0, mainLength));
if (!checkMatches(payload, mainPayloadShape, check, label)) {
return false;
}
if (limits === undefined) {
return true;
}
return (
check(
passStyleOf(limits) === 'copyRecord',
X`Limits must be a record: ${q(limits)}`,
) &&
entries(limits).every(([key, value]) =>
check(
passStyleOf(value) === 'number',
X`Value of limit ${q(key)} but be a number: ${q(value)}`,
),
)
);
return checkMatches(payload, payloadLimitShape, check, label);
};

// /////////////////////// Match Helpers /////////////////////////////////////
Expand Down Expand Up @@ -857,6 +888,57 @@ const makePatternKit = () => {
checkKeyPattern(rightOperand, check),
});

/** @type {MatchHelper} */
const matchRecordOfHelper = Far('match:recordOf helper', {
checkMatches: (
specimen,
[keyPatt, valuePatt, limits = undefined],
check = x => x,
) => {
const { numPropertiesLimit, propertyNameLengthLimit } = limit(limits);
return (
check(
passStyleOf(specimen) === 'copyRecord',
X`${specimen} - Must be a record`,
) &&
check(
ownKeys(specimen).length <= numPropertiesLimit,
X`Must not have more than ${q(
numPropertiesLimit,
)} properties: ${specimen}`,
) &&
entries(specimen).every(
([key, value]) =>
check(
key.length <= propertyNameLengthLimit,
X`Property name ${q(key)} but not be longer than ${q(
propertyNameLengthLimit,
)}`,
) &&
checkMatches(
harden([key, value]),
harden([keyPatt, valuePatt]),
check,
key,
),
)
);
},

checkIsMatcherPayload: (payload, check = x => x) =>
checkIsLimitPayload(
payload,
harden([M.pattern(), M.pattern()]),
check,
'match:recordOf payload',
),

getRankCover: _entryPatt => getPassStyleCover('copyRecord'),

checkKeyPattern: (_entryPatt, check = x => x) =>
check(false, X`Records not yet supported as keys`),
});

/** @type {MatchHelper} */
const matchArrayOfHelper = Far('match:arrayOf helper', {
checkMatches: (specimen, [subPatt, limits = undefined], check = x => x) => {
Expand Down Expand Up @@ -889,41 +971,32 @@ const makePatternKit = () => {
});

/** @type {MatchHelper} */
const matchRecordOfHelper = Far('match:recordOf helper', {
checkMatches: (specimen, entryPatt, check = x => x) =>
check(
passStyleOf(specimen) === 'copyRecord',
X`${specimen} - Must be a record`,
) &&
entries(specimen).every(el =>
checkMatches(harden(el), entryPatt, check, el[0]),
),
const matchSetOfHelper = Far('match:setOf helper', {
checkMatches: (specimen, [keyPatt, limits = undefined], check = x => x) => {
const { numSetElementsLimit } = limit(limits);
return (
check(
passStyleOf(specimen) === 'tagged' && getTag(specimen) === 'copySet',
X`${specimen} - Must be a CopySet`,
) &&
check(
specimen.payload.length < numSetElementsLimit,
X`Set must not have more than ${q(numSetElementsLimit)} elements: ${
specimen.payload.length
}`,
) &&
specimen.payload.every((el, i) => checkMatches(el, keyPatt, check, i))
);
},

checkIsMatcherPayload: (entryPatt, check = x => x) =>
checkMatches(
entryPatt,
harden([M.pattern(), M.pattern()]),
checkIsMatcherPayload: (payload, check = x => x) =>
checkIsLimitPayload(
payload,
harden([M.pattern()]),
check,
'match:recordOf payload',
'match:setOf payload',
),

getRankCover: _entryPatt => getPassStyleCover('copyRecord'),

checkKeyPattern: (_entryPatt, check = x => x) =>
check(false, X`Records not yet supported as keys`),
});

/** @type {MatchHelper} */
const matchSetOfHelper = Far('match:setOf helper', {
checkMatches: (specimen, keyPatt, check = x => x) =>
check(
passStyleOf(specimen) === 'tagged' && getTag(specimen) === 'copySet',
X`${specimen} - Must be a a CopySet`,
) &&
specimen.payload.every((el, i) => checkMatches(el, keyPatt, check, i)),

checkIsMatcherPayload: checkPattern,

getRankCover: () => getPassStyleCover('tagged'),

checkKeyPattern: (_, check = x => x) =>
Expand All @@ -932,20 +1005,40 @@ const makePatternKit = () => {

/** @type {MatchHelper} */
const matchBagOfHelper = Far('match:bagOf helper', {
checkMatches: (specimen, [keyPatt, countPatt], check = x => x) =>
check(
passStyleOf(specimen) === 'tagged' && getTag(specimen) === 'copyBag',
X`${specimen} - Must be a a CopyBag`,
) &&
specimen.payload.every(
([key, count], i) =>
checkMatches(key, keyPatt, check, `keys[${i}]`) &&
checkMatches(count, countPatt, check, `counts[${i}]`),
),
checkMatches: (
specimen,
[keyPatt, countPatt, limits = undefined],
check = x => x,
) => {
const { numUniqueBagElementsLimit, decimalDigitsLimit } = limit(limits);
return (
check(
passStyleOf(specimen) === 'tagged' && getTag(specimen) === 'copyBag',
X`${specimen} - Must be a CopyBag`,
) &&
check(
specimen.payload.length <= numUniqueBagElementsLimit,
X`Bag must not have more than ${q(
numUniqueBagElementsLimit,
)} unique elements: ${specimen}`,
) &&
specimen.payload.every(
([key, count], i) =>
checkMatches(key, keyPatt, check, `keys[${i}]`) &&
check(
`${count}`.length <= decimalDigitsLimit,
X`Each bag element may be appear at most ${q(
decimalDigitsLimit,
)} times: ${specimen}`,
) &&
checkMatches(count, countPatt, check, `counts[${i}]`),
)
);
},

checkIsMatcherPayload: (entryPatt, check = x => x) =>
checkMatches(
entryPatt,
checkIsMatcherPayload: (payload, check = x => x) =>
checkIsLimitPayload(
payload,
harden([M.pattern(), M.pattern()]),
check,
'match:bagOf payload',
Expand All @@ -959,21 +1052,35 @@ const makePatternKit = () => {

/** @type {MatchHelper} */
const matchMapOfHelper = Far('match:mapOf helper', {
checkMatches: (specimen, [keyPatt, valuePatt], check = x => x) =>
check(
passStyleOf(specimen) === 'tagged' && getTag(specimen) === 'copyMap',
X`${specimen} - Must be a CopyMap`,
) &&
specimen.payload.keys.every((k, i) =>
checkMatches(k, keyPatt, check, `keys[${i}]`),
) &&
specimen.payload.values.every((v, i) =>
checkMatches(v, valuePatt, check, `values[${i}]`),
),
checkMatches: (
specimen,
[keyPatt, valuePatt, limits = undefined],
check = x => x,
) => {
const { numMapEntriesLimit } = limit(limits);
return (
check(
passStyleOf(specimen) === 'tagged' && getTag(specimen) === 'copyMap',
X`${specimen} - Must be a CopyMap`,
) &&
check(
specimen.payload.keys.length <= numMapEntriesLimit,
X`CopyMap must have no more than ${q(
numMapEntriesLimit,
)} entries: ${specimen}`,
) &&
specimen.payload.keys.every((k, i) =>
checkMatches(k, keyPatt, check, `keys[${i}]`),
) &&
specimen.payload.values.every((v, i) =>
checkMatches(v, valuePatt, check, `values[${i}]`),
)
);
},

checkIsMatcherPayload: (entryPatt, check = x => x) =>
checkMatches(
entryPatt,
checkIsMatcherPayload: (payload, check = x => x) =>
checkIsLimitPayload(
payload,
harden([M.pattern(), M.pattern()]),
check,
'match:mapOf payload',
Expand Down Expand Up @@ -1159,10 +1266,6 @@ const makePatternKit = () => {
const NatShape = makeMatcher('match:gte', 0n);
const StringShape = makeKindMatcher('string');
const SymbolShape = makeKindMatcher('symbol');
const RecordShape = makeKindMatcher('copyRecord');
const SetShape = makeKindMatcher('copySet');
const BagShape = makeKindMatcher('copyBag');
const MapShape = makeKindMatcher('copyMap');
const RemotableShape = makeKindMatcher('remotable');
const ErrorShape = makeKindMatcher('error');
const PromiseShape = makeKindMatcher('promise');
Expand Down Expand Up @@ -1239,15 +1342,15 @@ const makePatternKit = () => {
kind: makeKindMatcher,
boolean: () => BooleanShape,
number: () => NumberShape,
bigint: () => BigintShape,
nat: () => NatShape,
string: () => StringShape,
symbol: () => SymbolShape,
record: () => RecordShape,
bigint: (_limits = undefined) => BigintShape,
nat: (_limits = undefined) => NatShape,
string: (_limits = undefined) => StringShape,
symbol: (_limits = undefined) => SymbolShape,
record: (limits = undefined) => M.recordOf(M.any(), M.any(), limits),
array: (limits = undefined) => M.arrayOf(M.any(), limits),
set: () => SetShape,
bag: () => BagShape,
map: () => MapShape,
set: (limits = undefined) => M.setOf(M.any(), limits),
bag: (limits = undefined) => M.bagOf(M.any(), limits),
map: (limits = undefined) => M.mapOf(M.any(), M.any(), limits),
remotable: makeRemotableMatcher,
error: () => ErrorShape,
promise: () => PromiseShape,
Expand All @@ -1264,18 +1367,19 @@ const makePatternKit = () => {
gte: rightOperand => makeMatcher('match:gte', rightOperand),
gt: rightOperand => makeMatcher('match:gt', rightOperand),

recordOf: (keyPatt = M.any(), valuePatt = M.any(), limits = undefined) =>
makeMatcher('match:recordOf', [keyPatt, valuePatt, limits]),
arrayOf: (subPatt = M.any(), limits = undefined) =>
makeMatcher('match:arrayOf', [subPatt, limits]),
recordOf: (keyPatt = M.any(), valuePatt = M.any()) =>
makeMatcher('match:recordOf', [keyPatt, valuePatt]),
setOf: (keyPatt = M.any()) => makeMatcher('match:setOf', keyPatt),
bagOf: (keyPatt = M.any(), countPatt = M.any()) =>
makeMatcher('match:bagOf', [keyPatt, countPatt]),
mapOf: (keyPatt = M.any(), valuePatt = M.any()) =>
makeMatcher('match:mapOf', [keyPatt, valuePatt]),
split: (base, rest = undefined) =>
setOf: (keyPatt = M.any(), limits = undefined) =>
makeMatcher('match:setOf', [keyPatt, limits]),
bagOf: (keyPatt = M.any(), countPatt = M.any(), limits = undefined) =>
makeMatcher('match:bagOf', [keyPatt, countPatt, limits]),
mapOf: (keyPatt = M.any(), valuePatt = M.any(), limits = undefined) =>
makeMatcher('match:mapOf', [keyPatt, valuePatt, limits]),
split: (base, rest = undefined, _limits = undefined) =>
makeMatcher('match:split', rest === undefined ? [base] : [base, rest]),
partial: (base, rest = undefined) =>
partial: (base, rest = undefined, _limits = undefined) =>
makeMatcher('match:partial', rest === undefined ? [base] : [base, rest]),

eref: t => M.or(t, M.promise()),
Expand Down
10 changes: 5 additions & 5 deletions packages/store/test/test-patterns.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ const matchTests = harden([
[M.partial([5]), 'optional-parts: [3] - Must be: [5]'],

[M.scalar(), 'A "copyArray" cannot be a scalar key: [3,4]'],
[M.set(), 'copyArray [3,4] - Must be a copySet'],
[M.set(), '[3,4] - Must be a CopySet'],
[M.arrayOf(M.string()), '[0]: number 3 - Must be a string'],
],
},
Expand Down Expand Up @@ -169,7 +169,7 @@ const matchTests = harden([
],

[M.scalar(), 'A "copyRecord" cannot be a scalar key: {"foo":3,"bar":4}'],
[M.map(), 'copyRecord {"foo":3,"bar":4} - Must be a copyMap'],
[M.map(), '{"foo":3,"bar":4} - Must be a CopyMap'],
[
M.recordOf(M.number(), M.number()),
'foo: [0]: string "foo" - Must be a number',
Expand All @@ -194,7 +194,7 @@ const matchTests = harden([
[makeCopySet([3, 4, 5]), '"[copySet]" - Must be: "[copySet]"'],
[M.lte(makeCopySet([])), '"[copySet]" - Must be <= "[copySet]"'],
[M.gte(makeCopySet([3, 4, 5])), '"[copySet]" - Must be >= "[copySet]"'],
[M.bag(), 'copySet "[copySet]" - Must be a copyBag'],
[M.bag(), '"[copySet]" - Must be a CopyBag'],
[M.setOf(M.string()), '[0]: number 4 - Must be a string'],
],
},
Expand Down Expand Up @@ -252,8 +252,8 @@ const matchTests = harden([
M.mapOf(M.record(), M.string()),
],
noPatterns: [
[M.bag(), 'copyMap "[copyMap]" - Must be a copyBag'],
[M.set(), 'copyMap "[copyMap]" - Must be a copySet'],
[M.bag(), '"[copyMap]" - Must be a CopyBag'],
[M.set(), '"[copyMap]" - Must be a CopySet'],
[
M.mapOf(M.string(), M.string()),
'keys[0]: copyRecord {"foo":3} - Must be a string',
Expand Down

0 comments on commit 3363055

Please sign in to comment.