Skip to content

Commit

Permalink
fix: amountMath does amount patterns
Browse files Browse the repository at this point in the history
  • Loading branch information
erights committed Jan 1, 2021
1 parent 9cc1f5b commit 19e184e
Show file tree
Hide file tree
Showing 24 changed files with 605 additions and 48 deletions.
77 changes: 72 additions & 5 deletions packages/ERTP/src/amountMath.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
// @ts-check

import { assert, details } from '@agoric/assert';
import { assert, details as d } from '@agoric/assert';

import { mustBeComparable } from '@agoric/same-structure';
import {
mustBeComparable,
patternKindOf,
STAR_PATTERN,
} from '@agoric/same-structure';

import './types';
import natMathHelpers from './mathHelpers/natMathHelpers';
Expand Down Expand Up @@ -87,7 +91,7 @@ function makeAmountMath(brand, amountMathKind) {
const helpers = mathHelpers[amountMathKind];
assert(
helpers !== undefined,
details`unrecognized amountMathKind: ${amountMathKind}`,
d`unrecognized amountMathKind: ${amountMathKind}`,
);

// Cache the amount if we can.
Expand Down Expand Up @@ -126,11 +130,11 @@ function makeAmountMath(brand, amountMathKind) {
const { brand: allegedBrand, value } = allegedAmount;
assert(
allegedBrand !== undefined,
details`alleged brand is undefined. Did you pass a value rather than an amount?`,
d`alleged brand is undefined. Did you pass a value rather than an amount?`,
);
assert(
brand === allegedBrand,
details`the brand in the allegedAmount in 'coerce' didn't match the amountMath brand`,
d`the brand in the allegedAmount in 'coerce' didn't match the amountMath brand`,
);
// Will throw on inappropriate value
return amountMath.make(value);
Expand Down Expand Up @@ -181,6 +185,69 @@ function makeAmountMath(brand, amountMathKind) {
amountMath.getValue(rightAmount),
),
),

/**
* TODO explain.
*
* @param {ValuePattern} valuePattern
* @returns {AmountPattern}
*/
makePattern: valuePattern => {
mustBeComparable(valuePattern);
const patternKind = patternKindOf(valuePattern);
if (patternKind === undefined) {
return amountMath.make(valuePattern);
}
return harden({ brand, value: valuePattern });
},

/**
* TODO explain.
*
* @returns {AmountPattern}
*/
makeStarPattern: () => {
return amountMath.makePattern(STAR_PATTERN);
},

/**
* TODO explain.
*
* @param {AmountPattern} allegedAmountPattern
* @returns {AmountPattern} or throws if invalid
*/
coercePattern: allegedAmountPattern => {
const { brand: allegedBrand, value: valuePattern } = allegedAmountPattern;
assert(
allegedBrand !== undefined,
d`alleged brand is undefined. Did you pass a value rather than an amount?`,
);
assert(
brand === allegedBrand,
d`the brand in the allegedAmount in 'coerce' didn't match the amountMath brand`,
);
// Will throw on inappropriate value
return amountMath.makePattern(valuePattern);
},

// TODO explain.
getValuePattern: amountPattern =>
amountMath.coercePattern(amountPattern).value,

frugalSplit: (pattern, specimen) => {
const split = helpers.doFrugalSplit(
amountMath.getValuePattern(pattern),
amountMath.getValue(specimen),
);
if (split === undefined) {
return undefined;
}
const { matched: valueMatched, change: valueChange } = split;
return harden({
matched: amountMath.make(valueMatched),
change: amountMath.make(valueChange),
});
},
});
const empty = amountMath.make(helpers.doGetEmpty());
return amountMath;
Expand Down
27 changes: 27 additions & 0 deletions packages/ERTP/src/mathHelpers/natMathHelpers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// @ts-check

import Nat from '@agoric/nat';
import { assert, details as d, q } from '@agoric/assert';
import { patternKindOf } from '@agoric/same-structure';

import '../types';

Expand All @@ -26,6 +28,31 @@ const natMathHelpers = harden({
doIsEqual: (left, right) => left === right,
doAdd: (left, right) => Nat(left + right),
doSubtract: (left, right) => Nat(left - right),

doFrugalSplit: (pattern, specimen) => {
const patternKind = patternKindOf(pattern);
if (patternKind === undefined) {
Nat(pattern);
if (specimen >= pattern) {
return harden({
matched: pattern,
change: specimen - pattern,
});
}
return undefined;
}
switch (patternKind) {
case '*': {
return harden({
matched: identity,
change: specimen,
});
}
default: {
throw assert.fail(d`Unexpected patternKind ${q(patternKind)}`);
}
}
},
});

harden(natMathHelpers);
Expand Down
42 changes: 38 additions & 4 deletions packages/ERTP/src/mathHelpers/setMathHelpers.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// @ts-check

import { passStyleOf } from '@agoric/marshal';
import { assert, details } from '@agoric/assert';
import { sameStructure } from '@agoric/same-structure';
import { assert, details as d } from '@agoric/assert';
import { sameStructure, isGround, match } from '@agoric/same-structure';

import '../types';

const { entries } = Object;

// Operations for arrays with unique objects identifying and providing
// information about digital assets. Used for Zoe invites.
const identity = harden([]);
Expand Down Expand Up @@ -43,7 +45,7 @@ const checkForDupes = buckets => {
for (let j = i + 1; j < maybeMatches.length; j += 1) {
assert(
!sameStructure(maybeMatches[i], maybeMatches[j]),
details`value has duplicates: ${maybeMatches[i]} and ${maybeMatches[j]}`,
d`value has duplicates: ${maybeMatches[i]} and ${maybeMatches[j]}`,
);
}
}
Expand Down Expand Up @@ -92,12 +94,44 @@ const setMathHelpers = harden({
right.forEach(rightElem => {
assert(
hasElement(leftBuckets, rightElem),
details`right element ${rightElem} was not in left`,
d`right element ${rightElem} was not in left`,
);
});
const leftElemNotInRight = leftElem => !hasElement(rightBuckets, leftElem);
return harden(left.filter(leftElemNotInRight));
},

// Do a case split among easy cases, and error on the rest for now.
// TODO Actually implement this correctly.
doFrugalSplit: (pattern, specimen) => {
if (isGround(pattern)) {
if (setMathHelpers.doIsGTE(specimen, pattern)) {
return harden({
matched: pattern,
change: setMathHelpers.doSubtract(specimen, pattern),
});
}
return undefined;
}
// Check for the special case where the pattern is a singleton array
if (Array.isArray(pattern) && pattern.length === 1) {
const subPattern = pattern[0];
for (const [i, elem] of entries(specimen)) {
if (match(subPattern, elem)) {
// This just takes the first match, which is rather arbitrary.
// At the level of abstraction where these lists represent
// unordered sets, this is choice is non-deterministic, which
// is inevitable.
return harden({
matched: [elem],
change: [...specimen.slice(0, i), ...specimen.slice(i + 1)],
});
}
}
return undefined;
}
throw assert.fail(d`Only singleton patterns supported for now`);
},
});

harden(setMathHelpers);
Expand Down
70 changes: 63 additions & 7 deletions packages/ERTP/src/mathHelpers/strSetMathHelpers.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
// @ts-check

import { passStyleOf } from '@agoric/marshal';
import { assert, details } from '@agoric/assert';
import { assert, details as d, q } from '@agoric/assert';
import { patternKindOf } from '@agoric/same-structure';

const identity = harden([]);

const checkForDupes = list => {
const set = new Set(list);
assert(set.size === list.length, details`value has duplicates: ${list}`);
assert(set.size === list.length, d`value has duplicates: ${list}`);
};

/**
Expand Down Expand Up @@ -43,10 +44,7 @@ const strSetMathHelpers = harden({
doAdd: (left, right) => {
const union = new Set(left);
const addToUnion = elem => {
assert(
!union.has(elem),
details`left and right have same element ${elem}`,
);
assert(!union.has(elem), d`left and right have same element ${elem}`);
union.add(elem);
};
right.forEach(addToUnion);
Expand All @@ -58,10 +56,68 @@ const strSetMathHelpers = harden({
const allRemovedCorrectly = right.every(remove);
assert(
allRemovedCorrectly,
details`some of the elements in right (${right}) were not present in left (${left})`,
d`some of the elements in right (${right}) were not present in left (${left})`,
);
return harden(Array.from(leftSet));
},

// TODO Reform awful code!
// Expanded this in place this way only as part of an expedient
// spike. It is indeed a horrible clump of code that must be broken up.
doFrugalSplit: (pattern, specimen) => {
const patternKind = patternKindOf(pattern);
if (patternKind === undefined) {
const changeSet = new Set(specimen);
let starCount = 0;
const remove = subPattern => {
const subPatternKind = patternKindOf(subPattern);
if (subPatternKind === undefined) {
return changeSet.delete(subPattern);
}
switch (subPatternKind) {
case '*': {
starCount += 1;
break;
}
default: {
throw assert.fail(
d`Unexpected subPatternKind ${q(subPatternKind)}`,
);
}
}
return true;
};
if (!pattern.every(remove)) {
return undefined;
}
if (changeSet.size < starCount) {
return undefined;
}
for (const choice of changeSet) {
changeSet.delete(choice);
starCount -= 1;
if (starCount === 0) {
break;
}
}
const change = harden(Array.from(changeSet));
return harden({
matched: strSetMathHelpers.doSubtract(specimen, change),
change,
});
}
switch (patternKind) {
case '*': {
return harden({
matched: identity,
change: specimen,
});
}
default: {
throw assert.fail(d`Unexpected patternKind ${q(patternKind)}`);
}
}
},
});

harden(strSetMathHelpers);
Expand Down
Loading

0 comments on commit 19e184e

Please sign in to comment.