-
Notifications
You must be signed in to change notification settings - Fork 74
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WIP: Event-based encoding API based on Data-E
- Loading branch information
Showing
7 changed files
with
452 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/** @typedef {import('@endo/pass-style').Remotable} Remotable */ | ||
|
||
/** | ||
* @template N | ||
* @template R | ||
* @typedef {object} Builder | ||
* @property {(node: N) => R} buildRoot | ||
* | ||
* @property {() => N} buildUndefined | ||
* @property {() => N} buildNull | ||
* @property {(num: number) => N} buildNumber | ||
* @property {(flag: boolean) => N} buildBoolean | ||
* @property {(bigint: bigint) => N} buildBigint | ||
* @property {(str: string) => N} buildString | ||
* @property {(sym: symbol) => N} buildSymbol | ||
* | ||
* @property {(builtEntries: [string, N][]) => N} buildRecord | ||
* The recognizer must pass the actual property names through. It is | ||
* up to the builder whether it wants to encode them. | ||
* It is up to the recognizer to sort the entries by their actual | ||
* property name first, and to encode their values in the resulting | ||
* sorted order. The builder may assume that sorted order. | ||
* @property {(builtElements: N[]) => N} buildArray | ||
* @property {(tagName: string, builtPayload: N) => N} buildTagged | ||
* The recognizer must pass the actual tagName through. It is | ||
* up to the builder whether it wants to encode it. | ||
* | ||
* @property {(error :Error) => N} buildError | ||
* @property {(remotable: Remotable) => N} buildRemotable | ||
* @property {(promise: Promise) => N} buildPromise | ||
*/ | ||
|
||
/** | ||
* @template E | ||
* @template N | ||
* @template R | ||
* @callback Recognize | ||
* @param {E} encoding | ||
* @param {Builder<N,R>} builder | ||
* @returns {R} | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,223 @@ | ||
/// <reference types="ses"/> | ||
|
||
import { | ||
assertPassableSymbol, | ||
Far, | ||
getErrorConstructor, | ||
hasOwnPropertyOf, | ||
nameForPassableSymbol, | ||
passableSymbolForName, | ||
} from '@endo/pass-style'; | ||
import { | ||
startsSpecial, | ||
encodeStringToSmallcaps as buildString, | ||
} from '../encodeToSmallcaps.js'; | ||
|
||
/** @typedef {import('../encodeToSmallcaps.js').SmallcapsEncoding} SmallcapsEncoding */ | ||
|
||
const { is, fromEntries, entries } = Object; | ||
const { isArray } = Array; | ||
const { ownKeys } = Reflect; | ||
const { quote: q, details: X, Fail } = assert; | ||
|
||
const makeSmallcapsBuilder = () => { | ||
/** @type {Builder<SmallcapsEncoding,SmallcapsEncoding>} */ | ||
const smallcapsBuilder = Far('SmallcapsBuilder', { | ||
buildRoot: node => node, | ||
|
||
buildUndefined: () => '#undefined', | ||
buildNull: () => null, | ||
buildBoolean: flag => flag, | ||
buildNumber: num => { | ||
// Special-case numbers with no digit-based representation. | ||
if (Number.isNaN(num)) { | ||
return '#NaN'; | ||
} else if (num === Infinity) { | ||
return '#Infinity'; | ||
} else if (num === -Infinity) { | ||
return '#-Infinity'; | ||
} | ||
// Pass through everything else, replacing -0 with 0. | ||
return is(num, -0) ? 0 : num; | ||
}, | ||
buildBigint: bigint => { | ||
const str = String(bigint); | ||
return bigint < 0n ? str : `+${str}`; | ||
}, | ||
buildString, | ||
buildSymbol: sym => { | ||
assertPassableSymbol(sym); | ||
const name = /** @type {string} */ (nameForPassableSymbol(sym)); | ||
return `%${name}`; | ||
}, | ||
buildRecord: builtEntries => | ||
// TODO Should be fromUniqueEntries, but utils needs to be | ||
// relocated first. | ||
fromEntries( | ||
builtEntries.map(([name, builtValue]) => [ | ||
buildString(name), | ||
builtValue, | ||
]), | ||
), | ||
buildArray: builtElements => builtElements, | ||
buildTagged: (tagName, builtPayload) => ({ | ||
'#tag': buildString(tagName), | ||
payload: builtPayload, | ||
}), | ||
|
||
// TODO slots and options and all that. Also errorId | ||
buildError: error => ({ | ||
'#error': buildString(error.message), | ||
name: buildString(error.name), | ||
}), | ||
// TODO slots and options and all that. | ||
buildRemotable: _remotable => '$', | ||
// TODO slots and options and all that. | ||
buildPromise: _promise => '&', | ||
}); | ||
return smallcapsBuilder; | ||
}; | ||
harden(makeSmallcapsBuilder); | ||
|
||
/** | ||
* Must be consistent with the full string recognition algorithm. | ||
* Must return what that algorithm would pass to builder.buildString. | ||
* | ||
* @param {string} str | ||
*/ | ||
const recognizeString = str => { | ||
typeof str === 'string' || Fail`${str} must be a string`; | ||
if (!startsSpecial(str)) { | ||
return str; | ||
} | ||
const c = str.charAt(0); | ||
c === '!' || Fail`${str} must encode a string`; | ||
return str.slice(1); | ||
}; | ||
|
||
const makeSmallcapsRecognizer = () => { | ||
const smallcapsRecognizer = (encoding, builder) => { | ||
switch (typeof encoding) { | ||
case 'boolean': { | ||
return builder.buildBoolean(encoding); | ||
} | ||
case 'number': { | ||
return builder.buildNumber(encoding); | ||
} | ||
case 'string': { | ||
if (!startsSpecial(encoding)) { | ||
return builder.buildString(encoding); | ||
} | ||
const c = encoding.charAt(0); | ||
switch (c) { | ||
case '!': { | ||
// un-hilbert-ify the string | ||
return builder.buildString(encoding.slice(1)); | ||
} | ||
case '%': { | ||
return builder.buildSymbol( | ||
passableSymbolForName(encoding.slice(1)), | ||
); | ||
} | ||
case '#': { | ||
switch (encoding) { | ||
case '#undefined': { | ||
return builder.buildUndefined(); | ||
} | ||
case '#NaN': { | ||
return builder.buildNumber(NaN); | ||
} | ||
case '#Infinity': { | ||
return builder.buildNumber(Infinity); | ||
} | ||
case '#-Infinity': { | ||
return builder.buildNumber(-Infinity); | ||
} | ||
default: { | ||
assert.fail(X`unknown constant "${q(encoding)}"`, TypeError); | ||
} | ||
} | ||
} | ||
case '+': | ||
case '-': { | ||
return builder.buildBigint(BigInt(encoding)); | ||
} | ||
case '$': { | ||
// TODO slots and options and all that. | ||
return builder.buildRemotable(Far('dummy')); | ||
} | ||
case '&': { | ||
// TODO slots and options and all that. | ||
return builder.buildPromise(Promise.resolve('dummy')); | ||
} | ||
default: { | ||
throw Fail`Special char ${q( | ||
c, | ||
)} reserved for future use: ${encoding}`; | ||
} | ||
} | ||
} | ||
case 'object': { | ||
if (encoding === null) { | ||
return builder.buildNull(); | ||
} | ||
|
||
if (isArray(encoding)) { | ||
const builtElements = encoding.map(val => | ||
smallcapsRecognizer(val, builder), | ||
); | ||
return builder.buildArray(builtElements); | ||
} | ||
|
||
if (hasOwnPropertyOf(encoding, '#tag')) { | ||
const { '#tag': tag, payload, ...rest } = encoding; | ||
ownKeys(rest).length === 0 || | ||
Fail`#tag record unexpected properties: ${q(ownKeys(rest))}`; | ||
return builder.buildTagged( | ||
recognizeString(tag), | ||
smallcapsRecognizer(payload, builder), | ||
); | ||
} | ||
|
||
if (hasOwnPropertyOf(encoding, '#error')) { | ||
// TODO slots and options and all that. Also errorId | ||
const { '#error': message, name } = encoding; | ||
const dMessage = recognizeString(message); | ||
const dName = recognizeString(name); | ||
const EC = getErrorConstructor(dName) || Error; | ||
const errorName = `Remote${EC.name}`; | ||
const error = assert.error(dMessage, EC, { errorName }); | ||
return builder.buildError(error); | ||
} | ||
|
||
const buildEntry = ([encodedName, encodedVal]) => { | ||
typeof encodedName === 'string' || | ||
Fail`Property name ${q( | ||
encodedName, | ||
)} of ${encoding} must be a string`; | ||
!encodedName.startsWith('#') || | ||
Fail`Unrecognized record type ${q(encodedName)}: ${encoding}`; | ||
const name = recognizeString(encodedName); | ||
return [name, smallcapsRecognizer(encodedVal, builder)]; | ||
}; | ||
const builtEntries = entries(encoding).map(buildEntry); | ||
return builder.buildRecord(builtEntries); | ||
} | ||
default: { | ||
assert.fail( | ||
X`internal: unrecognized JSON typeof ${q( | ||
typeof encoding, | ||
)}: ${encoding}`, | ||
TypeError, | ||
); | ||
} | ||
} | ||
}; | ||
return smallcapsRecognizer; | ||
}; | ||
harden(makeSmallcapsRecognizer); | ||
|
||
export { | ||
makeSmallcapsBuilder as makeBuilder, | ||
makeSmallcapsRecognizer as makeRecognizer, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
/// <reference types="ses"/> | ||
|
||
import { Far, getTag, makeTagged, passStyleOf } from '@endo/pass-style'; | ||
|
||
const { fromEntries } = Object; | ||
const { ownKeys } = Reflect; | ||
const { quote: q, details: X } = assert; | ||
|
||
const makeSubgraphBuilder = () => { | ||
const ident = val => val; | ||
|
||
/** @type {Builder<any,any>} */ | ||
const subgraphBuilder = Far('SubgraphBuilder', { | ||
buildRoot: ident, | ||
|
||
buildUndefined: () => undefined, | ||
buildNull: () => null, | ||
buildBoolean: ident, | ||
buildNumber: ident, | ||
buildBigint: ident, | ||
buildString: ident, | ||
buildSymbol: ident, | ||
buildRecord: builtEntries => harden(fromEntries(builtEntries)), | ||
buildArray: ident, | ||
buildTagged: (tagName, builtPayload) => makeTagged(tagName, builtPayload), | ||
|
||
buildError: ident, | ||
buildRemotable: ident, | ||
buildPromise: ident, | ||
}); | ||
return subgraphBuilder; | ||
}; | ||
harden(makeSubgraphBuilder); | ||
|
||
const makeSubgraphRecognizer = () => { | ||
const subgraphRecognizer = (passable, builder) => { | ||
// First we handle all primitives. Some can be represented directly as | ||
// JSON, and some must be encoded into smallcaps strings. | ||
const passStyle = passStyleOf(passable); | ||
switch (passStyle) { | ||
case 'null': { | ||
return builder.buildNull(); | ||
} | ||
case 'boolean': { | ||
return builder.buildBoolean(passable); | ||
} | ||
case 'string': { | ||
return builder.buildString(passable); | ||
} | ||
case 'undefined': { | ||
return builder.buildUndefined(); | ||
} | ||
case 'number': { | ||
return builder.buildNumber(passable); | ||
} | ||
case 'bigint': { | ||
return builder.buildBigint(passable); | ||
} | ||
case 'symbol': { | ||
return builder.buildSymbol(passable); | ||
} | ||
case 'copyRecord': { | ||
// copyRecord allows only string keys so this will | ||
// work. | ||
const names = ownKeys(passable).sort(); | ||
return builder.buildRecord( | ||
names.map(name => [ | ||
name, | ||
subgraphRecognizer(passable[name], builder), | ||
]), | ||
); | ||
} | ||
case 'copyArray': { | ||
return builder.buildArray( | ||
passable.map(el => subgraphRecognizer(el, builder)), | ||
); | ||
} | ||
case 'tagged': { | ||
return builder.buildTagged( | ||
getTag(passable), | ||
subgraphRecognizer(passable.payload, builder), | ||
); | ||
} | ||
case 'remotable': { | ||
return builder.buildRemotable(passable); | ||
} | ||
case 'promise': { | ||
return builder.buildPromise(passable); | ||
} | ||
case 'error': { | ||
return builder.buildError(passable); | ||
} | ||
default: { | ||
assert.fail( | ||
X`internal: Unrecognized passStyle ${q(passStyle)}`, | ||
TypeError, | ||
); | ||
} | ||
} | ||
}; | ||
return subgraphRecognizer; | ||
}; | ||
harden(makeSubgraphRecognizer); | ||
|
||
export { | ||
makeSubgraphBuilder as makeBuilder, | ||
makeSubgraphRecognizer as makeRecognizer, | ||
}; |
Oops, something went wrong.