seroval 1.3.2 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/dist/cjs/development/index.cjs +2640 -2683
  2. package/dist/cjs/development/index.cjs.map +4 -4
  3. package/dist/cjs/production/index.cjs +3 -17
  4. package/dist/esm/development/index.mjs +2640 -2683
  5. package/dist/esm/development/index.mjs.map +4 -4
  6. package/dist/esm/production/index.mjs +3 -17
  7. package/dist/types/core/base-primitives.d.ts +1 -2
  8. package/dist/types/core/base-primitives.d.ts.map +1 -1
  9. package/dist/types/core/compat.d.ts +1 -1
  10. package/dist/types/core/compat.d.ts.map +1 -1
  11. package/dist/types/core/constants.d.ts +1 -0
  12. package/dist/types/core/constants.d.ts.map +1 -1
  13. package/dist/types/core/constructors.d.ts +54 -0
  14. package/dist/types/core/constructors.d.ts.map +1 -0
  15. package/dist/types/core/context/async-parser.d.ts +19 -0
  16. package/dist/types/core/context/async-parser.d.ts.map +1 -0
  17. package/dist/types/core/context/deserializer.d.ts +35 -38
  18. package/dist/types/core/context/deserializer.d.ts.map +1 -1
  19. package/dist/types/core/context/parser.d.ts +32 -19
  20. package/dist/types/core/context/parser.d.ts.map +1 -1
  21. package/dist/types/core/context/serializer.d.ts +38 -99
  22. package/dist/types/core/context/serializer.d.ts.map +1 -1
  23. package/dist/types/core/context/sync-parser.d.ts +59 -0
  24. package/dist/types/core/context/sync-parser.d.ts.map +1 -0
  25. package/dist/types/core/cross/index.d.ts +12 -13
  26. package/dist/types/core/cross/index.d.ts.map +1 -1
  27. package/dist/types/core/errors.d.ts.map +1 -1
  28. package/dist/types/core/function-string.d.ts +2 -2
  29. package/dist/types/core/function-string.d.ts.map +1 -1
  30. package/dist/types/core/plugin.d.ts +13 -11
  31. package/dist/types/core/plugin.d.ts.map +1 -1
  32. package/dist/types/core/reference.d.ts.map +1 -1
  33. package/dist/types/core/special-reference.d.ts +3 -2
  34. package/dist/types/core/special-reference.d.ts.map +1 -1
  35. package/dist/types/core/stream.d.ts +1 -2
  36. package/dist/types/core/stream.d.ts.map +1 -1
  37. package/dist/types/core/symbols.d.ts +14 -0
  38. package/dist/types/core/symbols.d.ts.map +1 -0
  39. package/dist/types/core/tree/index.d.ts +3 -2
  40. package/dist/types/core/tree/index.d.ts.map +1 -1
  41. package/dist/types/core/types.d.ts +3 -1
  42. package/dist/types/core/types.d.ts.map +1 -1
  43. package/dist/types/core/utils/iterator-to-sequence.d.ts.map +1 -1
  44. package/dist/types/index.d.ts +10 -5
  45. package/dist/types/index.d.ts.map +1 -1
  46. package/package.json +17 -19
  47. package/src/core/base-primitives.ts +0 -27
  48. package/src/core/compat.ts +2 -1
  49. package/src/core/constants.ts +28 -26
  50. package/src/core/constructors.ts +269 -0
  51. package/src/core/context/async-parser.ts +586 -0
  52. package/src/core/context/deserializer.ts +506 -316
  53. package/src/core/context/parser.ts +243 -180
  54. package/src/core/context/serializer.ts +1058 -827
  55. package/src/core/context/sync-parser.ts +833 -0
  56. package/src/core/cross/index.ts +63 -47
  57. package/src/core/errors.ts +73 -12
  58. package/src/core/function-string.ts +11 -10
  59. package/src/core/plugin.ts +20 -11
  60. package/src/core/reference.ts +8 -5
  61. package/src/core/special-reference.ts +18 -90
  62. package/src/core/stream.ts +18 -152
  63. package/src/core/symbols.ts +18 -0
  64. package/src/core/tree/index.ts +45 -31
  65. package/src/core/types.ts +7 -2
  66. package/src/core/utils/iterator-to-sequence.ts +6 -28
  67. package/src/index.ts +29 -11
  68. package/dist/types/core/context/parser/async.d.ts +0 -24
  69. package/dist/types/core/context/parser/async.d.ts.map +0 -1
  70. package/dist/types/core/context/parser/stream.d.ts +0 -43
  71. package/dist/types/core/context/parser/stream.d.ts.map +0 -1
  72. package/dist/types/core/context/parser/sync.d.ts +0 -30
  73. package/dist/types/core/context/parser/sync.d.ts.map +0 -1
  74. package/dist/types/core/cross/async.d.ts +0 -8
  75. package/dist/types/core/cross/async.d.ts.map +0 -1
  76. package/dist/types/core/cross/deserializer.d.ts +0 -9
  77. package/dist/types/core/cross/deserializer.d.ts.map +0 -1
  78. package/dist/types/core/cross/parser.d.ts +0 -6
  79. package/dist/types/core/cross/parser.d.ts.map +0 -1
  80. package/dist/types/core/cross/serializer.d.ts +0 -16
  81. package/dist/types/core/cross/serializer.d.ts.map +0 -1
  82. package/dist/types/core/cross/stream.d.ts +0 -8
  83. package/dist/types/core/cross/stream.d.ts.map +0 -1
  84. package/dist/types/core/cross/sync.d.ts +0 -8
  85. package/dist/types/core/cross/sync.d.ts.map +0 -1
  86. package/dist/types/core/tree/async.d.ts +0 -8
  87. package/dist/types/core/tree/async.d.ts.map +0 -1
  88. package/dist/types/core/tree/deserializer.d.ts +0 -13
  89. package/dist/types/core/tree/deserializer.d.ts.map +0 -1
  90. package/dist/types/core/tree/serializer.d.ts +0 -29
  91. package/dist/types/core/tree/serializer.d.ts.map +0 -1
  92. package/dist/types/core/tree/sync.d.ts +0 -8
  93. package/dist/types/core/tree/sync.d.ts.map +0 -1
  94. package/dist/types/core/utils/assert.d.ts +0 -2
  95. package/dist/types/core/utils/assert.d.ts.map +0 -1
  96. package/dist/types/core/utils/deferred.d.ts +0 -7
  97. package/dist/types/core/utils/deferred.d.ts.map +0 -1
  98. package/src/core/context/parser/async.ts +0 -495
  99. package/src/core/context/parser/stream.ts +0 -323
  100. package/src/core/context/parser/sync.ts +0 -404
  101. package/src/core/cross/async.ts +0 -9
  102. package/src/core/cross/deserializer.ts +0 -16
  103. package/src/core/cross/parser.ts +0 -7
  104. package/src/core/cross/serializer.ts +0 -72
  105. package/src/core/cross/stream.ts +0 -9
  106. package/src/core/cross/sync.ts +0 -9
  107. package/src/core/tree/async.ts +0 -9
  108. package/src/core/tree/deserializer.ts +0 -26
  109. package/src/core/tree/serializer.ts +0 -98
  110. package/src/core/tree/sync.ts +0 -9
  111. package/src/core/utils/assert.ts +0 -5
  112. package/src/core/utils/deferred.ts +0 -22
@@ -3,19 +3,25 @@ import {
3
3
  CONSTANT_STRING,
4
4
  ERROR_CONSTRUCTOR_STRING,
5
5
  NIL,
6
- SYMBOL_STRING,
7
6
  SerovalNodeType,
8
7
  SerovalObjectFlags,
8
+ SYMBOL_STRING,
9
9
  } from '../constants';
10
+ import {
11
+ SERIALIZED_ASYNC_ITERATOR_CONSTRUCTOR,
12
+ SERIALIZED_ITERATOR_CONSTRUCTOR,
13
+ } from '../constructors';
10
14
  import {
11
15
  SerovalMissingPluginError,
12
16
  SerovalSerializationError,
13
17
  SerovalUnsupportedNodeError,
14
18
  } from '../errors';
15
19
  import { createEffectfulFunction, createFunction } from '../function-string';
16
- import { REFERENCES_KEY } from '../keys';
17
- import type { Plugin, PluginAccessOptions, SerovalMode } from '../plugin';
18
- import { serializeSpecialReferenceValue } from '../special-reference';
20
+ import { GLOBAL_CONTEXT_REFERENCES, REFERENCES_KEY } from '../keys';
21
+ import type { PluginAccessOptions } from '../plugin';
22
+ import { SerovalMode } from '../plugin';
23
+ import { SPECIAL_REF_STRING } from '../special-reference';
24
+ import { serializeString } from '../string';
19
25
  import type {
20
26
  SerovalAggregateErrorNode,
21
27
  SerovalArrayBufferNode,
@@ -45,14 +51,13 @@ import type {
45
51
  SerovalReferenceNode,
46
52
  SerovalRegExpNode,
47
53
  SerovalSetNode,
48
- SerovalSpecialReferenceNode,
49
54
  SerovalStreamConstructorNode,
50
55
  SerovalStreamNextNode,
51
56
  SerovalStreamReturnNode,
52
57
  SerovalStreamThrowNode,
53
58
  SerovalTypedArrayNode,
54
- SerovalWKSymbolNode,
55
59
  } from '../types';
60
+ import getIdentifier from '../utils/get-identifier';
56
61
  import { isValidIdentifier } from '../utils/is-valid-identifier';
57
62
 
58
63
  const enum AssignmentType {
@@ -208,318 +213,377 @@ export interface BaseSerializerContextOptions extends PluginAccessOptions {
208
213
  markedRefs: number[] | Set<number>;
209
214
  }
210
215
 
211
- export default abstract class BaseSerializerContext
212
- implements PluginAccessOptions
213
- {
214
- /**
215
- * @private
216
- */
217
- features: number;
216
+ export interface BaseSerializerContext extends PluginAccessOptions {
217
+ readonly mode: SerovalMode;
218
218
 
219
- /**
219
+ features: number;
220
+ /*
220
221
  * To check if an object is synchronously referencing itself
221
- * @private
222
222
  */
223
- stack: number[] = [];
224
-
223
+ stack: number[];
225
224
  /**
226
225
  * Array of object mutations
227
- * @private
228
226
  */
229
- flags: FlaggedObject[] = [];
230
-
227
+ flags: FlaggedObject[];
231
228
  /**
232
229
  * Array of assignments to be done (used for recursion)
233
- * @private
234
230
  */
235
- assignments: Assignment[] = [];
236
-
237
- plugins?: Plugin<any, any>[] | undefined;
238
-
231
+ assignments: Assignment[];
239
232
  /**
240
233
  * Refs that are...referenced
241
- * @private
242
234
  */
243
235
  marked: Set<number>;
236
+ }
244
237
 
245
- constructor(options: BaseSerializerContextOptions) {
246
- this.plugins = options.plugins;
247
- this.features = options.features;
248
- this.marked = new Set(options.markedRefs);
249
- }
238
+ export interface CrossContextOptions {
239
+ scopeId?: string;
240
+ }
250
241
 
251
- abstract readonly mode: SerovalMode;
242
+ export function createBaseSerializerContext(
243
+ mode: SerovalMode,
244
+ options: BaseSerializerContextOptions,
245
+ ): BaseSerializerContext {
246
+ return {
247
+ mode,
248
+ plugins: options.plugins,
249
+ features: options.features,
250
+ marked: new Set(options.markedRefs),
251
+ stack: [],
252
+ flags: [],
253
+ assignments: [],
254
+ };
255
+ }
252
256
 
253
- createFunction(parameters: string[], body: string): string {
254
- return createFunction(this.features, parameters, body);
255
- }
257
+ export interface VanillaSerializerState {
258
+ valid: Map<number, number>;
259
+ vars: string[];
260
+ }
256
261
 
257
- createEffectfulFunction(parameters: string[], body: string): string {
258
- return createEffectfulFunction(this.features, parameters, body);
259
- }
262
+ function createVanillaSerializerState(): VanillaSerializerState {
263
+ return {
264
+ valid: new Map(),
265
+ vars: [],
266
+ };
267
+ }
260
268
 
261
- /**
262
- * A tiny function that tells if a reference
263
- * is to be accessed. This is a requirement for
264
- * deciding whether or not we should generate
265
- * an identifier for the object
266
- */
267
- protected markRef(id: number): void {
268
- this.marked.add(id);
269
- }
269
+ export interface VanillaSerializerContext {
270
+ mode: SerovalMode.Vanilla;
271
+ base: BaseSerializerContext;
272
+ state: VanillaSerializerState;
273
+ child: SerializePluginContext | undefined;
274
+ }
270
275
 
271
- protected isMarked(id: number): boolean {
272
- return this.marked.has(id);
273
- }
276
+ export type VanillaSerializerContextOptions = BaseSerializerContextOptions;
274
277
 
275
- /**
276
- * Converts the ID of a reference into a identifier string
277
- * that is used to refer to the object instance in the
278
- * generated script.
279
- */
280
- abstract getRefParam(id: number): string;
281
-
282
- protected pushObjectFlag(flag: SerovalObjectFlags, id: number): void {
283
- if (flag !== SerovalObjectFlags.None) {
284
- this.markRef(id);
285
- this.flags.push({
286
- type: flag,
287
- value: this.getRefParam(id),
288
- });
289
- }
290
- }
278
+ export function createVanillaSerializerContext(
279
+ options: VanillaSerializerContextOptions,
280
+ ): VanillaSerializerContext {
281
+ return {
282
+ mode: SerovalMode.Vanilla,
283
+ base: createBaseSerializerContext(SerovalMode.Vanilla, options),
284
+ state: createVanillaSerializerState(),
285
+ child: undefined,
286
+ };
287
+ }
291
288
 
292
- private resolveFlags(): string | undefined {
293
- let result = '';
294
- for (let i = 0, current = this.flags, len = current.length; i < len; i++) {
295
- const flag = current[i];
296
- result += OBJECT_FLAG_CONSTRUCTOR[flag.type] + '(' + flag.value + '),';
297
- }
298
- return result;
299
- }
289
+ export interface CrossSerializerContext {
290
+ mode: SerovalMode.Cross;
291
+ base: BaseSerializerContext;
292
+ state: CrossContextOptions;
293
+ child: SerializePluginContext | undefined;
294
+ }
300
295
 
301
- protected resolvePatches(): string | undefined {
302
- const assignments = resolveAssignments(this.assignments);
303
- const flags = this.resolveFlags();
304
- if (assignments) {
305
- if (flags) {
306
- return assignments + flags;
307
- }
308
- return assignments;
309
- }
310
- return flags;
296
+ export interface CrossSerializerContextOptions
297
+ extends BaseSerializerContextOptions,
298
+ CrossContextOptions {
299
+ // empty
300
+ }
301
+
302
+ export function createCrossSerializerContext(
303
+ options: CrossSerializerContextOptions,
304
+ ): CrossSerializerContext {
305
+ return {
306
+ mode: SerovalMode.Cross,
307
+ base: createBaseSerializerContext(SerovalMode.Cross, options),
308
+ state: options,
309
+ child: undefined,
310
+ };
311
+ }
312
+
313
+ type SerializerContext = VanillaSerializerContext | CrossSerializerContext;
314
+
315
+ export class SerializePluginContext {
316
+ constructor(private _p: SerializerContext) {}
317
+
318
+ serialize(node: SerovalNode) {
319
+ return serialize(this._p, node);
311
320
  }
321
+ }
312
322
 
323
+ /**
324
+ * Creates the reference param (identifier) from the given reference ID
325
+ * Calling this function means the value has been referenced somewhere
326
+ */
327
+ function getVanillaRefParam(
328
+ state: VanillaSerializerState,
329
+ index: number,
330
+ ): string {
313
331
  /**
314
- * Generates the inlined assignment for the reference
315
- * This is different from the assignments array as this one
316
- * signifies creation rather than mutation
332
+ * Creates a new reference ID from a given reference ID
333
+ * This new reference ID means that the reference itself
334
+ * has been referenced at least once, and is used to generate
335
+ * the variables
317
336
  */
318
- protected createAssignment(source: string, value: string): void {
319
- this.assignments.push({
320
- t: AssignmentType.Index,
321
- s: source,
322
- k: NIL,
323
- v: value,
324
- });
337
+ let actualIndex = state.valid.get(index);
338
+ if (actualIndex == null) {
339
+ actualIndex = state.valid.size;
340
+ state.valid.set(index, actualIndex);
325
341
  }
326
-
327
- protected createAddAssignment(ref: number, value: string): void {
328
- this.assignments.push({
329
- t: AssignmentType.Add,
330
- s: this.getRefParam(ref),
331
- k: NIL,
332
- v: value,
333
- });
342
+ let identifier = state.vars[actualIndex];
343
+ if (identifier == null) {
344
+ identifier = getIdentifier(actualIndex);
345
+ state.vars[actualIndex] = identifier;
334
346
  }
347
+ return identifier;
348
+ }
335
349
 
336
- protected createSetAssignment(ref: number, key: string, value: string): void {
337
- this.assignments.push({
338
- t: AssignmentType.Set,
339
- s: this.getRefParam(ref),
340
- k: key,
341
- v: value,
342
- });
343
- }
350
+ function getCrossRefParam(id: number): string {
351
+ return GLOBAL_CONTEXT_REFERENCES + '[' + id + ']';
352
+ }
353
+
354
+ /**
355
+ * Converts the ID of a reference into a identifier string
356
+ * that is used to refer to the object instance in the
357
+ * generated script.
358
+ */
359
+ function getRefParam(ctx: SerializerContext, id: number): string {
360
+ return ctx.mode === SerovalMode.Vanilla
361
+ ? getVanillaRefParam(ctx.state, id)
362
+ : getCrossRefParam(id);
363
+ }
364
+
365
+ function markSerializerRef(ctx: BaseSerializerContext, id: number): void {
366
+ ctx.marked.add(id);
367
+ }
368
+
369
+ function isSerializerRefMarked(
370
+ ctx: BaseSerializerContext,
371
+ id: number,
372
+ ): boolean {
373
+ return ctx.marked.has(id);
374
+ }
344
375
 
345
- protected createDeleteAssignment(ref: number, key: string): void {
346
- this.assignments.push({
347
- t: AssignmentType.Delete,
348
- s: this.getRefParam(ref),
349
- k: key,
350
- v: NIL,
376
+ function pushObjectFlag(
377
+ ctx: SerializerContext,
378
+ flag: SerovalObjectFlags,
379
+ id: number,
380
+ ): void {
381
+ if (flag !== SerovalObjectFlags.None) {
382
+ markSerializerRef(ctx.base, id);
383
+ ctx.base.flags.push({
384
+ type: flag,
385
+ value: getRefParam(ctx, id),
351
386
  });
352
387
  }
388
+ }
353
389
 
354
- protected createArrayAssign(
355
- ref: number,
356
- index: number | string,
357
- value: string,
358
- ): void {
359
- this.createAssignment(this.getRefParam(ref) + '[' + index + ']', value);
390
+ function resolveFlags(ctx: BaseSerializerContext): string | undefined {
391
+ let result = '';
392
+ for (let i = 0, current = ctx.flags, len = current.length; i < len; i++) {
393
+ const flag = current[i];
394
+ result += OBJECT_FLAG_CONSTRUCTOR[flag.type] + '(' + flag.value + '),';
360
395
  }
396
+ return result;
397
+ }
361
398
 
362
- protected createObjectAssign(ref: number, key: string, value: string): void {
363
- this.createAssignment(this.getRefParam(ref) + '.' + key, value);
399
+ function resolvePatches(ctx: BaseSerializerContext): string | undefined {
400
+ const assignments = resolveAssignments(ctx.assignments);
401
+ const flags = resolveFlags(ctx);
402
+ if (assignments) {
403
+ if (flags) {
404
+ return assignments + flags;
405
+ }
406
+ return assignments;
364
407
  }
408
+ return flags;
409
+ }
365
410
 
366
- /**
367
- * Checks if the value is in the stack. Stack here is a reference
368
- * structure to know if a object is to be accessed in a TDZ.
369
- */
370
- isIndexedValueInStack(node: SerovalNode): boolean {
371
- return (
372
- node.t === SerovalNodeType.IndexedValue && this.stack.includes(node.i)
373
- );
374
- }
411
+ /**
412
+ * Generates the inlined assignment for the reference
413
+ * This is different from the assignments array as this one
414
+ * signifies creation rather than mutation
415
+ */
416
+ function createAssignment(
417
+ ctx: BaseSerializerContext,
418
+ source: string,
419
+ value: string,
420
+ ): void {
421
+ ctx.assignments.push({
422
+ t: AssignmentType.Index,
423
+ s: source,
424
+ k: NIL,
425
+ v: value,
426
+ });
427
+ }
375
428
 
376
- /**
377
- * Produces an assignment expression. `id` generates a reference
378
- * parameter (through `getRefParam`) and has the option to
379
- * return the reference parameter directly or assign a value to
380
- * it.
381
- */
382
- protected abstract assignIndexedValue(id: number, value: string): string;
429
+ function createAddAssignment(
430
+ ctx: SerializerContext,
431
+ ref: number,
432
+ value: string,
433
+ ): void {
434
+ ctx.base.assignments.push({
435
+ t: AssignmentType.Add,
436
+ s: getRefParam(ctx, ref),
437
+ k: NIL,
438
+ v: value,
439
+ });
440
+ }
383
441
 
384
- protected serializeReference(node: SerovalReferenceNode): string {
385
- return this.assignIndexedValue(
386
- node.i,
387
- REFERENCES_KEY + '.get("' + node.s + '")',
388
- );
389
- }
442
+ function createSetAssignment(
443
+ ctx: SerializerContext,
444
+ ref: number,
445
+ key: string,
446
+ value: string,
447
+ ): void {
448
+ ctx.base.assignments.push({
449
+ t: AssignmentType.Set,
450
+ s: getRefParam(ctx, ref),
451
+ k: key,
452
+ v: value,
453
+ });
454
+ }
390
455
 
391
- protected serializeArrayItem(
392
- id: number,
393
- item: SerovalNode | undefined,
394
- index: number,
395
- ): string {
396
- // Check if index is a hole
397
- if (item) {
398
- // Check if item is a parent
399
- if (this.isIndexedValueInStack(item)) {
400
- this.markRef(id);
401
- this.createArrayAssign(
402
- id,
403
- index,
404
- this.getRefParam((item as SerovalIndexedValueNode).i),
405
- );
406
- return '';
407
- }
408
- return this.serialize(item);
409
- }
410
- return '';
411
- }
456
+ function createDeleteAssignment(
457
+ ctx: SerializerContext,
458
+ ref: number,
459
+ key: string,
460
+ ): void {
461
+ ctx.base.assignments.push({
462
+ t: AssignmentType.Delete,
463
+ s: getRefParam(ctx, ref),
464
+ k: key,
465
+ v: NIL,
466
+ });
467
+ }
412
468
 
413
- protected serializeArray(node: SerovalArrayNode): string {
414
- const id = node.i;
415
- if (node.l) {
416
- this.stack.push(id);
417
- const list = node.a;
418
- let values = this.serializeArrayItem(id, list[0], 0);
419
- // This is different than Map and Set
420
- // because we also need to serialize
421
- // the holes of the Array
422
- let isHoley = values === '';
423
- for (let i = 1, len = node.l, item: string; i < len; i++) {
424
- item = this.serializeArrayItem(id, list[i], i);
425
- values += ',' + item;
426
- isHoley = item === '';
427
- }
428
- this.stack.pop();
429
- this.pushObjectFlag(node.o, node.i);
430
- return this.assignIndexedValue(id, '[' + values + (isHoley ? ',]' : ']'));
431
- }
432
- return this.assignIndexedValue(id, '[]');
433
- }
469
+ function createArrayAssign(
470
+ ctx: SerializerContext,
471
+ ref: number,
472
+ index: number | string,
473
+ value: string,
474
+ ): void {
475
+ createAssignment(ctx.base, getRefParam(ctx, ref) + '[' + index + ']', value);
476
+ }
434
477
 
435
- protected serializeProperty(
436
- source: SerovalNodeWithProperties,
437
- key: SerovalObjectRecordKey,
438
- val: SerovalNode,
439
- ): string {
440
- if (typeof key === 'string') {
441
- const check = Number(key);
442
- const isIdentifier =
443
- // Test if key is a valid positive number or JS identifier
444
- // so that we don't have to serialize the key and wrap with brackets
445
- (check >= 0 &&
446
- // It's also important to consider that if the key is
447
- // indeed numeric, we need to make sure that when
448
- // converted back into a string, it's still the same
449
- // to the original key. This allows us to differentiate
450
- // keys that has numeric formats but in a different
451
- // format, which can cause unintentional key declaration
452
- // Example: { 0x1: 1 } vs { '0x1': 1 }
453
- check.toString() === key) ||
454
- isValidIdentifier(key);
455
- if (this.isIndexedValueInStack(val)) {
456
- const refParam = this.getRefParam((val as SerovalIndexedValueNode).i);
457
- this.markRef(source.i);
458
- // Strict identifier check, make sure
459
- // that it isn't numeric (except NaN)
460
- if (isIdentifier && check !== check) {
461
- this.createObjectAssign(source.i, key, refParam);
462
- } else {
463
- this.createArrayAssign(
464
- source.i,
465
- isIdentifier ? key : '"' + key + '"',
466
- refParam,
467
- );
468
- }
469
- return '';
470
- }
471
- return (isIdentifier ? key : '"' + key + '"') + ':' + this.serialize(val);
472
- }
473
- return '[' + this.serialize(key) + ']:' + this.serialize(val);
474
- }
478
+ function createObjectAssign(
479
+ ctx: SerializerContext,
480
+ ref: number,
481
+ key: string,
482
+ value: string,
483
+ ): void {
484
+ createAssignment(ctx.base, getRefParam(ctx, ref) + '.' + key, value);
485
+ }
475
486
 
476
- protected serializeProperties(
477
- source: SerovalNodeWithProperties,
478
- record: SerovalObjectRecordNode,
479
- ): string {
480
- const len = record.s;
481
- if (len) {
482
- const keys = record.k;
483
- const values = record.v;
484
- this.stack.push(source.i);
485
- let result = this.serializeProperty(source, keys[0], values[0]);
486
- for (let i = 1, item = result; i < len; i++) {
487
- item = this.serializeProperty(source, keys[i], values[i]);
488
- result += (item && result && ',') + item;
489
- }
490
- this.stack.pop();
491
- return '{' + result + '}';
492
- }
493
- return '{}';
487
+ /**
488
+ * Checks if the value is in the stack. Stack here is a reference
489
+ * structure to know if a object is to be accessed in a TDZ.
490
+ */
491
+ function isIndexedValueInStack(
492
+ ctx: BaseSerializerContext,
493
+ node: SerovalNode,
494
+ ): boolean {
495
+ return node.t === SerovalNodeType.IndexedValue && ctx.stack.includes(node.i);
496
+ }
497
+
498
+ /**
499
+ * Produces an assignment expression. `id` generates a reference
500
+ * parameter (through `getRefParam`) and has the option to
501
+ * return the reference parameter directly or assign a value to
502
+ * it.
503
+ */
504
+ function assignIndexedValue(
505
+ ctx: SerializerContext,
506
+ index: number,
507
+ value: string,
508
+ ): string {
509
+ if (
510
+ ctx.mode === SerovalMode.Vanilla &&
511
+ !isSerializerRefMarked(ctx.base, index)
512
+ ) {
513
+ return value;
494
514
  }
515
+ /**
516
+ * In cross-reference, we have to assume that
517
+ * every reference are going to be referenced
518
+ * in the future, and so we need to store
519
+ * all of it into the reference array.
520
+ *
521
+ * otherwise in vanilla, we only do this if it
522
+ * is actually referenced
523
+ */
524
+ return getRefParam(ctx, index) + '=' + value;
525
+ }
495
526
 
496
- protected serializeObject(node: SerovalObjectNode): string {
497
- this.pushObjectFlag(node.o, node.i);
498
- return this.assignIndexedValue(
499
- node.i,
500
- this.serializeProperties(node, node.p),
501
- );
527
+ function serializeReference(node: SerovalReferenceNode): string {
528
+ return REFERENCES_KEY + '.get("' + node.s + '")';
529
+ }
530
+
531
+ function serializeArrayItem(
532
+ ctx: SerializerContext,
533
+ id: number,
534
+ item: SerovalNode | undefined,
535
+ index: number,
536
+ ): string {
537
+ // Check if index is a hole
538
+ if (item) {
539
+ // Check if item is a parent
540
+ if (isIndexedValueInStack(ctx.base, item)) {
541
+ markSerializerRef(ctx.base, id);
542
+ createArrayAssign(
543
+ ctx,
544
+ id,
545
+ index,
546
+ getRefParam(ctx, (item as SerovalIndexedValueNode).i),
547
+ );
548
+ return '';
549
+ }
550
+ return serialize(ctx, item);
502
551
  }
552
+ return '';
553
+ }
503
554
 
504
- protected serializeWithObjectAssign(
505
- source: SerovalNodeWithProperties,
506
- value: SerovalObjectRecordNode,
507
- serialized: string,
508
- ): string {
509
- const fields = this.serializeProperties(source, value);
510
- if (fields !== '{}') {
511
- return 'Object.assign(' + serialized + ',' + fields + ')';
555
+ function serializeArray(
556
+ ctx: SerializerContext,
557
+ node: SerovalArrayNode,
558
+ ): string {
559
+ const id = node.i;
560
+ if (node.l) {
561
+ ctx.base.stack.push(id);
562
+ const list = node.a;
563
+ let values = serializeArrayItem(ctx, id, list[0], 0);
564
+ // This is different than Map and Set
565
+ // because we also need to serialize
566
+ // the holes of the Array
567
+ let isHoley = values === '';
568
+ for (let i = 1, len = node.l, item: string; i < len; i++) {
569
+ item = serializeArrayItem(ctx, id, list[i], i);
570
+ values += ',' + item;
571
+ isHoley = item === '';
512
572
  }
513
- return serialized;
573
+ ctx.base.stack.pop();
574
+ pushObjectFlag(ctx, node.o, node.i);
575
+ return '[' + values + (isHoley ? ',]' : ']');
514
576
  }
577
+ return '[]';
578
+ }
515
579
 
516
- private serializeStringKeyAssignment(
517
- source: SerovalNodeWithProperties,
518
- mainAssignments: Assignment[],
519
- key: string,
520
- value: SerovalNode,
521
- ): void {
522
- const serialized = this.serialize(value);
580
+ function serializeProperty(
581
+ ctx: SerializerContext,
582
+ source: SerovalNodeWithProperties,
583
+ key: SerovalObjectRecordKey,
584
+ val: SerovalNode,
585
+ ): string {
586
+ if (typeof key === 'string') {
523
587
  const check = Number(key);
524
588
  const isIdentifier =
525
589
  // Test if key is a valid positive number or JS identifier
@@ -534,647 +598,814 @@ export default abstract class BaseSerializerContext
534
598
  // Example: { 0x1: 1 } vs { '0x1': 1 }
535
599
  check.toString() === key) ||
536
600
  isValidIdentifier(key);
537
- if (this.isIndexedValueInStack(value)) {
601
+ if (isIndexedValueInStack(ctx.base, val)) {
602
+ const refParam = getRefParam(ctx, (val as SerovalIndexedValueNode).i);
603
+ markSerializerRef(ctx.base, source.i);
538
604
  // Strict identifier check, make sure
539
605
  // that it isn't numeric (except NaN)
540
606
  if (isIdentifier && check !== check) {
541
- this.createObjectAssign(source.i, key, serialized);
542
- } else {
543
- this.createArrayAssign(
544
- source.i,
545
- isIdentifier ? key : '"' + key + '"',
546
- serialized,
547
- );
548
- }
549
- } else {
550
- const parentAssignment = this.assignments;
551
- this.assignments = mainAssignments;
552
- if (isIdentifier && check !== check) {
553
- this.createObjectAssign(source.i, key, serialized);
607
+ createObjectAssign(ctx, source.i, key, refParam);
554
608
  } else {
555
- this.createArrayAssign(
609
+ createArrayAssign(
610
+ ctx,
556
611
  source.i,
557
612
  isIdentifier ? key : '"' + key + '"',
558
- serialized,
613
+ refParam,
559
614
  );
560
615
  }
561
- this.assignments = parentAssignment;
616
+ return '';
562
617
  }
618
+ return (isIdentifier ? key : '"' + key + '"') + ':' + serialize(ctx, val);
563
619
  }
620
+ return '[' + serialize(ctx, key) + ']:' + serialize(ctx, val);
621
+ }
564
622
 
565
- protected serializeAssignment(
566
- source: SerovalNodeWithProperties,
567
- mainAssignments: Assignment[],
568
- key: SerovalObjectRecordKey,
569
- value: SerovalNode,
570
- ): void {
571
- if (typeof key === 'string') {
572
- this.serializeStringKeyAssignment(source, mainAssignments, key, value);
573
- } else {
574
- const parent = this.stack;
575
- this.stack = [];
576
- const serialized = this.serialize(value);
577
- this.stack = parent;
578
- const parentAssignment = this.assignments;
579
- this.assignments = mainAssignments;
580
- this.createArrayAssign(source.i, this.serialize(key), serialized);
581
- this.assignments = parentAssignment;
623
+ function serializeProperties(
624
+ ctx: SerializerContext,
625
+ source: SerovalNodeWithProperties,
626
+ record: SerovalObjectRecordNode,
627
+ ): string {
628
+ const len = record.s;
629
+ if (len) {
630
+ const keys = record.k;
631
+ const values = record.v;
632
+ ctx.base.stack.push(source.i);
633
+ let result = serializeProperty(ctx, source, keys[0], values[0]);
634
+ for (let i = 1, item = result; i < len; i++) {
635
+ item = serializeProperty(ctx, source, keys[i], values[i]);
636
+ result += (item && result && ',') + item;
582
637
  }
638
+ ctx.base.stack.pop();
639
+ return '{' + result + '}';
583
640
  }
641
+ return '{}';
642
+ }
584
643
 
585
- protected serializeAssignments(
586
- source: SerovalNodeWithProperties,
587
- node: SerovalObjectRecordNode,
588
- ): string | undefined {
589
- const len = node.s;
590
- if (len) {
591
- const mainAssignments: Assignment[] = [];
592
- const keys = node.k;
593
- const values = node.v;
594
- this.stack.push(source.i);
595
- for (let i = 0; i < len; i++) {
596
- this.serializeAssignment(source, mainAssignments, keys[i], values[i]);
597
- }
598
- this.stack.pop();
599
- return resolveAssignments(mainAssignments);
600
- }
601
- return NIL;
644
+ function serializeObject(
645
+ ctx: SerializerContext,
646
+ node: SerovalObjectNode,
647
+ ): string {
648
+ pushObjectFlag(ctx, node.o, node.i);
649
+ return serializeProperties(ctx, node, node.p);
650
+ }
651
+
652
+ function serializeWithObjectAssign(
653
+ ctx: SerializerContext,
654
+ source: SerovalNodeWithProperties,
655
+ value: SerovalObjectRecordNode,
656
+ serialized: string,
657
+ ): string {
658
+ const fields = serializeProperties(ctx, source, value);
659
+ if (fields !== '{}') {
660
+ return 'Object.assign(' + serialized + ',' + fields + ')';
602
661
  }
662
+ return serialized;
663
+ }
603
664
 
604
- protected serializeDictionary(
605
- node: SerovalNodeWithProperties,
606
- init: string,
607
- ): string {
608
- if (node.p) {
609
- if (this.features & Feature.ObjectAssign) {
610
- init = this.serializeWithObjectAssign(node, node.p, init);
611
- } else {
612
- this.markRef(node.i);
613
- const assignments = this.serializeAssignments(node, node.p);
614
- if (assignments) {
615
- return (
616
- '(' +
617
- this.assignIndexedValue(node.i, init) +
618
- ',' +
619
- assignments +
620
- this.getRefParam(node.i) +
621
- ')'
622
- );
623
- }
624
- }
665
+ function serializeStringKeyAssignment(
666
+ ctx: SerializerContext,
667
+ source: SerovalNodeWithProperties,
668
+ mainAssignments: Assignment[],
669
+ key: string,
670
+ value: SerovalNode,
671
+ ): void {
672
+ const base = ctx.base;
673
+ const serialized = serialize(ctx, value);
674
+ const check = Number(key);
675
+ const isIdentifier =
676
+ // Test if key is a valid positive number or JS identifier
677
+ // so that we don't have to serialize the key and wrap with brackets
678
+ (check >= 0 &&
679
+ // It's also important to consider that if the key is
680
+ // indeed numeric, we need to make sure that when
681
+ // converted back into a string, it's still the same
682
+ // to the original key. This allows us to differentiate
683
+ // keys that has numeric formats but in a different
684
+ // format, which can cause unintentional key declaration
685
+ // Example: { 0x1: 1 } vs { '0x1': 1 }
686
+ check.toString() === key) ||
687
+ isValidIdentifier(key);
688
+ if (isIndexedValueInStack(base, value)) {
689
+ // Strict identifier check, make sure
690
+ // that it isn't numeric (except NaN)
691
+ if (isIdentifier && check !== check) {
692
+ createObjectAssign(ctx, source.i, key, serialized);
693
+ } else {
694
+ createArrayAssign(
695
+ ctx,
696
+ source.i,
697
+ isIdentifier ? key : '"' + key + '"',
698
+ serialized,
699
+ );
700
+ }
701
+ } else {
702
+ const parentAssignment = base.assignments;
703
+ base.assignments = mainAssignments;
704
+ if (isIdentifier && check !== check) {
705
+ createObjectAssign(ctx, source.i, key, serialized);
706
+ } else {
707
+ createArrayAssign(
708
+ ctx,
709
+ source.i,
710
+ isIdentifier ? key : '"' + key + '"',
711
+ serialized,
712
+ );
625
713
  }
626
- return this.assignIndexedValue(node.i, init);
714
+ base.assignments = parentAssignment;
627
715
  }
716
+ }
628
717
 
629
- protected serializeNullConstructor(node: SerovalNullConstructorNode): string {
630
- this.pushObjectFlag(node.o, node.i);
631
- return this.serializeDictionary(node, NULL_CONSTRUCTOR);
718
+ function serializeAssignment(
719
+ ctx: SerializerContext,
720
+ source: SerovalNodeWithProperties,
721
+ mainAssignments: Assignment[],
722
+ key: SerovalObjectRecordKey,
723
+ value: SerovalNode,
724
+ ): void {
725
+ if (typeof key === 'string') {
726
+ serializeStringKeyAssignment(ctx, source, mainAssignments, key, value);
727
+ } else {
728
+ const base = ctx.base;
729
+ const parent = base.stack;
730
+ base.stack = [];
731
+ const serialized = serialize(ctx, value);
732
+ base.stack = parent;
733
+ const parentAssignment = base.assignments;
734
+ base.assignments = mainAssignments;
735
+ createArrayAssign(ctx, source.i, serialize(ctx, key), serialized);
736
+ base.assignments = parentAssignment;
632
737
  }
738
+ }
633
739
 
634
- protected serializeDate(node: SerovalDateNode): string {
635
- return this.assignIndexedValue(node.i, 'new Date("' + node.s + '")');
740
+ function serializeAssignments(
741
+ ctx: SerializerContext,
742
+ source: SerovalNodeWithProperties,
743
+ node: SerovalObjectRecordNode,
744
+ ): string | undefined {
745
+ const len = node.s;
746
+ if (len) {
747
+ const mainAssignments: Assignment[] = [];
748
+ const keys = node.k;
749
+ const values = node.v;
750
+ ctx.base.stack.push(source.i);
751
+ for (let i = 0; i < len; i++) {
752
+ serializeAssignment(ctx, source, mainAssignments, keys[i], values[i]);
753
+ }
754
+ ctx.base.stack.pop();
755
+ return resolveAssignments(mainAssignments);
636
756
  }
757
+ return NIL;
758
+ }
637
759
 
638
- protected serializeRegExp(node: SerovalRegExpNode): string {
639
- return this.assignIndexedValue(node.i, '/' + node.c + '/' + node.m);
760
+ function serializeDictionary(
761
+ ctx: SerializerContext,
762
+ node: SerovalNodeWithProperties,
763
+ init: string,
764
+ ): string {
765
+ if (node.p) {
766
+ const base = ctx.base;
767
+ if (base.features & Feature.ObjectAssign) {
768
+ init = serializeWithObjectAssign(ctx, node, node.p, init);
769
+ } else {
770
+ markSerializerRef(base, node.i);
771
+ const assignments = serializeAssignments(ctx, node, node.p);
772
+ if (assignments) {
773
+ return (
774
+ '(' +
775
+ assignIndexedValue(ctx, node.i, init) +
776
+ ',' +
777
+ assignments +
778
+ getRefParam(ctx, node.i) +
779
+ ')'
780
+ );
781
+ }
782
+ }
640
783
  }
784
+ return init;
785
+ }
641
786
 
642
- protected serializeSetItem(id: number, item: SerovalNode): string {
643
- if (this.isIndexedValueInStack(item)) {
644
- this.markRef(id);
645
- this.createAddAssignment(
646
- id,
647
- this.getRefParam((item as SerovalIndexedValueNode).i),
648
- );
649
- return '';
650
- }
651
- return this.serialize(item);
787
+ function serializeNullConstructor(
788
+ ctx: SerializerContext,
789
+ node: SerovalNullConstructorNode,
790
+ ): string {
791
+ pushObjectFlag(ctx, node.o, node.i);
792
+ return serializeDictionary(ctx, node, NULL_CONSTRUCTOR);
793
+ }
794
+
795
+ function serializeDate(node: SerovalDateNode): string {
796
+ return 'new Date("' + node.s + '")';
797
+ }
798
+
799
+ function serializeRegExp(node: SerovalRegExpNode): string {
800
+ return '/' + node.c + '/' + node.m;
801
+ }
802
+
803
+ function serializeSetItem(
804
+ ctx: SerializerContext,
805
+ id: number,
806
+ item: SerovalNode,
807
+ ): string {
808
+ const base = ctx.base;
809
+ if (isIndexedValueInStack(base, item)) {
810
+ markSerializerRef(base, id);
811
+ createAddAssignment(
812
+ ctx,
813
+ id,
814
+ getRefParam(ctx, (item as SerovalIndexedValueNode).i),
815
+ );
816
+ return '';
652
817
  }
818
+ return serialize(ctx, item);
819
+ }
653
820
 
654
- protected serializeSet(node: SerovalSetNode): string {
655
- let serialized = SET_CONSTRUCTOR;
656
- const size = node.l;
657
- const id = node.i;
658
- if (size) {
659
- const items = node.a;
660
- this.stack.push(id);
661
- let result = this.serializeSetItem(id, items[0]);
662
- for (let i = 1, item = result; i < size; i++) {
663
- item = this.serializeSetItem(id, items[i]);
664
- result += (item && result && ',') + item;
665
- }
666
- this.stack.pop();
667
- if (result) {
668
- serialized += '([' + result + '])';
669
- }
821
+ function serializeSet(ctx: SerializerContext, node: SerovalSetNode): string {
822
+ let serialized = SET_CONSTRUCTOR;
823
+ const size = node.l;
824
+ const id = node.i;
825
+ if (size) {
826
+ const items = node.a;
827
+ ctx.base.stack.push(id);
828
+ let result = serializeSetItem(ctx, id, items[0]);
829
+ for (let i = 1, item = result; i < size; i++) {
830
+ item = serializeSetItem(ctx, id, items[i]);
831
+ result += (item && result && ',') + item;
832
+ }
833
+ ctx.base.stack.pop();
834
+ if (result) {
835
+ serialized += '([' + result + '])';
670
836
  }
671
- return this.assignIndexedValue(id, serialized);
672
837
  }
838
+ return serialized;
839
+ }
673
840
 
674
- protected serializeMapEntry(
675
- id: number,
676
- key: SerovalNode,
677
- val: SerovalNode,
678
- sentinel: string,
679
- ): string {
680
- if (this.isIndexedValueInStack(key)) {
681
- // Create reference for the map instance
682
- const keyRef = this.getRefParam((key as SerovalIndexedValueNode).i);
683
- this.markRef(id);
684
- // Check if value is a parent
685
- if (this.isIndexedValueInStack(val)) {
686
- const valueRef = this.getRefParam((val as SerovalIndexedValueNode).i);
687
- // Register an assignment since
688
- // both key and value are a parent of this
689
- // Map instance
690
- this.createSetAssignment(id, keyRef, valueRef);
691
- return '';
692
- }
693
- // Reset the stack
694
- // This is required because the serialized
695
- // value is no longer part of the expression
696
- // tree and has been moved to the deferred
697
- // assignment
698
- if (
699
- val.t !== SerovalNodeType.IndexedValue &&
700
- val.i != null &&
701
- this.isMarked(val.i)
702
- ) {
703
- // We use a trick here using sequence (or comma) expressions
704
- // basically we serialize the intended object in place WITHOUT
705
- // actually returning it, this is by returning a placeholder
706
- // value that we will remove sometime after.
707
- const serialized =
708
- '(' + this.serialize(val) + ',[' + sentinel + ',' + sentinel + '])';
709
- this.createSetAssignment(id, keyRef, this.getRefParam(val.i));
710
- this.createDeleteAssignment(id, sentinel);
711
- return serialized;
712
- }
713
- const parent = this.stack;
714
- this.stack = [];
715
- this.createSetAssignment(id, keyRef, this.serialize(val));
716
- this.stack = parent;
841
+ function serializeMapEntry(
842
+ ctx: SerializerContext,
843
+ id: number,
844
+ key: SerovalNode,
845
+ val: SerovalNode,
846
+ sentinel: string,
847
+ ): string {
848
+ const base = ctx.base;
849
+ if (isIndexedValueInStack(base, key)) {
850
+ // Create reference for the map instance
851
+ const keyRef = getRefParam(ctx, (key as SerovalIndexedValueNode).i);
852
+ markSerializerRef(base, id);
853
+ // Check if value is a parent
854
+ if (isIndexedValueInStack(base, val)) {
855
+ const valueRef = getRefParam(ctx, (val as SerovalIndexedValueNode).i);
856
+ // Register an assignment since
857
+ // both key and value are a parent of this
858
+ // Map instance
859
+ createSetAssignment(ctx, id, keyRef, valueRef);
717
860
  return '';
718
861
  }
719
- if (this.isIndexedValueInStack(val)) {
720
- // Create ref for the Map instance
721
- const valueRef = this.getRefParam((val as SerovalIndexedValueNode).i);
722
- this.markRef(id);
723
- if (
724
- key.t !== SerovalNodeType.IndexedValue &&
725
- key.i != null &&
726
- this.isMarked(key.i)
727
- ) {
728
- const serialized =
729
- '(' + this.serialize(key) + ',[' + sentinel + ',' + sentinel + '])';
730
- this.createSetAssignment(id, this.getRefParam(key.i), valueRef);
731
- this.createDeleteAssignment(id, sentinel);
732
- return serialized;
733
- }
734
- // Reset stack for the key serialization
735
- const parent = this.stack;
736
- this.stack = [];
737
- this.createSetAssignment(id, this.serialize(key), valueRef);
738
- this.stack = parent;
739
- return '';
862
+ // Reset the stack
863
+ // This is required because the serialized
864
+ // value is no longer part of the expression
865
+ // tree and has been moved to the deferred
866
+ // assignment
867
+ if (
868
+ val.t !== SerovalNodeType.IndexedValue &&
869
+ val.i != null &&
870
+ isSerializerRefMarked(base, val.i)
871
+ ) {
872
+ // We use a trick here using sequence (or comma) expressions
873
+ // basically we serialize the intended object in place WITHOUT
874
+ // actually returning it, this is by returning a placeholder
875
+ // value that we will remove sometime after.
876
+ const serialized =
877
+ '(' + serialize(ctx, val) + ',[' + sentinel + ',' + sentinel + '])';
878
+ createSetAssignment(ctx, id, keyRef, getRefParam(ctx, val.i));
879
+ createDeleteAssignment(ctx, id, sentinel);
880
+ return serialized;
740
881
  }
741
-
742
- return '[' + this.serialize(key) + ',' + this.serialize(val) + ']';
882
+ const parent = base.stack;
883
+ base.stack = [];
884
+ createSetAssignment(ctx, id, keyRef, serialize(ctx, val));
885
+ base.stack = parent;
886
+ return '';
743
887
  }
744
-
745
- protected serializeMap(node: SerovalMapNode): string {
746
- let serialized = MAP_CONSTRUCTOR;
747
- const size = node.e.s;
748
- const id = node.i;
749
- const sentinel = node.f;
750
- const sentinelId = this.getRefParam(sentinel.i);
751
- if (size) {
752
- const keys = node.e.k;
753
- const vals = node.e.v;
754
- this.stack.push(id);
755
- let result = this.serializeMapEntry(id, keys[0], vals[0], sentinelId);
756
- for (let i = 1, item = result; i < size; i++) {
757
- item = this.serializeMapEntry(id, keys[i], vals[i], sentinelId);
758
- result += (item && result && ',') + item;
759
- }
760
- this.stack.pop();
761
- // Check if there are any values
762
- // so that the empty Map constructor
763
- // can be used instead
764
- if (result) {
765
- serialized += '([' + result + '])';
766
- }
767
- }
768
- if (sentinel.t === SerovalNodeType.SpecialReference) {
769
- this.markRef(sentinel.i);
770
- serialized = '(' + this.serialize(sentinel) + ',' + serialized + ')';
888
+ if (isIndexedValueInStack(base, val)) {
889
+ // Create ref for the Map instance
890
+ const valueRef = getRefParam(ctx, (val as SerovalIndexedValueNode).i);
891
+ markSerializerRef(base, id);
892
+ if (
893
+ key.t !== SerovalNodeType.IndexedValue &&
894
+ key.i != null &&
895
+ isSerializerRefMarked(base, key.i)
896
+ ) {
897
+ const serialized =
898
+ '(' + serialize(ctx, key) + ',[' + sentinel + ',' + sentinel + '])';
899
+ createSetAssignment(ctx, id, getRefParam(ctx, key.i), valueRef);
900
+ createDeleteAssignment(ctx, id, sentinel);
901
+ return serialized;
771
902
  }
772
- return this.assignIndexedValue(id, serialized);
903
+ // Reset stack for the key serialization
904
+ const parent = base.stack;
905
+ base.stack = [];
906
+ createSetAssignment(ctx, id, serialize(ctx, key), valueRef);
907
+ base.stack = parent;
908
+ return '';
773
909
  }
774
910
 
775
- protected serializeArrayBuffer(node: SerovalArrayBufferNode): string {
776
- let result = 'new Uint8Array(';
777
- const buffer = node.s;
778
- const len = buffer.length;
779
- if (len) {
780
- result += '[' + buffer[0];
781
- for (let i = 1; i < len; i++) {
782
- result += ',' + buffer[i];
783
- }
784
- result += ']';
911
+ return '[' + serialize(ctx, key) + ',' + serialize(ctx, val) + ']';
912
+ }
913
+
914
+ function serializeMap(ctx: SerializerContext, node: SerovalMapNode): string {
915
+ let serialized = MAP_CONSTRUCTOR;
916
+ const size = node.e.s;
917
+ const id = node.i;
918
+ const sentinel = node.f;
919
+ const sentinelId = getRefParam(ctx, sentinel.i);
920
+ const base = ctx.base;
921
+ if (size) {
922
+ const keys = node.e.k;
923
+ const vals = node.e.v;
924
+ base.stack.push(id);
925
+ let result = serializeMapEntry(ctx, id, keys[0], vals[0], sentinelId);
926
+ for (let i = 1, item = result; i < size; i++) {
927
+ item = serializeMapEntry(ctx, id, keys[i], vals[i], sentinelId);
928
+ result += (item && result && ',') + item;
929
+ }
930
+ base.stack.pop();
931
+ // Check if there are any values
932
+ // so that the empty Map constructor
933
+ // can be used instead
934
+ if (result) {
935
+ serialized += '([' + result + '])';
785
936
  }
786
- return this.assignIndexedValue(node.i, result + ').buffer');
787
937
  }
788
-
789
- protected serializeTypedArray(
790
- node: SerovalTypedArrayNode | SerovalBigIntTypedArrayNode,
791
- ): string {
792
- return this.assignIndexedValue(
793
- node.i,
794
- 'new ' +
795
- node.c +
796
- '(' +
797
- this.serialize(node.f) +
798
- ',' +
799
- node.b +
800
- ',' +
801
- node.l +
802
- ')',
803
- );
938
+ if (sentinel.t === SerovalNodeType.SpecialReference) {
939
+ markSerializerRef(base, sentinel.i);
940
+ serialized = '(' + serialize(ctx, sentinel) + ',' + serialized + ')';
804
941
  }
942
+ return serialized;
943
+ }
805
944
 
806
- protected serializeDataView(node: SerovalDataViewNode): string {
807
- return this.assignIndexedValue(
808
- node.i,
809
- 'new DataView(' +
810
- this.serialize(node.f) +
811
- ',' +
812
- node.b +
813
- ',' +
814
- node.l +
815
- ')',
816
- );
817
- }
945
+ function serializeArrayBuffer(
946
+ ctx: SerializerContext,
947
+ node: SerovalArrayBufferNode,
948
+ ): string {
949
+ return getConstructor(ctx, node.f) + '(' + node.l + ',"' + node.s + '")';
950
+ }
818
951
 
819
- protected serializeAggregateError(node: SerovalAggregateErrorNode): string {
820
- const id = node.i;
821
- // `AggregateError` might've been extended
822
- // either through class or custom properties
823
- // Make sure to assign extra properties
824
- this.stack.push(id);
825
- const serialized = this.serializeDictionary(
826
- node,
827
- 'new AggregateError([],"' + node.m + '")',
828
- );
829
- this.stack.pop();
830
- return serialized;
831
- }
952
+ function serializeTypedArray(
953
+ ctx: SerializerContext,
954
+ node: SerovalTypedArrayNode | SerovalBigIntTypedArrayNode,
955
+ ): string {
956
+ return (
957
+ 'new ' +
958
+ node.c +
959
+ '(' +
960
+ serialize(ctx, node.f) +
961
+ ',' +
962
+ node.b +
963
+ ',' +
964
+ node.l +
965
+ ')'
966
+ );
967
+ }
832
968
 
833
- protected serializeError(node: SerovalErrorNode): string {
834
- return this.serializeDictionary(
835
- node,
836
- 'new ' + ERROR_CONSTRUCTOR_STRING[node.s] + '("' + node.m + '")',
837
- );
969
+ function serializeDataView(
970
+ ctx: SerializerContext,
971
+ node: SerovalDataViewNode,
972
+ ): string {
973
+ return (
974
+ 'new DataView(' + serialize(ctx, node.f) + ',' + node.b + ',' + node.l + ')'
975
+ );
976
+ }
977
+
978
+ function serializeAggregateError(
979
+ ctx: SerializerContext,
980
+ node: SerovalAggregateErrorNode,
981
+ ): string {
982
+ const id = node.i;
983
+ // `AggregateError` might've been extended
984
+ // either through class or custom properties
985
+ // Make sure to assign extra properties
986
+ ctx.base.stack.push(id);
987
+ const serialized = serializeDictionary(
988
+ ctx,
989
+ node,
990
+ 'new AggregateError([],"' + node.m + '")',
991
+ );
992
+ ctx.base.stack.pop();
993
+ return serialized;
994
+ }
995
+
996
+ function serializeError(
997
+ ctx: SerializerContext,
998
+ node: SerovalErrorNode,
999
+ ): string {
1000
+ return serializeDictionary(
1001
+ ctx,
1002
+ node,
1003
+ 'new ' + ERROR_CONSTRUCTOR_STRING[node.s] + '("' + node.m + '")',
1004
+ );
1005
+ }
1006
+
1007
+ function serializePromise(
1008
+ ctx: SerializerContext,
1009
+ node: SerovalPromiseNode,
1010
+ ): string {
1011
+ let serialized: string;
1012
+ // Check if resolved value is a parent expression
1013
+ const fulfilled = node.f;
1014
+ const id = node.i;
1015
+ const promiseConstructor = node.s ? PROMISE_RESOLVE : PROMISE_REJECT;
1016
+ const base = ctx.base;
1017
+ if (isIndexedValueInStack(base, fulfilled)) {
1018
+ // A Promise trick, reference the value
1019
+ // inside the `then` expression so that
1020
+ // the Promise evaluates after the parent
1021
+ // has initialized
1022
+ const ref = getRefParam(ctx, (fulfilled as SerovalIndexedValueNode).i);
1023
+ serialized =
1024
+ promiseConstructor +
1025
+ (node.s
1026
+ ? '().then(' + createFunction([], ref) + ')'
1027
+ : '().catch(' + createEffectfulFunction([], 'throw ' + ref) + ')');
1028
+ } else {
1029
+ base.stack.push(id);
1030
+ const result = serialize(ctx, fulfilled);
1031
+ base.stack.pop();
1032
+ // just inline the value/reference here
1033
+ serialized = promiseConstructor + '(' + result + ')';
838
1034
  }
1035
+ return serialized;
1036
+ }
839
1037
 
840
- protected serializePromise(node: SerovalPromiseNode): string {
841
- let serialized: string;
842
- // Check if resolved value is a parent expression
843
- const fulfilled = node.f;
844
- const id = node.i;
845
- const promiseConstructor = node.s ? PROMISE_RESOLVE : PROMISE_REJECT;
846
- if (this.isIndexedValueInStack(fulfilled)) {
847
- // A Promise trick, reference the value
848
- // inside the `then` expression so that
849
- // the Promise evaluates after the parent
850
- // has initialized
851
- const ref = this.getRefParam((fulfilled as SerovalIndexedValueNode).i);
852
- serialized =
853
- promiseConstructor +
854
- (node.s
855
- ? '().then(' + this.createFunction([], ref) + ')'
856
- : '().catch(' +
857
- this.createEffectfulFunction([], 'throw ' + ref) +
858
- ')');
859
- } else {
860
- this.stack.push(id);
861
- const result = this.serialize(fulfilled);
862
- this.stack.pop();
863
- // just inline the value/reference here
864
- serialized = promiseConstructor + '(' + result + ')';
865
- }
866
- return this.assignIndexedValue(id, serialized);
1038
+ function serializeBoxed(
1039
+ ctx: SerializerContext,
1040
+ node: SerovalBoxedNode,
1041
+ ): string {
1042
+ return 'Object(' + serialize(ctx, node.f) + ')';
1043
+ }
1044
+
1045
+ function getConstructor(
1046
+ ctx: SerializerContext,
1047
+ node: SerovalNodeWithID,
1048
+ ): string {
1049
+ const current = serialize(ctx, node);
1050
+ return node.t === SerovalNodeType.IndexedValue
1051
+ ? current
1052
+ : '(' + current + ')';
1053
+ }
1054
+
1055
+ function serializePromiseConstructor(
1056
+ ctx: SerializerContext,
1057
+ node: SerovalPromiseConstructorNode,
1058
+ ): string {
1059
+ if (ctx.mode === SerovalMode.Vanilla) {
1060
+ throw new SerovalUnsupportedNodeError(node);
867
1061
  }
1062
+ const resolver = assignIndexedValue(
1063
+ ctx,
1064
+ node.s,
1065
+ getConstructor(ctx, node.f) + '()',
1066
+ );
1067
+ return '(' + resolver + ').p';
1068
+ }
868
1069
 
869
- protected serializeWellKnownSymbol(node: SerovalWKSymbolNode): string {
870
- return this.assignIndexedValue(node.i, SYMBOL_STRING[node.s]);
1070
+ function serializePromiseResolve(
1071
+ ctx: SerializerContext,
1072
+ node: SerovalPromiseResolveNode,
1073
+ ): string {
1074
+ if (ctx.mode === SerovalMode.Vanilla) {
1075
+ throw new SerovalUnsupportedNodeError(node);
871
1076
  }
1077
+ return (
1078
+ getConstructor(ctx, node.a[0]) +
1079
+ '(' +
1080
+ getRefParam(ctx, node.i) +
1081
+ ',' +
1082
+ serialize(ctx, node.a[1]) +
1083
+ ')'
1084
+ );
1085
+ }
872
1086
 
873
- protected serializeBoxed(node: SerovalBoxedNode): string {
874
- return this.assignIndexedValue(
875
- node.i,
876
- 'Object(' + this.serialize(node.f) + ')',
877
- );
1087
+ function serializePromiseReject(
1088
+ ctx: SerializerContext,
1089
+ node: SerovalPromiseRejectNode,
1090
+ ): string {
1091
+ if (ctx.mode === SerovalMode.Vanilla) {
1092
+ throw new SerovalUnsupportedNodeError(node);
878
1093
  }
1094
+ return (
1095
+ getConstructor(ctx, node.a[0]) +
1096
+ '(' +
1097
+ getRefParam(ctx, node.i) +
1098
+ ',' +
1099
+ serialize(ctx, node.a[1]) +
1100
+ ')'
1101
+ );
1102
+ }
879
1103
 
880
- protected serializePlugin(node: SerovalPluginNode): string {
881
- const currentPlugins = this.plugins;
882
- if (currentPlugins) {
883
- for (let i = 0, len = currentPlugins.length; i < len; i++) {
884
- const plugin = currentPlugins[i];
885
- if (plugin.tag === node.c) {
886
- return this.assignIndexedValue(
887
- node.i,
888
- plugin.serialize(node.s, this, {
889
- id: node.i,
890
- }),
891
- );
1104
+ function serializePlugin(
1105
+ ctx: SerializerContext,
1106
+ node: SerovalPluginNode,
1107
+ ): string {
1108
+ const currentPlugins = ctx.base.plugins;
1109
+ if (currentPlugins) {
1110
+ for (let i = 0, len = currentPlugins.length; i < len; i++) {
1111
+ const plugin = currentPlugins[i];
1112
+ if (plugin.tag === node.c) {
1113
+ if (ctx.child == null) {
1114
+ ctx.child = new SerializePluginContext(ctx);
892
1115
  }
1116
+ return plugin.serialize(node.s, ctx.child, {
1117
+ id: node.i,
1118
+ });
893
1119
  }
894
1120
  }
895
- throw new SerovalMissingPluginError(node.c);
896
1121
  }
1122
+ throw new SerovalMissingPluginError(node.c);
1123
+ }
897
1124
 
898
- private getConstructor(node: SerovalNodeWithID): string {
899
- const current = this.serialize(node);
900
- return current === this.getRefParam(node.i) ? current : '(' + current + ')';
1125
+ function serializeIteratorFactory(
1126
+ ctx: SerializerContext,
1127
+ node: SerovalIteratorFactoryNode,
1128
+ ): string {
1129
+ let result = '';
1130
+ let initialized = false;
1131
+ if (node.f.t !== SerovalNodeType.IndexedValue) {
1132
+ markSerializerRef(ctx.base, node.f.i);
1133
+ result = '(' + serialize(ctx, node.f) + ',';
1134
+ initialized = true;
901
1135
  }
902
-
903
- protected serializePromiseConstructor(
904
- node: SerovalPromiseConstructorNode,
905
- ): string {
906
- const resolver = this.assignIndexedValue(node.s, '{p:0,s:0,f:0}');
907
- return this.assignIndexedValue(
908
- node.i,
909
- this.getConstructor(node.f) + '(' + resolver + ')',
910
- );
1136
+ result += assignIndexedValue(
1137
+ ctx,
1138
+ node.i,
1139
+ '(' +
1140
+ SERIALIZED_ITERATOR_CONSTRUCTOR +
1141
+ ')(' +
1142
+ getRefParam(ctx, node.f.i) +
1143
+ ')',
1144
+ );
1145
+ if (initialized) {
1146
+ result += ')';
911
1147
  }
1148
+ return result;
1149
+ }
912
1150
 
913
- protected serializePromiseResolve(node: SerovalPromiseResolveNode): string {
914
- return (
915
- this.getConstructor(node.a[0]) +
916
- '(' +
917
- this.getRefParam(node.i) +
918
- ',' +
919
- this.serialize(node.a[1]) +
920
- ')'
921
- );
922
- }
1151
+ function serializeIteratorFactoryInstance(
1152
+ ctx: SerializerContext,
1153
+ node: SerovalIteratorFactoryInstanceNode,
1154
+ ): string {
1155
+ return getConstructor(ctx, node.a[0]) + '(' + serialize(ctx, node.a[1]) + ')';
1156
+ }
923
1157
 
924
- protected serializePromiseReject(node: SerovalPromiseRejectNode): string {
925
- return (
926
- this.getConstructor(node.a[0]) +
927
- '(' +
928
- this.getRefParam(node.i) +
929
- ',' +
930
- this.serialize(node.a[1]) +
931
- ')'
932
- );
933
- }
1158
+ function serializeAsyncIteratorFactory(
1159
+ ctx: SerializerContext,
1160
+ node: SerovalAsyncIteratorFactoryNode,
1161
+ ): string {
1162
+ const promise = node.a[0];
1163
+ const symbol = node.a[1];
1164
+ const base = ctx.base;
934
1165
 
935
- protected serializeSpecialReference(
936
- node: SerovalSpecialReferenceNode,
937
- ): string {
938
- return this.assignIndexedValue(
939
- node.i,
940
- serializeSpecialReferenceValue(this.features, node.s),
941
- );
942
- }
1166
+ let result = '';
943
1167
 
944
- protected serializeIteratorFactory(node: SerovalIteratorFactoryNode): string {
945
- let result = '';
946
- let initialized = false;
947
- if (node.f.t !== SerovalNodeType.IndexedValue) {
948
- this.markRef(node.f.i);
949
- result = '(' + this.serialize(node.f) + ',';
950
- initialized = true;
951
- }
952
- result += this.assignIndexedValue(
953
- node.i,
954
- this.createFunction(
955
- ['s'],
956
- this.createFunction(
957
- ['i', 'c', 'd', 't'],
958
- '(i=0,t={[' +
959
- this.getRefParam(node.f.i) +
960
- ']:' +
961
- this.createFunction([], 't') +
962
- ',next:' +
963
- this.createEffectfulFunction(
964
- [],
965
- 'if(i>s.d)return{done:!0,value:void 0};if(d=s.v[c=i++],c===s.t)throw d;return{done:c===s.d,value:d}',
966
- ) +
967
- '})',
968
- ),
969
- ),
970
- );
971
- if (initialized) {
972
- result += ')';
973
- }
974
- return result;
1168
+ if (promise.t !== SerovalNodeType.IndexedValue) {
1169
+ markSerializerRef(base, promise.i);
1170
+ result += '(' + serialize(ctx, promise);
1171
+ }
1172
+ if (symbol.t !== SerovalNodeType.IndexedValue) {
1173
+ markSerializerRef(base, symbol.i);
1174
+ result += (result ? ',' : '(') + serialize(ctx, symbol);
1175
+ }
1176
+ if (result) {
1177
+ result += ',';
975
1178
  }
976
1179
 
977
- protected serializeIteratorFactoryInstance(
978
- node: SerovalIteratorFactoryInstanceNode,
979
- ): string {
980
- return (
981
- this.getConstructor(node.a[0]) + '(' + this.serialize(node.a[1]) + ')'
982
- );
1180
+ const iterator = assignIndexedValue(
1181
+ ctx,
1182
+ node.i,
1183
+ '(' +
1184
+ SERIALIZED_ASYNC_ITERATOR_CONSTRUCTOR +
1185
+ ')(' +
1186
+ getRefParam(ctx, symbol.i) +
1187
+ ',' +
1188
+ getRefParam(ctx, promise.i) +
1189
+ ')',
1190
+ );
1191
+
1192
+ if (result) {
1193
+ return result + iterator + ')';
983
1194
  }
984
1195
 
985
- protected serializeAsyncIteratorFactory(
986
- node: SerovalAsyncIteratorFactoryNode,
987
- ): string {
988
- const promise = node.a[0];
989
- const symbol = node.a[1];
1196
+ return iterator;
1197
+ }
990
1198
 
991
- let result = '';
1199
+ function serializeAsyncIteratorFactoryInstance(
1200
+ ctx: SerializerContext,
1201
+ node: SerovalAsyncIteratorFactoryInstanceNode,
1202
+ ): string {
1203
+ return getConstructor(ctx, node.a[0]) + '(' + serialize(ctx, node.a[1]) + ')';
1204
+ }
992
1205
 
993
- if (promise.t !== SerovalNodeType.IndexedValue) {
994
- this.markRef(promise.i);
995
- result += '(' + this.serialize(promise);
996
- }
997
- if (symbol.t !== SerovalNodeType.IndexedValue) {
998
- this.markRef(symbol.i);
999
- result += (result ? ',' : '(') + this.serialize(symbol);
1000
- }
1001
- if (result) {
1002
- result += ',';
1206
+ function serializeStreamConstructor(
1207
+ ctx: SerializerContext,
1208
+ node: SerovalStreamConstructorNode,
1209
+ ): string {
1210
+ const result = assignIndexedValue(
1211
+ ctx,
1212
+ node.i,
1213
+ getConstructor(ctx, node.f) + '()',
1214
+ );
1215
+ const len = node.a.length;
1216
+ if (len) {
1217
+ let values = serialize(ctx, node.a[0]);
1218
+ for (let i = 1; i < len; i++) {
1219
+ values += ',' + serialize(ctx, node.a[i]);
1003
1220
  }
1221
+ return '(' + result + ',' + values + ',' + getRefParam(ctx, node.i) + ')';
1222
+ }
1223
+ return result;
1224
+ }
1004
1225
 
1005
- const iterator = this.assignIndexedValue(
1006
- node.i,
1007
- this.createFunction(
1008
- ['s'],
1009
- this.createFunction(
1010
- ['b', 'c', 'p', 'd', 'e', 't', 'f'],
1011
- /**
1012
- * b = resolved values
1013
- * c = b size
1014
- * p = pending promises
1015
- * d = index where the resolved value stops
1016
- * e = if the last value is a throw
1017
- * t = placeholder variable
1018
- * f = finalize
1019
- */
1020
- '(b=[],c=0,p=[],d=-1,e=!1,f=' +
1021
- this.createEffectfulFunction(
1022
- ['i', 'l'],
1023
- 'for(i=0,l=p.length;i<l;i++)p[i].s({done:!0,value:void 0})',
1024
- ) +
1025
- ',s.on({next:' +
1026
- this.createEffectfulFunction(
1027
- ['v', 't'],
1028
- 'if(t=p.shift())t.s({done:!1,value:v});b.push(v)',
1029
- ) +
1030
- ',throw:' +
1031
- this.createEffectfulFunction(
1032
- ['v', 't'],
1033
- 'if(t=p.shift())t.f(v);f(),d=b.length,e=!0,b.push(v)',
1034
- ) +
1035
- ',return:' +
1036
- this.createEffectfulFunction(
1037
- ['v', 't'],
1038
- 'if(t=p.shift())t.s({done:!0,value:v});f(),d=b.length,b.push(v)',
1039
- ) +
1040
- '}),t={[' +
1041
- this.getRefParam(symbol.i) +
1042
- ']:' +
1043
- this.createFunction([], 't.p') +
1044
- ',next:' +
1045
- this.createEffectfulFunction(
1046
- ['i', 't', 'v'],
1047
- 'if(d===-1){return((i=c++)>=b.length)?(' +
1048
- this.getRefParam(promise.i) +
1049
- '(t={p:0,s:0,f:0}),p.push(t),t.p):{done:!1,value:b[i]}}if(c>d)return{done:!0,value:void 0};if(v=b[i=c++],i!==d)return{done:!1,value:v};if(e)throw v;return{done:!0,value:v}',
1050
- ) +
1051
- '})',
1052
- ),
1053
- ),
1054
- );
1226
+ function serializeStreamNext(
1227
+ ctx: SerializerContext,
1228
+ node: SerovalStreamNextNode,
1229
+ ): string {
1230
+ return getRefParam(ctx, node.i) + '.next(' + serialize(ctx, node.f) + ')';
1231
+ }
1055
1232
 
1056
- if (result) {
1057
- return result + iterator + ')';
1058
- }
1233
+ function serializeStreamThrow(
1234
+ ctx: SerializerContext,
1235
+ node: SerovalStreamThrowNode,
1236
+ ): string {
1237
+ return getRefParam(ctx, node.i) + '.throw(' + serialize(ctx, node.f) + ')';
1238
+ }
1059
1239
 
1060
- return iterator;
1061
- }
1240
+ function serializeStreamReturn(
1241
+ ctx: SerializerContext,
1242
+ node: SerovalStreamReturnNode,
1243
+ ): string {
1244
+ return getRefParam(ctx, node.i) + '.return(' + serialize(ctx, node.f) + ')';
1245
+ }
1062
1246
 
1063
- protected serializeAsyncIteratorFactoryInstance(
1064
- node: SerovalAsyncIteratorFactoryInstanceNode,
1065
- ): string {
1066
- return (
1067
- this.getConstructor(node.a[0]) + '(' + this.serialize(node.a[1]) + ')'
1068
- );
1247
+ function serializeAssignable(
1248
+ ctx: SerializerContext,
1249
+ node: SerovalNode,
1250
+ ): string {
1251
+ switch (node.t) {
1252
+ case SerovalNodeType.WKSymbol:
1253
+ return SYMBOL_STRING[node.s];
1254
+ case SerovalNodeType.Reference:
1255
+ return serializeReference(node);
1256
+ case SerovalNodeType.Array:
1257
+ return serializeArray(ctx, node);
1258
+ case SerovalNodeType.Object:
1259
+ return serializeObject(ctx, node);
1260
+ case SerovalNodeType.NullConstructor:
1261
+ return serializeNullConstructor(ctx, node);
1262
+ case SerovalNodeType.Date:
1263
+ return serializeDate(node);
1264
+ case SerovalNodeType.RegExp:
1265
+ return serializeRegExp(node);
1266
+ case SerovalNodeType.Set:
1267
+ return serializeSet(ctx, node);
1268
+ case SerovalNodeType.Map:
1269
+ return serializeMap(ctx, node);
1270
+ case SerovalNodeType.ArrayBuffer:
1271
+ return serializeArrayBuffer(ctx, node);
1272
+ case SerovalNodeType.BigIntTypedArray:
1273
+ case SerovalNodeType.TypedArray:
1274
+ return serializeTypedArray(ctx, node);
1275
+ case SerovalNodeType.DataView:
1276
+ return serializeDataView(ctx, node);
1277
+ case SerovalNodeType.AggregateError:
1278
+ return serializeAggregateError(ctx, node);
1279
+ case SerovalNodeType.Error:
1280
+ return serializeError(ctx, node);
1281
+ case SerovalNodeType.Promise:
1282
+ return serializePromise(ctx, node);
1283
+ case SerovalNodeType.Boxed:
1284
+ return serializeBoxed(ctx, node);
1285
+ case SerovalNodeType.PromiseConstructor:
1286
+ return serializePromiseConstructor(ctx, node);
1287
+ case SerovalNodeType.Plugin:
1288
+ return serializePlugin(ctx, node);
1289
+ case SerovalNodeType.SpecialReference:
1290
+ return SPECIAL_REF_STRING[node.s];
1291
+ default:
1292
+ throw new SerovalUnsupportedNodeError(node);
1069
1293
  }
1294
+ }
1070
1295
 
1071
- protected serializeStreamConstructor(
1072
- node: SerovalStreamConstructorNode,
1073
- ): string {
1074
- const result = this.assignIndexedValue(
1075
- node.i,
1076
- this.getConstructor(node.f) + '()',
1077
- );
1078
- const len = node.a.length;
1079
- if (len) {
1080
- let values = this.serialize(node.a[0]);
1081
- for (let i = 1; i < len; i++) {
1082
- values += ',' + this.serialize(node.a[i]);
1083
- }
1084
- return '(' + result + ',' + values + ',' + this.getRefParam(node.i) + ')';
1085
- }
1086
- return result;
1296
+ function serialize(ctx: SerializerContext, node: SerovalNode): string {
1297
+ switch (node.t) {
1298
+ case SerovalNodeType.Constant:
1299
+ return CONSTANT_STRING[node.s];
1300
+ case SerovalNodeType.Number:
1301
+ return '' + node.s;
1302
+ case SerovalNodeType.String:
1303
+ return '"' + node.s + '"';
1304
+ case SerovalNodeType.BigInt:
1305
+ return node.s + 'n';
1306
+ case SerovalNodeType.IndexedValue:
1307
+ return getRefParam(ctx, node.i);
1308
+ case SerovalNodeType.PromiseSuccess:
1309
+ return serializePromiseResolve(ctx, node);
1310
+ case SerovalNodeType.PromiseFailure:
1311
+ return serializePromiseReject(ctx, node);
1312
+ case SerovalNodeType.IteratorFactory:
1313
+ return serializeIteratorFactory(ctx, node);
1314
+ case SerovalNodeType.IteratorFactoryInstance:
1315
+ return serializeIteratorFactoryInstance(ctx, node);
1316
+ case SerovalNodeType.AsyncIteratorFactory:
1317
+ return serializeAsyncIteratorFactory(ctx, node);
1318
+ case SerovalNodeType.AsyncIteratorFactoryInstance:
1319
+ return serializeAsyncIteratorFactoryInstance(ctx, node);
1320
+ case SerovalNodeType.StreamConstructor:
1321
+ return serializeStreamConstructor(ctx, node);
1322
+ case SerovalNodeType.StreamNext:
1323
+ return serializeStreamNext(ctx, node);
1324
+ case SerovalNodeType.StreamThrow:
1325
+ return serializeStreamThrow(ctx, node);
1326
+ case SerovalNodeType.StreamReturn:
1327
+ return serializeStreamReturn(ctx, node);
1328
+ default:
1329
+ return assignIndexedValue(ctx, node.i, serializeAssignable(ctx, node));
1087
1330
  }
1331
+ }
1088
1332
 
1089
- protected serializeStreamNext(node: SerovalStreamNextNode): string {
1090
- return this.getRefParam(node.i) + '.next(' + this.serialize(node.f) + ')';
1333
+ export function serializeRoot(
1334
+ ctx: SerializerContext,
1335
+ node: SerovalNode,
1336
+ ): string {
1337
+ try {
1338
+ return serialize(ctx, node);
1339
+ } catch (error) {
1340
+ throw error instanceof SerovalSerializationError
1341
+ ? error
1342
+ : new SerovalSerializationError(error);
1091
1343
  }
1344
+ }
1092
1345
 
1093
- protected serializeStreamThrow(node: SerovalStreamThrowNode): string {
1094
- return this.getRefParam(node.i) + '.throw(' + this.serialize(node.f) + ')';
1346
+ export function serializeTopVanilla(
1347
+ ctx: VanillaSerializerContext,
1348
+ tree: SerovalNode,
1349
+ ): string {
1350
+ const result = serialize(ctx, tree);
1351
+ // Shared references detected
1352
+ if (tree.i != null && ctx.state.vars.length) {
1353
+ const patches = resolvePatches(ctx.base);
1354
+ let body = result;
1355
+ if (patches) {
1356
+ // Get (or create) a ref from the source
1357
+ const index = getRefParam(ctx, tree.i);
1358
+ body = result + ',' + patches + index;
1359
+ if (!result.startsWith(index + '=')) {
1360
+ body = index + '=' + body;
1361
+ }
1362
+ body = '(' + body + ')';
1363
+ }
1364
+ return '(' + createFunction(ctx.state.vars, body) + ')()';
1095
1365
  }
1096
-
1097
- protected serializeStreamReturn(node: SerovalStreamReturnNode): string {
1098
- return this.getRefParam(node.i) + '.return(' + this.serialize(node.f) + ')';
1366
+ if (tree.t === SerovalNodeType.Object) {
1367
+ return '(' + result + ')';
1099
1368
  }
1369
+ return result;
1370
+ }
1100
1371
 
1101
- serialize(node: SerovalNode): string {
1102
- try {
1103
- switch (node.t) {
1104
- case SerovalNodeType.Constant:
1105
- return CONSTANT_STRING[node.s];
1106
- case SerovalNodeType.Number:
1107
- return '' + node.s;
1108
- case SerovalNodeType.String:
1109
- return '"' + node.s + '"';
1110
- case SerovalNodeType.BigInt:
1111
- return node.s + 'n';
1112
- case SerovalNodeType.IndexedValue:
1113
- return this.getRefParam(node.i);
1114
- case SerovalNodeType.Reference:
1115
- return this.serializeReference(node);
1116
- case SerovalNodeType.Array:
1117
- return this.serializeArray(node);
1118
- case SerovalNodeType.Object:
1119
- return this.serializeObject(node);
1120
- case SerovalNodeType.NullConstructor:
1121
- return this.serializeNullConstructor(node);
1122
- case SerovalNodeType.Date:
1123
- return this.serializeDate(node);
1124
- case SerovalNodeType.RegExp:
1125
- return this.serializeRegExp(node);
1126
- case SerovalNodeType.Set:
1127
- return this.serializeSet(node);
1128
- case SerovalNodeType.Map:
1129
- return this.serializeMap(node);
1130
- case SerovalNodeType.ArrayBuffer:
1131
- return this.serializeArrayBuffer(node);
1132
- case SerovalNodeType.BigIntTypedArray:
1133
- case SerovalNodeType.TypedArray:
1134
- return this.serializeTypedArray(node);
1135
- case SerovalNodeType.DataView:
1136
- return this.serializeDataView(node);
1137
- case SerovalNodeType.AggregateError:
1138
- return this.serializeAggregateError(node);
1139
- case SerovalNodeType.Error:
1140
- return this.serializeError(node);
1141
- case SerovalNodeType.Promise:
1142
- return this.serializePromise(node);
1143
- case SerovalNodeType.WKSymbol:
1144
- return this.serializeWellKnownSymbol(node);
1145
- case SerovalNodeType.Boxed:
1146
- return this.serializeBoxed(node);
1147
- case SerovalNodeType.PromiseConstructor:
1148
- return this.serializePromiseConstructor(node);
1149
- case SerovalNodeType.PromiseSuccess:
1150
- return this.serializePromiseResolve(node);
1151
- case SerovalNodeType.PromiseFailure:
1152
- return this.serializePromiseReject(node);
1153
- case SerovalNodeType.Plugin:
1154
- return this.serializePlugin(node);
1155
- case SerovalNodeType.SpecialReference:
1156
- return this.serializeSpecialReference(node);
1157
- case SerovalNodeType.IteratorFactory:
1158
- return this.serializeIteratorFactory(node);
1159
- case SerovalNodeType.IteratorFactoryInstance:
1160
- return this.serializeIteratorFactoryInstance(node);
1161
- case SerovalNodeType.AsyncIteratorFactory:
1162
- return this.serializeAsyncIteratorFactory(node);
1163
- case SerovalNodeType.AsyncIteratorFactoryInstance:
1164
- return this.serializeAsyncIteratorFactoryInstance(node);
1165
- case SerovalNodeType.StreamConstructor:
1166
- return this.serializeStreamConstructor(node);
1167
- case SerovalNodeType.StreamNext:
1168
- return this.serializeStreamNext(node);
1169
- case SerovalNodeType.StreamThrow:
1170
- return this.serializeStreamThrow(node);
1171
- case SerovalNodeType.StreamReturn:
1172
- return this.serializeStreamReturn(node);
1173
- default:
1174
- throw new SerovalUnsupportedNodeError(node);
1175
- }
1176
- } catch (error) {
1177
- throw new SerovalSerializationError(error);
1372
+ export function serializeTopCross(
1373
+ ctx: CrossSerializerContext,
1374
+ tree: SerovalNode,
1375
+ ): string {
1376
+ // Get the serialized result
1377
+ const result = serialize(ctx, tree);
1378
+ // If the node is a non-reference, return
1379
+ // the result immediately
1380
+ const id = tree.i;
1381
+ if (id == null) {
1382
+ return result;
1383
+ }
1384
+ // Get the patches
1385
+ const patches = resolvePatches(ctx.base);
1386
+ // Get the variable that represents the root
1387
+ const ref = getRefParam(ctx, id);
1388
+ const scopeId = ctx.state.scopeId;
1389
+ // Parameters needed for scoping
1390
+ const params = scopeId == null ? '' : GLOBAL_CONTEXT_REFERENCES;
1391
+ // If there are patches, append it after the result
1392
+ const body = patches ? '(' + result + ',' + patches + ref + ')' : result;
1393
+ // If there are no params, there's no need to generate a function
1394
+ if (params === '') {
1395
+ if (tree.t === SerovalNodeType.Object && !patches) {
1396
+ return '(' + body + ')';
1178
1397
  }
1398
+ return body;
1179
1399
  }
1400
+ // Get the arguments for the IIFE
1401
+ const args =
1402
+ scopeId == null
1403
+ ? '()'
1404
+ : '(' +
1405
+ GLOBAL_CONTEXT_REFERENCES +
1406
+ '["' +
1407
+ serializeString(scopeId) +
1408
+ '"])';
1409
+ // Create the IIFE
1410
+ return '(' + createFunction([params], body) + ')' + args;
1180
1411
  }