6
6
import { Nat } from '@agoric/nat' ;
7
7
import { assert , details as X , q } from '@agoric/assert' ;
8
8
import { QCLASS , getErrorConstructor } from './marshal' ;
9
- // import { makeReviverIbidTable } from './ibidTables';
9
+ import { makeReviverIbidTable } from './ibidTables' ;
10
10
11
11
import './types' ;
12
12
@@ -31,16 +31,30 @@ const { stringify: quote } = JSON;
31
31
const makeYesIndenter = ( ) => {
32
32
const strings = [ ] ;
33
33
let level = 0 ;
34
- const line = ( ) => strings . push ( '\n' , ' ' . repeat ( level ) ) ;
34
+ let needSpace = false ;
35
+ const line = ( ) => {
36
+ needSpace = false ;
37
+ return strings . push ( '\n' , ' ' . repeat ( level ) ) ;
38
+ } ;
35
39
return harden ( {
36
40
open : openBracket => {
37
- assert ( level >= 1 ) ;
38
41
level += 1 ;
39
- return strings . push ( ' ' , openBracket ) ;
42
+ if ( needSpace ) {
43
+ strings . push ( ' ' ) ;
44
+ }
45
+ needSpace = false ;
46
+ return strings . push ( openBracket ) ;
40
47
} ,
41
48
line,
42
- next : token => strings . push ( ' ' , token ) ,
49
+ next : token => {
50
+ if ( needSpace && token !== ',' ) {
51
+ strings . push ( ' ' ) ;
52
+ }
53
+ needSpace = true ;
54
+ return strings . push ( token ) ;
55
+ } ,
43
56
close : closeBracket => {
57
+ assert ( level >= 1 ) ;
44
58
level -= 1 ;
45
59
line ( ) ;
46
60
return strings . push ( closeBracket ) ;
@@ -96,60 +110,30 @@ const makeNoIndenter = () => {
96
110
} ) ;
97
111
} ;
98
112
99
- const registerIbid = _rawTree => {
100
- // doesn't do anything yet.
101
- } ;
102
-
103
- const startIbid = _rawTree => {
104
- // doesn't do anything yet.
105
- } ;
106
-
107
- const finishIbid = _rawTree => {
108
- // doesn't do anything yet.
109
- } ;
110
-
111
113
const identPattern = / ^ [ a - z A - Z ] \w * $ / ;
112
114
113
115
/**
114
116
* @param {Encoding } encoding
115
- * @param {CyclePolicy } _cyclePolicy
116
117
* @param {boolean= } shouldIndent
117
118
* @returns {string }
118
119
*/
119
- const decodeToJustin = ( encoding , _cyclePolicy , shouldIndent = false ) => {
120
- // const ibidTable = makeReviverIbidTable(cyclePolicy);
121
- const makeIndenter = shouldIndent ? makeYesIndenter : makeNoIndenter ;
122
- const out = makeIndenter ( ) ;
123
-
124
- const decodeProperty = ( name , value ) => {
125
- out . line ( ) ;
126
- assert . typeof ( name , 'string' , X `Property name ${ name } of must be a string` ) ;
127
- if ( name === '__proto__' ) {
128
- // JavaScript interprets `{__proto__: x, ...}`
129
- // as making an object inheriting from `x`, whereas
130
- // in JSON it is simply a property name. Preserve the
131
- // JSON meaning.
132
- out . next ( `["__proto__"]:` ) ;
133
- } else if ( identPattern . test ( name ) ) {
134
- out . next ( `${ name } :` ) ;
135
- } else {
136
- out . next ( `${ quote ( name ) } :` ) ;
137
- }
138
- // eslint-disable-next-line no-use-before-define
139
- recur ( value ) ;
140
- out . next ( ',' ) ;
141
- } ;
120
+ const decodeToJustin = ( encoding , shouldIndent = false ) => {
121
+ const ibidTable = makeReviverIbidTable ( 'forbidCycles' ) ;
122
+ const ibidMap = new Map ( ) ;
142
123
143
124
/**
144
- * Modeled after `fullRevive` in marshal.js
125
+ * The first pass populates ibidMap for use by `decode`.
126
+ * Since this is the first pass, it should do all input validation.
127
+ * Its control flow should mirror `recur` as closely as possible
128
+ * and the two should be maintained together. They must visit everything
129
+ * in the same order.
145
130
*
146
131
* @param {Encoding } rawTree
147
- * @returns {number }
132
+ * @returns {void }
148
133
*/
149
- const recur = rawTree => {
134
+ const prepare = rawTree => {
150
135
if ( Object ( rawTree ) !== rawTree ) {
151
- // primitives get quoted
152
- return out . next ( quote ( rawTree ) ) ;
136
+ return ;
153
137
}
154
138
// Assertions of the above to narrow the type.
155
139
assert . typeof ( rawTree , 'object' ) ;
@@ -162,17 +146,12 @@ const decodeToJustin = (encoding, _cyclePolicy, shouldIndent = false) => {
162
146
X `invalid qclass typeof ${ q ( typeof qclass ) } ` ,
163
147
) ;
164
148
assert ( ! Array . isArray ( rawTree ) ) ;
165
- // Switching on `encoded[QCLASS]` (or anything less direct, like
166
- // `qclass`) does not discriminate rawTree in typescript@4.2.3 and
167
- // earlier.
168
149
switch ( rawTree [ '@qclass' ] ) {
169
- // Encoding of primitives not handled by JSON
170
150
case 'undefined' :
171
151
case 'NaN' :
172
152
case 'Infinity' :
173
153
case '-Infinity' : {
174
- // Their qclass is their expression source.
175
- return out . next ( qclass ) ;
154
+ return ;
176
155
}
177
156
case 'bigint' : {
178
157
const { digits } = rawTree ;
@@ -181,19 +160,20 @@ const decodeToJustin = (encoding, _cyclePolicy, shouldIndent = false) => {
181
160
'string' ,
182
161
X `invalid digits typeof ${ q ( typeof digits ) } ` ,
183
162
) ;
184
- return out . next ( ` ${ BigInt ( digits ) } n` ) ;
163
+ return ;
185
164
}
186
165
case '@@asyncIterator' : {
187
- return out . next ( 'Symbol.asyncIterator' ) ;
166
+ return ;
188
167
}
189
-
190
168
case 'ibid' : {
191
169
const { index } = rawTree ;
192
- return out . next ( `getIbid(${ index } )` ) ;
170
+ // ibidTable's `get` does some input validation.
171
+ const rawNode = ibidTable . get ( index ) ;
172
+ ibidMap . set ( rawNode , index ) ;
173
+ return ;
193
174
}
194
-
195
175
case 'error' : {
196
- registerIbid ( rawTree ) ;
176
+ ibidTable . register ( rawTree ) ;
197
177
const { name, message } = rawTree ;
198
178
assert . typeof (
199
179
name ,
@@ -209,27 +189,24 @@ const decodeToJustin = (encoding, _cyclePolicy, shouldIndent = false) => {
209
189
'string' ,
210
190
X `invalid error message typeof ${ q ( typeof message ) } ` ,
211
191
) ;
212
- return out . next ( ` ${ name } ( ${ quote ( message ) } )` ) ;
192
+ return ;
213
193
}
214
-
215
194
case 'slot' : {
216
- registerIbid ( rawTree ) ;
217
- let { index, iface } = rawTree ;
218
- index = Number ( Nat ( index ) ) ;
195
+ ibidTable . register ( rawTree ) ;
196
+ const { index, iface } = rawTree ;
197
+ assert . typeof ( index , 'number' ) ;
198
+ Nat ( index ) ;
219
199
assert . typeof ( iface , 'string' ) ;
220
- iface = quote ( iface ) ;
221
- return out . next ( `getSlotVal(${ index } ,${ iface } )` ) ;
200
+ return ;
222
201
}
223
-
224
202
case 'hilbert' : {
225
- startIbid ( rawTree ) ;
203
+ ibidTable . start ( rawTree ) ;
226
204
const { original, rest } = rawTree ;
227
205
assert (
228
206
'original' in rawTree ,
229
207
X `Invalid Hilbert Hotel encoding ${ rawTree } ` ,
230
208
) ;
231
- out . open ( '{' ) ;
232
- decodeProperty ( QCLASS , original ) ;
209
+ prepare ( original ) ;
233
210
if ( 'rest' in rawTree ) {
234
211
assert . typeof (
235
212
rest ,
@@ -245,14 +222,155 @@ const decodeToJustin = (encoding, _cyclePolicy, shouldIndent = false) => {
245
222
! ( QCLASS in rest ) ,
246
223
X `Rest encoding ${ rest } must not contain ${ q ( QCLASS ) } ` ,
247
224
) ;
248
- startIbid ( rest ) ;
225
+ ibidTable . start ( rest ) ;
226
+ const names = ownKeys ( rest ) ;
227
+ for ( const name of names ) {
228
+ assert . typeof (
229
+ name ,
230
+ 'string' ,
231
+ X `Property name ${ name } of ${ rawTree } must be a string` ,
232
+ ) ;
233
+ prepare ( rest [ name ] ) ;
234
+ }
235
+ ibidTable . finish ( rest ) ;
236
+ }
237
+ ibidTable . finish ( rawTree ) ;
238
+ return ;
239
+ }
240
+
241
+ default : {
242
+ assert . fail ( X `unrecognized ${ q ( QCLASS ) } ${ q ( qclass ) } ` , TypeError ) ;
243
+ }
244
+ }
245
+ } else if ( Array . isArray ( rawTree ) ) {
246
+ ibidTable . start ( rawTree ) ;
247
+ const { length } = rawTree ;
248
+ for ( let i = 0 ; i < length ; i += 1 ) {
249
+ prepare ( rawTree [ i ] ) ;
250
+ }
251
+ ibidTable . finish ( rawTree ) ;
252
+ } else {
253
+ ibidTable . start ( rawTree ) ;
254
+ const names = ownKeys ( rawTree ) ;
255
+ for ( const name of names ) {
256
+ assert . typeof (
257
+ name ,
258
+ 'string' ,
259
+ X `Property name ${ name } of ${ rawTree } must be a string` ,
260
+ ) ;
261
+ prepare ( rawTree [ name ] ) ;
262
+ }
263
+ ibidTable . finish ( rawTree ) ;
264
+ }
265
+ } ;
266
+
267
+ const makeIndenter = shouldIndent ? makeYesIndenter : makeNoIndenter ;
268
+ const out = makeIndenter ( ) ;
269
+
270
+ /**
271
+ * This is the second pass recursion after the first pass `prepare`.
272
+ * The first pass initialized `ibidMap` and did input validation so
273
+ * here we can safely assume everything it validated.
274
+ *
275
+ * @param {Encoding } rawTree
276
+ * @returns {number }
277
+ */
278
+ const decode = rawTree => {
279
+ if ( ibidMap . has ( rawTree ) ) {
280
+ const index = ibidMap . get ( rawTree ) ;
281
+ out . next ( `initIbid(${ index } ,` ) ;
282
+ // eslint-disable-next-line no-use-before-define
283
+ recur ( rawTree ) ;
284
+ return out . next ( ')' ) ;
285
+ }
286
+ // eslint-disable-next-line no-use-before-define
287
+ return recur ( rawTree ) ;
288
+ } ;
289
+
290
+ const decodeProperty = ( name , value ) => {
291
+ out . line ( ) ;
292
+ if ( name === '__proto__' ) {
293
+ // JavaScript interprets `{__proto__: x, ...}`
294
+ // as making an object inheriting from `x`, whereas
295
+ // in JSON it is simply a property name. Preserve the
296
+ // JSON meaning.
297
+ out . next ( `["__proto__"]:` ) ;
298
+ } else if ( identPattern . test ( name ) ) {
299
+ out . next ( `${ name } :` ) ;
300
+ } else {
301
+ out . next ( `${ quote ( name ) } :` ) ;
302
+ }
303
+ decode ( value ) ;
304
+ out . next ( ',' ) ;
305
+ } ;
306
+
307
+ /**
308
+ * Modeled after `fullRevive` in marshal.js
309
+ *
310
+ * @param {Encoding } rawTree
311
+ * @returns {number }
312
+ */
313
+ const recur = rawTree => {
314
+ if ( Object ( rawTree ) !== rawTree ) {
315
+ // primitives get quoted
316
+ return out . next ( quote ( rawTree ) ) ;
317
+ }
318
+ // Assertions of the above to narrow the type.
319
+ assert . typeof ( rawTree , 'object' ) ;
320
+ assert ( rawTree !== null ) ;
321
+ if ( QCLASS in rawTree ) {
322
+ const qclass = rawTree [ QCLASS ] ;
323
+ assert . typeof ( qclass , 'string' ) ;
324
+ assert ( ! Array . isArray ( rawTree ) ) ;
325
+ // Switching on `encoded[QCLASS]` (or anything less direct, like
326
+ // `qclass`) does not discriminate rawTree in typescript@4.2.3 and
327
+ // earlier.
328
+ switch ( rawTree [ '@qclass' ] ) {
329
+ // Encoding of primitives not handled by JSON
330
+ case 'undefined' :
331
+ case 'NaN' :
332
+ case 'Infinity' :
333
+ case '-Infinity' : {
334
+ // Their qclass is their expression source.
335
+ return out . next ( qclass ) ;
336
+ }
337
+ case 'bigint' : {
338
+ const { digits } = rawTree ;
339
+ return out . next ( `${ BigInt ( digits ) } n` ) ;
340
+ }
341
+ case '@@asyncIterator' : {
342
+ return out . next ( 'Symbol.asyncIterator' ) ;
343
+ }
344
+
345
+ case 'ibid' : {
346
+ const { index } = rawTree ;
347
+ return out . next ( `getIbid(${ index } )` ) ;
348
+ }
349
+
350
+ case 'error' : {
351
+ const { name, message } = rawTree ;
352
+ return out . next ( `${ name } (${ quote ( message ) } )` ) ;
353
+ }
354
+
355
+ case 'slot' : {
356
+ let { index, iface } = rawTree ;
357
+ index = Number ( Nat ( index ) ) ;
358
+ iface = quote ( iface ) ;
359
+ return out . next ( `getSlotVal(${ index } ,${ iface } )` ) ;
360
+ }
361
+
362
+ case 'hilbert' : {
363
+ const { original, rest } = rawTree ;
364
+ out . open ( '{' ) ;
365
+ decodeProperty ( QCLASS , original ) ;
366
+ if ( 'rest' in rawTree ) {
367
+ assert . typeof ( rest , 'object' ) ;
368
+ assert ( rest !== null ) ;
249
369
const names = ownKeys ( rest ) ;
250
370
for ( const name of names ) {
251
371
decodeProperty ( name , rest [ name ] ) ;
252
372
}
253
- finishIbid ( rest ) ;
254
373
}
255
- finishIbid ( rawTree ) ;
256
374
return out . close ( '}' ) ;
257
375
}
258
376
@@ -261,38 +379,33 @@ const decodeToJustin = (encoding, _cyclePolicy, shouldIndent = false) => {
261
379
}
262
380
}
263
381
} else if ( Array . isArray ( rawTree ) ) {
264
- startIbid ( rawTree ) ;
265
382
const { length } = rawTree ;
266
383
if ( length === 0 ) {
267
- finishIbid ( rawTree ) ;
268
384
return out . next ( '[]' ) ;
269
385
} else {
270
386
out . open ( '[' ) ;
271
387
for ( let i = 0 ; i < length ; i += 1 ) {
272
388
out . line ( ) ;
273
- recur ( rawTree [ i ] ) ;
389
+ decode ( rawTree [ i ] ) ;
274
390
out . next ( ',' ) ;
275
391
}
276
- finishIbid ( rawTree ) ;
277
392
return out . close ( ']' ) ;
278
393
}
279
394
} else {
280
- startIbid ( rawTree ) ;
281
395
const names = ownKeys ( rawTree ) ;
282
396
if ( names . length === 0 ) {
283
- finishIbid ( rawTree ) ;
284
397
return out . next ( '{}' ) ;
285
398
} else {
286
399
out . open ( '{' ) ;
287
400
for ( const name of names ) {
288
401
decodeProperty ( name , rawTree [ name ] ) ;
289
402
}
290
- finishIbid ( rawTree ) ;
291
403
return out . close ( '}' ) ;
292
404
}
293
405
}
294
406
} ;
295
- recur ( encoding ) ;
407
+ prepare ( encoding ) ;
408
+ decode ( encoding ) ;
296
409
return out . done ( ) ;
297
410
} ;
298
411
harden ( decodeToJustin ) ;
0 commit comments