@dusted/anqst 1.0.1 → 1.5.1

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.
@@ -0,0 +1,522 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildBoundaryCodecPlan = buildBoundaryCodecPlan;
4
+ const boundary_codec_model_1 = require("./boundary-codec-model");
5
+ class BoundaryPlanBuilder {
6
+ constructor(codecId, analysis) {
7
+ this.codecId = codecId;
8
+ this.analysis = analysis;
9
+ this.nextIdValue = 0;
10
+ this.blobEntries = [];
11
+ this.itemEntries = [];
12
+ this.usedScalarLeafKinds = new Set();
13
+ this.usedBinaryLeafKinds = new Set();
14
+ this.namedNodes = new Map();
15
+ this.namedShapes = new Map();
16
+ this.tsScalarEncodeHelpers = new Set();
17
+ this.tsScalarDecodeHelpers = new Set();
18
+ this.tsBinaryEncodeHelpers = new Set();
19
+ this.tsBinaryDecodeHelpers = new Set();
20
+ this.tsFiniteDomainEncodeHelpers = new Set();
21
+ this.tsFiniteDomainDecodeHelpers = new Set();
22
+ this.cppScalarEncodeHelpers = new Set();
23
+ this.cppScalarDecodeHelpers = new Set();
24
+ this.cppBinaryEncodeHelpers = new Set();
25
+ this.cppBinaryDecodeHelpers = new Set();
26
+ this.cppFiniteDomainEncodeHelpers = new Set();
27
+ this.cppFiniteDomainDecodeHelpers = new Set();
28
+ this.usesArrayCounts = false;
29
+ this.usesOptionalPresence = false;
30
+ this.usesFiniteDomainCodes = false;
31
+ this.nextItemOrder = 0;
32
+ }
33
+ build() {
34
+ const root = this.buildNode(this.analysis.root, { isRoot: true }).node;
35
+ const requirements = this.buildRequirements();
36
+ return {
37
+ codecId: this.codecId,
38
+ typeText: this.analysis.typeText,
39
+ tsTypeText: this.analysis.tsTypeText,
40
+ decodePolicy: "trusted-only",
41
+ analysis: this.analysis,
42
+ root,
43
+ blobEntries: this.blobEntries,
44
+ itemEntries: this.itemEntries,
45
+ requirements
46
+ };
47
+ }
48
+ buildRequirements() {
49
+ const hasBlob = this.blobEntries.length > 0;
50
+ const itemKinds = [...new Set(this.itemEntries.map((entry) => entry.itemKind))];
51
+ const sortedScalarKinds = (values) => [...values].sort();
52
+ const sortedBinaryKinds = (values) => [...values].sort();
53
+ const sortedStrings = (values) => [...values].sort();
54
+ const helperRequirements = (scalarEncodeKinds, scalarDecodeKinds, binaryEncodeKinds, binaryDecodeKinds, finiteDomainEncodeHelpers, finiteDomainDecodeHelpers) => ({
55
+ scalarEncodeKinds: sortedScalarKinds(scalarEncodeKinds),
56
+ scalarDecodeKinds: sortedScalarKinds(scalarDecodeKinds),
57
+ binaryEncodeKinds: sortedBinaryKinds(binaryEncodeKinds),
58
+ binaryDecodeKinds: sortedBinaryKinds(binaryDecodeKinds),
59
+ finiteDomainEncodeHelpers: sortedStrings(finiteDomainEncodeHelpers),
60
+ finiteDomainDecodeHelpers: sortedStrings(finiteDomainDecodeHelpers)
61
+ });
62
+ return {
63
+ hasBlob,
64
+ hasItems: this.itemEntries.length > 0,
65
+ itemKinds,
66
+ itemCountHeaderKinds: [],
67
+ usesArrayCounts: this.usesArrayCounts,
68
+ usesOptionalPresence: this.usesOptionalPresence,
69
+ usesFiniteDomainCodes: this.usesFiniteDomainCodes,
70
+ usedScalarLeafKinds: [...this.usedScalarLeafKinds].sort(),
71
+ usedBinaryLeafKinds: [...this.usedBinaryLeafKinds].sort(),
72
+ tsHelperRequirements: helperRequirements(this.tsScalarEncodeHelpers, this.tsScalarDecodeHelpers, this.tsBinaryEncodeHelpers, this.tsBinaryDecodeHelpers, this.tsFiniteDomainEncodeHelpers, this.tsFiniteDomainDecodeHelpers),
73
+ cppHelperRequirements: helperRequirements(this.cppScalarEncodeHelpers, this.cppScalarDecodeHelpers, this.cppBinaryEncodeHelpers, this.cppBinaryDecodeHelpers, this.cppFiniteDomainEncodeHelpers, this.cppFiniteDomainDecodeHelpers)
74
+ };
75
+ }
76
+ nextId(prefix) {
77
+ this.nextIdValue += 1;
78
+ return `${this.codecId}_${prefix}_${this.nextIdValue}`;
79
+ }
80
+ addBlobEntry(role, path, widthBytes, leafKind, logicalKind) {
81
+ const entryId = this.nextId(role === "leaf" ? "blob" : role.replace(/-/g, "_"));
82
+ this.blobEntries.push({
83
+ entryId,
84
+ role,
85
+ path,
86
+ widthBytes,
87
+ leafKind,
88
+ logicalKind
89
+ });
90
+ this.usedScalarLeafKinds.add(leafKind);
91
+ return entryId;
92
+ }
93
+ addItemEntry(itemKind, path, logicalKind) {
94
+ const entryId = this.nextId(itemKind);
95
+ this.nextItemOrder += 1;
96
+ const entry = { entryId, itemKind, path, logicalKind, order: this.nextItemOrder };
97
+ this.itemEntries.push(entry);
98
+ return entryId;
99
+ }
100
+ static emptyShape() {
101
+ return {
102
+ fixedBlobWidthBytes: 0,
103
+ fixedItemCount: 0,
104
+ hasItems: false,
105
+ itemKinds: new Set(),
106
+ hasVariableExtent: false
107
+ };
108
+ }
109
+ static recursiveNamedShape() {
110
+ return {
111
+ fixedBlobWidthBytes: null,
112
+ fixedItemCount: null,
113
+ hasItems: false,
114
+ itemKinds: new Set(),
115
+ hasVariableExtent: true
116
+ };
117
+ }
118
+ mergeShapes(left, right) {
119
+ const fixedBlobWidthBytes = left.fixedBlobWidthBytes !== null &&
120
+ right.fixedBlobWidthBytes !== null &&
121
+ !left.hasVariableExtent &&
122
+ !right.hasVariableExtent
123
+ ? left.fixedBlobWidthBytes + right.fixedBlobWidthBytes
124
+ : null;
125
+ const fixedItemCount = left.fixedItemCount !== null &&
126
+ right.fixedItemCount !== null &&
127
+ !left.hasVariableExtent &&
128
+ !right.hasVariableExtent
129
+ ? left.fixedItemCount + right.fixedItemCount
130
+ : null;
131
+ return {
132
+ fixedBlobWidthBytes,
133
+ fixedItemCount,
134
+ hasItems: left.hasItems || right.hasItems,
135
+ itemKinds: new Set([...left.itemKinds, ...right.itemKinds]),
136
+ hasVariableExtent: left.hasVariableExtent || right.hasVariableExtent
137
+ };
138
+ }
139
+ chooseLeafPacking(node, isRoot) {
140
+ void isRoot;
141
+ if (node.leaf.region === "blob")
142
+ return "byte-packed";
143
+ if (node.leaf.region === "binary")
144
+ return "binary-packed";
145
+ if (node.leaf.region === "dynamic")
146
+ return "dynamic";
147
+ return "text-packed";
148
+ }
149
+ loweringSelection(mode, reason, helperNameHint) {
150
+ return { mode, reason, helperNameHint };
151
+ }
152
+ defaultInlineLeafLowering() {
153
+ return {
154
+ tsEncode: this.loweringSelection("inline", "trivial-op"),
155
+ tsDecode: this.loweringSelection("inline", "trivial-op"),
156
+ cppEncode: this.loweringSelection("inline", "trivial-op"),
157
+ cppDecode: this.loweringSelection("inline", "trivial-op")
158
+ };
159
+ }
160
+ finiteDomainHelperHint(node) {
161
+ const joined = node.path.join("_");
162
+ return (0, boundary_codec_model_1.sanitizeIdentifier)(`finite_domain_${joined.length > 0 ? joined : node.typeIdentityKey}`);
163
+ }
164
+ chooseLeafLowering(node, selectedPacking, _context) {
165
+ if (node.leaf.region === "binary" && selectedPacking === "binary-packed") {
166
+ const helperNameHint = (0, boundary_codec_model_1.sanitizeIdentifier)(`binary_${String(node.leaf.key)}`);
167
+ return {
168
+ tsEncode: this.loweringSelection("helper-call", "complex-op", helperNameHint),
169
+ tsDecode: this.loweringSelection("helper-call", "complex-op", helperNameHint),
170
+ cppEncode: this.loweringSelection("helper-call", "complex-op", helperNameHint),
171
+ cppDecode: this.loweringSelection("helper-call", "complex-op", helperNameHint)
172
+ };
173
+ }
174
+ return this.defaultInlineLeafLowering();
175
+ }
176
+ chooseFiniteDomainLowering(node, representation, _context) {
177
+ if (representation.kind === "coded-scalar" && node.domain.variants.length > 64) {
178
+ const helperNameHint = this.finiteDomainHelperHint(node);
179
+ return {
180
+ tsEncode: this.loweringSelection("helper-call", "code-size", helperNameHint),
181
+ tsDecode: this.loweringSelection("helper-call", "code-size", helperNameHint),
182
+ cppEncode: this.loweringSelection("helper-call", "code-size", helperNameHint),
183
+ cppDecode: this.loweringSelection("helper-call", "code-size", helperNameHint)
184
+ };
185
+ }
186
+ return this.defaultInlineLeafLowering();
187
+ }
188
+ registerLeafLoweringRequirements(node, lowering) {
189
+ const scalarLeaf = node.leaf.key;
190
+ const binaryLeaf = node.leaf.key;
191
+ if (node.leaf.region === "blob") {
192
+ if (lowering.tsEncode.mode === "helper-call")
193
+ this.tsScalarEncodeHelpers.add(scalarLeaf);
194
+ if (lowering.tsDecode.mode === "helper-call")
195
+ this.tsScalarDecodeHelpers.add(scalarLeaf);
196
+ if (lowering.cppEncode.mode === "helper-call")
197
+ this.cppScalarEncodeHelpers.add(scalarLeaf);
198
+ if (lowering.cppDecode.mode === "helper-call")
199
+ this.cppScalarDecodeHelpers.add(scalarLeaf);
200
+ return;
201
+ }
202
+ if (node.leaf.region === "binary") {
203
+ if (lowering.tsEncode.mode === "helper-call")
204
+ this.tsBinaryEncodeHelpers.add(binaryLeaf);
205
+ if (lowering.tsDecode.mode === "helper-call")
206
+ this.tsBinaryDecodeHelpers.add(binaryLeaf);
207
+ if (lowering.cppEncode.mode === "helper-call")
208
+ this.cppBinaryEncodeHelpers.add(binaryLeaf);
209
+ if (lowering.cppDecode.mode === "helper-call")
210
+ this.cppBinaryDecodeHelpers.add(binaryLeaf);
211
+ }
212
+ }
213
+ registerFiniteDomainLoweringRequirements(representation, lowering) {
214
+ if (representation.kind === "coded-scalar") {
215
+ if (lowering.tsEncode.mode === "helper-call")
216
+ this.tsScalarEncodeHelpers.add(representation.scalarKind);
217
+ if (lowering.tsDecode.mode === "helper-call")
218
+ this.tsScalarDecodeHelpers.add(representation.scalarKind);
219
+ if (lowering.cppEncode.mode === "helper-call")
220
+ this.cppScalarEncodeHelpers.add(representation.scalarKind);
221
+ if (lowering.cppDecode.mode === "helper-call")
222
+ this.cppScalarDecodeHelpers.add(representation.scalarKind);
223
+ }
224
+ if (lowering.tsEncode.mode === "helper-call" && lowering.tsEncode.helperNameHint) {
225
+ this.tsFiniteDomainEncodeHelpers.add(lowering.tsEncode.helperNameHint);
226
+ }
227
+ if (lowering.tsDecode.mode === "helper-call" && lowering.tsDecode.helperNameHint) {
228
+ this.tsFiniteDomainDecodeHelpers.add(lowering.tsDecode.helperNameHint);
229
+ }
230
+ if (lowering.cppEncode.mode === "helper-call" && lowering.cppEncode.helperNameHint) {
231
+ this.cppFiniteDomainEncodeHelpers.add(lowering.cppEncode.helperNameHint);
232
+ }
233
+ if (lowering.cppDecode.mode === "helper-call" && lowering.cppDecode.helperNameHint) {
234
+ this.cppFiniteDomainDecodeHelpers.add(lowering.cppDecode.helperNameHint);
235
+ }
236
+ }
237
+ chooseFiniteDomainScalar(variantCount) {
238
+ if (variantCount <= 0xff)
239
+ return "uint8";
240
+ if (variantCount <= 0xffff)
241
+ return "uint16";
242
+ return "uint32";
243
+ }
244
+ chooseRootArrayExtentStrategy(nodeShape, isRoot) {
245
+ if (!isRoot || nodeShape.hasVariableExtent)
246
+ return "explicit-count";
247
+ if (nodeShape.fixedBlobWidthBytes !== null && nodeShape.fixedBlobWidthBytes > 0)
248
+ return "blob-tail";
249
+ if (nodeShape.fixedItemCount !== null && nodeShape.fixedItemCount > 0)
250
+ return "item-tail";
251
+ return "explicit-count";
252
+ }
253
+ buildNode(node, context) {
254
+ switch (node.nodeKind) {
255
+ case "leaf":
256
+ return this.buildLeafNode(node, context);
257
+ case "finite-domain":
258
+ return this.buildFiniteDomainNode(node, context);
259
+ case "array":
260
+ return this.buildArrayNode(node, context);
261
+ case "struct":
262
+ return this.buildStructNode(node, context);
263
+ case "named":
264
+ return this.buildNamedNode(node);
265
+ }
266
+ }
267
+ buildNamedNode(node) {
268
+ const existing = this.namedNodes.get(node.name);
269
+ if (existing) {
270
+ return {
271
+ node: existing,
272
+ shape: this.namedShapes.get(node.name) ?? BoundaryPlanBuilder.recursiveNamedShape()
273
+ };
274
+ }
275
+ const placeholder = {
276
+ nodeKind: "named",
277
+ typeText: node.typeText,
278
+ path: node.path,
279
+ typeIdentityKey: node.typeIdentityKey,
280
+ cppNameHintParts: node.cppNameHintParts,
281
+ name: node.name,
282
+ target: null
283
+ };
284
+ this.namedNodes.set(node.name, placeholder);
285
+ const targetResult = this.buildNode(node.target, { isRoot: false });
286
+ placeholder.target = targetResult.node;
287
+ this.namedShapes.set(node.name, targetResult.shape);
288
+ return {
289
+ node: placeholder,
290
+ shape: targetResult.shape
291
+ };
292
+ }
293
+ buildLeafNode(node, context) {
294
+ const selectedPacking = this.chooseLeafPacking(node, context.isRoot);
295
+ const lowering = this.chooseLeafLowering(node, selectedPacking, context);
296
+ this.registerLeafLoweringRequirements(node, lowering);
297
+ if (node.leaf.region === "blob" && selectedPacking === "byte-packed") {
298
+ const widthBytes = node.leaf.fixedByteWidth ?? 0;
299
+ const blobEntryId = this.addBlobEntry("leaf", node.path, widthBytes, node.leaf.key, node.leaf.logicalKind);
300
+ return {
301
+ node: {
302
+ nodeKind: "leaf",
303
+ typeText: node.typeText,
304
+ path: node.path,
305
+ typeIdentityKey: node.typeIdentityKey,
306
+ cppNameHintParts: node.cppNameHintParts,
307
+ leaf: node.leaf,
308
+ selectedPacking,
309
+ lowering,
310
+ blobEntryId
311
+ },
312
+ shape: {
313
+ fixedBlobWidthBytes: widthBytes,
314
+ fixedItemCount: 0,
315
+ hasItems: false,
316
+ itemKinds: new Set(),
317
+ hasVariableExtent: false
318
+ }
319
+ };
320
+ }
321
+ if (node.leaf.region === "binary") {
322
+ this.usedBinaryLeafKinds.add(node.leaf.key);
323
+ return {
324
+ node: {
325
+ nodeKind: "leaf",
326
+ typeText: node.typeText,
327
+ path: node.path,
328
+ typeIdentityKey: node.typeIdentityKey,
329
+ cppNameHintParts: node.cppNameHintParts,
330
+ leaf: node.leaf,
331
+ selectedPacking,
332
+ lowering,
333
+ itemEntryId: this.addItemEntry("binary", node.path, node.leaf.logicalKind)
334
+ },
335
+ shape: {
336
+ fixedBlobWidthBytes: 0,
337
+ fixedItemCount: 1,
338
+ hasItems: true,
339
+ itemKinds: new Set(["binary"]),
340
+ hasVariableExtent: false
341
+ }
342
+ };
343
+ }
344
+ const itemKind = node.leaf.region === "dynamic" ? "dynamic" : "string";
345
+ return {
346
+ node: {
347
+ nodeKind: "leaf",
348
+ typeText: node.typeText,
349
+ path: node.path,
350
+ typeIdentityKey: node.typeIdentityKey,
351
+ cppNameHintParts: node.cppNameHintParts,
352
+ leaf: node.leaf,
353
+ selectedPacking,
354
+ lowering,
355
+ itemEntryId: this.addItemEntry(itemKind, node.path, node.leaf.logicalKind)
356
+ },
357
+ shape: {
358
+ fixedBlobWidthBytes: 0,
359
+ fixedItemCount: 1,
360
+ hasItems: true,
361
+ itemKinds: new Set([itemKind]),
362
+ hasVariableExtent: false
363
+ }
364
+ };
365
+ }
366
+ buildFiniteDomainNode(node, context) {
367
+ if (context.isRoot && node.domain.primitive === "boolean") {
368
+ const representation = { kind: "identity-text" };
369
+ const lowering = this.chooseFiniteDomainLowering(node, representation, context);
370
+ this.registerFiniteDomainLoweringRequirements(representation, lowering);
371
+ return {
372
+ node: {
373
+ nodeKind: "finite-domain",
374
+ typeText: node.typeText,
375
+ path: node.path,
376
+ typeIdentityKey: node.typeIdentityKey,
377
+ cppNameHintParts: node.cppNameHintParts,
378
+ domain: node.domain,
379
+ representation,
380
+ lowering,
381
+ itemEntryId: this.addItemEntry("string", node.path, `finite-domain-${node.domain.primitive}`)
382
+ },
383
+ shape: {
384
+ fixedBlobWidthBytes: 0,
385
+ fixedItemCount: 1,
386
+ hasItems: true,
387
+ itemKinds: new Set(["string"]),
388
+ hasVariableExtent: false
389
+ }
390
+ };
391
+ }
392
+ const scalarKind = this.chooseFiniteDomainScalar(node.domain.variants.length);
393
+ this.usesFiniteDomainCodes = true;
394
+ const widthBytes = scalarKind === "uint8" ? 1 : scalarKind === "uint16" ? 2 : 4;
395
+ const representation = { kind: "coded-scalar", scalarKind };
396
+ const lowering = this.chooseFiniteDomainLowering(node, representation, context);
397
+ this.registerFiniteDomainLoweringRequirements(representation, lowering);
398
+ const blobEntryId = this.addBlobEntry("finite-domain-code", node.path, widthBytes, scalarKind, `finite-domain-${node.domain.primitive}`);
399
+ return {
400
+ node: {
401
+ nodeKind: "finite-domain",
402
+ typeText: node.typeText,
403
+ path: node.path,
404
+ typeIdentityKey: node.typeIdentityKey,
405
+ cppNameHintParts: node.cppNameHintParts,
406
+ domain: node.domain,
407
+ representation,
408
+ lowering,
409
+ blobEntryId
410
+ },
411
+ shape: {
412
+ fixedBlobWidthBytes: widthBytes,
413
+ fixedItemCount: 0,
414
+ hasItems: false,
415
+ itemKinds: new Set(),
416
+ hasVariableExtent: false
417
+ }
418
+ };
419
+ }
420
+ buildArrayNode(node, context) {
421
+ const elementResult = this.buildNode(node.element, { isRoot: false });
422
+ const extentStrategy = this.chooseRootArrayExtentStrategy(elementResult.shape, context.isRoot);
423
+ const countEntryId = extentStrategy === "explicit-count"
424
+ ? (() => {
425
+ this.usesArrayCounts = true;
426
+ return this.addBlobEntry("array-count", node.path, 4, "uint32", "array-count");
427
+ })()
428
+ : undefined;
429
+ return {
430
+ node: {
431
+ nodeKind: "array",
432
+ typeText: node.typeText,
433
+ path: node.path,
434
+ typeIdentityKey: node.typeIdentityKey,
435
+ cppNameHintParts: node.cppNameHintParts,
436
+ extentStrategy,
437
+ countEntryId,
438
+ elementBlobWidthBytes: elementResult.shape.fixedBlobWidthBytes ?? undefined,
439
+ elementItemCount: elementResult.shape.fixedItemCount ?? undefined,
440
+ element: elementResult.node
441
+ },
442
+ shape: {
443
+ fixedBlobWidthBytes: null,
444
+ fixedItemCount: null,
445
+ hasItems: elementResult.shape.hasItems,
446
+ itemKinds: new Set(elementResult.shape.itemKinds),
447
+ hasVariableExtent: true
448
+ }
449
+ };
450
+ }
451
+ shouldTailOptimizeStruct(node) {
452
+ if (node.fields.length < 2)
453
+ return false;
454
+ if (this.analysis.root !== node)
455
+ return false;
456
+ let candidates = 0;
457
+ for (const field of node.fields) {
458
+ if (field.optional || field.node.nodeKind !== "array")
459
+ continue;
460
+ const elementNode = field.node.element;
461
+ if (elementNode.nodeKind !== "leaf" && elementNode.nodeKind !== "finite-domain")
462
+ continue;
463
+ candidates += 1;
464
+ }
465
+ return candidates === 1;
466
+ }
467
+ buildStructNode(node, context) {
468
+ const ordering = this.shouldTailOptimizeStruct(node) ? "tail-optimized" : "source-order";
469
+ const orderedFields = ordering === "tail-optimized"
470
+ ? [...node.fields].sort((left, right) => {
471
+ const leftIsArray = left.node.nodeKind === "array" ? 1 : 0;
472
+ const rightIsArray = right.node.nodeKind === "array" ? 1 : 0;
473
+ return leftIsArray - rightIsArray;
474
+ })
475
+ : node.fields;
476
+ const builtFields = [];
477
+ let shape = BoundaryPlanBuilder.emptyShape();
478
+ for (const field of orderedFields) {
479
+ const presenceEntryId = field.optional
480
+ ? (() => {
481
+ this.usesOptionalPresence = true;
482
+ return this.addBlobEntry("optional-presence", field.path, 1, "uint8", "optional-presence");
483
+ })()
484
+ : undefined;
485
+ const fieldResult = this.buildNode(field.node, { isRoot: false });
486
+ builtFields.push({
487
+ name: field.name,
488
+ optional: field.optional,
489
+ typeText: field.typeText,
490
+ path: field.path,
491
+ presenceStrategy: field.optional ? "byte-flag" : undefined,
492
+ presenceEntryId,
493
+ node: fieldResult.node
494
+ });
495
+ const fieldShape = field.optional
496
+ ? {
497
+ fixedBlobWidthBytes: null,
498
+ fixedItemCount: null,
499
+ hasItems: fieldResult.shape.hasItems,
500
+ itemKinds: new Set(fieldResult.shape.itemKinds),
501
+ hasVariableExtent: true
502
+ }
503
+ : fieldResult.shape;
504
+ shape = this.mergeShapes(shape, fieldShape);
505
+ }
506
+ return {
507
+ node: {
508
+ nodeKind: "struct",
509
+ typeText: node.typeText,
510
+ path: node.path,
511
+ typeIdentityKey: node.typeIdentityKey,
512
+ cppNameHintParts: node.cppNameHintParts,
513
+ ordering,
514
+ fields: builtFields
515
+ },
516
+ shape
517
+ };
518
+ }
519
+ }
520
+ function buildBoundaryCodecPlan(codecId, analysis) {
521
+ return new BoundaryPlanBuilder(codecId, analysis).build();
522
+ }