Skip to content

Commit

Permalink
WIP: Event-based encoding API based on Data-E
Browse files Browse the repository at this point in the history
  • Loading branch information
erights committed Jan 11, 2024
1 parent 3fae760 commit 4e8e848
Show file tree
Hide file tree
Showing 11 changed files with 1,242 additions and 21 deletions.
41 changes: 41 additions & 0 deletions packages/marshal/src/builders/builder-types.js
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 {(buildTopFn: () => 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 {(names: string[], buildValuesIter: Iterable<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 {(count: number, buildElementsIter: Iterable<N>) => N} buildArray
* @property {(tagName: string, buildPayloadFn: () => 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}
*/
132 changes: 132 additions & 0 deletions packages/marshal/src/builders/justinBuilder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/// <reference types="ses"/>

import { Far, getInterfaceOf, nameForPassableSymbol } from '@endo/pass-style';
import {
identPattern,
AtAtPrefixPattern,
makeNoIndenter,
makeYesIndenter,
} from '../marshal-justin.js';

const { stringify } = JSON;
const { Fail, quote: q } = assert;
const { is } = Object;

export const makeJustinBuilder = (shouldIndent = false, _slots = []) => {
let out;
let slotIndex;
const outNextJSON = val => out.next(stringify(val));

/** @type {Builder<number,string>} */
const justinBuilder = Far('JustinBuilder', {
buildRoot: buildTopFn => {
const makeIndenter = shouldIndent ? makeYesIndenter : makeNoIndenter;
out = makeIndenter();
slotIndex = -1;
buildTopFn();
return out.done();
},

buildUndefined: () => out.next('undefined'),
buildNull: () => out.next('null'),
buildBoolean: outNextJSON,
buildNumber: num => {
if (num === Infinity) {
return out.next('Infinity');
} else if (num === -Infinity) {
return out.next('-Infinity');
} else if (is(num, NaN)) {
return out.next('NaN');
} else {
return out.next(stringify(num));
}
},
buildBigint: bigint => out.next(`${bigint}n`),
buildString: outNextJSON,
buildSymbol: sym => {
assert.typeof(sym, 'symbol');
const name = nameForPassableSymbol(sym);
if (name === undefined) {
throw Fail`Symbol must be either registered or well known: ${q(sym)}`;
}
const registeredName = Symbol.keyFor(sym);
if (registeredName === undefined) {
const match = AtAtPrefixPattern.exec(name);
assert(match !== null);
const suffix = match[1];
assert(Symbol[suffix] === sym);
assert(identPattern.test(suffix));
return out.next(`Symbol.${suffix}`);
}
return out.next(`Symbol.for(${stringify(registeredName)})`);
},

buildRecord: (names, buildValuesIter) => {
if (names.length === 0) {
return out.next('{}');
}
out.open('{');
const iter = buildValuesIter[Symbol.iterator]();
for (const name of names) {
out.line();
if (name === '__proto__') {
// JavaScript interprets `{__proto__: x, ...}`
// as making an object inheriting from `x`, whereas
// in JSON it is simply a property name. Preserve the
// JSON meaning.
out.next(`["__proto__"]:`);
} else if (identPattern.test(name)) {
out.next(`${name}:`);
} else {
out.next(`${stringify(name)}:`);
}
const { value: _, done } = iter.next();
if (done) {
break;
}
out.next(',');
}
return out.close('}');
},
buildArray: (count, buildElementsIter) => {
if (count === 0) {
return out.next('[]');
}
out.open('[');
const iter = buildElementsIter[Symbol.iterator]();
for (let i = 0; ; i += 1) {
if (i < count) {
out.line();
}
const { value: _, done } = iter.next();
if (done) {
break;
}
out.next(',');
}
return out.close(']');
},
buildTagged: (tagName, buildPayloadFn) => {
out.next(`makeTagged(${stringify(tagName)},`);
buildPayloadFn();
return out.next(')');
},

buildError: error => out.next(`${error.name}(${stringify(error.message)})`),
buildRemotable: remotable => {
slotIndex += 1;
return out.next(
`slot(${slotIndex},${stringify(getInterfaceOf(remotable))})`,
);
},
buildPromise: _promise => {
slotIndex += 1;
return out.next(`slot(${slotIndex})`);
},
});
return justinBuilder;
};
harden(makeJustinBuilder);

export const makeBuilder = () => makeJustinBuilder();
harden(makeBuilder);
Loading

0 comments on commit 4e8e848

Please sign in to comment.