feat(marshal): display slot values if supplied
MODIFY
packages/marshal/src/marshal-justin.js
packages/marshal/src/marshal-justin.js
@@ -8,6 +8,7 @@ import { isObject } from './helpers/passStyle-helpers.js';
import { AtAtPrefixPattern, passableSymbolForName } from './helpers/symbol.js';
/** @typedef {import('./types.js').Encoding} Encoding */
+/** @template T @typedef {import('./types.js').CapData<T>} CapData */
const { ownKeys } = Reflect;
const { isArray } = Array;
@@ -115,9 +116,10 @@ const identPattern = /^[a-zA-Z]\w*$/;
/**
* @param {Encoding} encoding
* @param {boolean=} shouldIndent
+ * @param {any[]} [slots]
* @returns {string}
*/
-const decodeToJustin = (encoding, shouldIndent = false) => {
+const decodeToJustin = (encoding, shouldIndent = false, slots = []) => {
/**
* The first pass does some input validation.
* Its control flow should mirror `recur` as closely as possible
@@ -254,7 +256,7 @@ const decodeToJustin = (encoding, shouldIndent = false) => {
};
const makeIndenter = shouldIndent ? makeYesIndenter : makeNoIndenter;
- const out = makeIndenter();
+ let out = makeIndenter();
/**
* This is the second pass recursion after the first pass `prepare`.
@@ -349,12 +351,28 @@ const decodeToJustin = (encoding, shouldIndent = false) => {
case 'slot': {
let { index, iface } = rawTree;
index = Number(Nat(index));
+ const nestedRender = arg => {
+ const oldOut = out;
+ try {
+ out = makeNoIndenter();
+ decode(arg);
+ return out.done();
+ } finally {
+ out = oldOut;
+ }
+ };
+ if (index < slots.length) {
+ const slot = nestedRender(slots[index]);
if (iface === undefined) {
+ return out.next(`slotToVal(${slot})`);
+ }
+ iface = nestedRender(iface);
+ return out.next(`slotToVal(${slot},${iface})`);
+ } else if (iface === undefined) {
return out.next(`slot(${index})`);
- } else {
- iface = quote(iface);
- return out.next(`slot(${index},${iface})`);
}
+ iface = nestedRender(iface);
+ return out.next(`slot(${index},${iface})`);
}
case 'hilbert': {

MODIFY
packages/marshal/test/test-marshal-justin.js
packages/marshal/test/test-marshal-justin.js
@@ -13,6 +13,8 @@ import { decodeToJustin } from '../src/marshal-justin.js';
* A list of `[body, justinSrc]` pairs, where the body parses into
* an encoding that decodes to a Justin expression that evaluates to something
* that has the same encoding.
+ *
+ * @type {([string, string] | [string, string, unknown[]])[]}
*/
export const jsonPairs = harden([
// Justin is the same as the JSON encoding but without unnecessary quoting
@@ -73,6 +75,12 @@ export const jsonPairs = harden([
'[{"@qclass":"slot","iface":"Alleged: for testing Justin","index":0}]',
'[slot(0,"Alleged: for testing Justin")]',
],
+ // More Slots
+ [
+ '[{"@qclass":"slot","iface":"Alleged: for testing Justin","index":0},{"@qclass":"slot","iface":"Remotable","index":1}]',
+ '[slotToVal("hello","Alleged: for testing Justin"),slotToVal(null,"Remotable")]',
+ ['hello', null],
+ ],
// Tests https://github.com/endojs/endo/issues/1185 fix
[
'[{"@qclass":"slot","iface":"Alleged: for testing Justin","index":0},{"@qclass":"slot","index":0}]',
@@ -82,17 +90,30 @@ export const jsonPairs = harden([
const fakeJustinCompartment = () => {
const slots = [];
+ const slotVals = new Map();
+ const populateSlot = (index, iface) => {
+ assert.typeof(iface, 'string'); // Assumes not optional the first time
+ const r = Remotable(iface, undefined, { getIndex: () => index });
+ const s = `s${index}`;
+ slotVals.set(s, r);
+ slots[index] = s;
+ return r;
+ };
const slot = (index, iface = undefined) => {
if (slots[index] !== undefined) {
assert(iface === undefined); // Assumes backrefs omit iface
- return slots[index];
+ return slotVals.get(slots[index]);
}
- assert.typeof(iface, 'string'); // Assumes not optional the first time
- const r = Remotable(iface, undefined, { getIndex: () => index });
- slots[index] = r;
- return r;
+ return populateSlot(index, iface);
+ };
+ const slotToVal = (s, iface = undefined) => {
+ if (slotVals.has(s)) {
+ assert(iface === undefined); // Assumes backrefs omit iface
+ return slotVals.get(s);
+ }
+ return populateSlot(slots.length, iface);
};
- return new Compartment({ slot, makeTagged });
+ return new Compartment({ slot, slotToVal, makeTagged });
};
test('serialize decodeToJustin eval round trip pairs', t => {
@@ -103,10 +124,10 @@ test('serialize decodeToJustin eval round trip pairs', t => {
// TODO make Justin work with smallcaps
serializeBodyFormat: 'capdata',
});
- for (const [body, justinSrc] of jsonPairs) {
+ for (const [body, justinSrc, slots] of jsonPairs) {
const c = fakeJustinCompartment();
const encoding = JSON.parse(body);
- const justinExpr = decodeToJustin(encoding);
+ const justinExpr = decodeToJustin(encoding, false, slots);
t.is(justinExpr, justinSrc);
const value = harden(c.evaluate(`(${justinExpr})`));
const { body: newBody } = serialize(value);
@@ -127,10 +148,11 @@ test('serialize decodeToJustin indented eval round trip', t => {
// TODO make Justin work with smallcaps
serializeBodyFormat: 'capdata',
});
- for (const [body] of jsonPairs) {
+ for (const [body, _, slots] of jsonPairs) {
const c = fakeJustinCompartment();
+ t.log(body);
const encoding = JSON.parse(body);
- const justinExpr = decodeToJustin(encoding, true);
+ const justinExpr = decodeToJustin(encoding, true, slots);
const value = harden(c.evaluate(`(${justinExpr})`));
const { body: newBody } = serialize(value);
t.is(newBody, body);

feat(marshal): display slot values if supplied