@fraczak/k 6.0.2 → 6.0.4

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.
package/object.mjs CHANGED
@@ -1,11 +1,10 @@
1
1
  import { annotate } from "./index.mjs";
2
- import run from "./run.mjs";
2
+ import run, { run_rel, run_converged } from "./run.mjs";
3
3
  import codes from "./codes.mjs";
4
4
  import { TypePatternGraph } from "./TypePatternGraph.mjs";
5
5
  import { patterns2filters, prettyRel } from "./pretty.mjs";
6
6
 
7
- const MAGIC = Buffer.from([0x4b, 0x4f, 0x42, 0x4a, 0x00, 0x01, 0x0a]);
8
- const LIB_MAGIC = Buffer.from([0x4b, 0x4c, 0x49, 0x42, 0x00, 0x01, 0x0a]);
7
+ const OBJECT_MAGIC = Buffer.from("KOBJ\n");
9
8
 
10
9
  function serializeTypePatternGraph(typePatternGraph) {
11
10
  return {
@@ -27,9 +26,11 @@ function hydrateTypePatternGraph(serialized) {
27
26
  return graph;
28
27
  }
29
28
 
30
- function serializeRelation(rel) {
29
+ function serializeRelation(rel, typeDerivation = rel.typeDerivation) {
30
+ const def = stripDebugFieldsFromExp(stripRelationBoundaryFilters(rel.def, rel.typePatternGraph));
31
31
  return {
32
- ...rel,
32
+ def,
33
+ ...(typeDerivation ? { typeDerivation } : {}),
33
34
  typePatternGraph: serializeTypePatternGraph(rel.typePatternGraph)
34
35
  };
35
36
  }
@@ -42,9 +43,13 @@ function hydrateRelation(rel) {
42
43
  }
43
44
 
44
45
  function serializeAnnotated(annotated) {
46
+ const typeDerivations = buildRelTypeDerivations(annotated.rels, annotated.compileStats);
45
47
  return {
46
48
  rels: Object.fromEntries(
47
- Object.entries(annotated.rels).map(([name, rel]) => [name, serializeRelation(rel)])
49
+ Object.entries(annotated.rels).map(([name, rel]) => [
50
+ name,
51
+ serializeRelation(rel, typeDerivations[name])
52
+ ])
48
53
  ),
49
54
  representatives: annotated.representatives,
50
55
  relAlias: annotated.relAlias,
@@ -52,28 +57,290 @@ function serializeAnnotated(annotated) {
52
57
  };
53
58
  }
54
59
 
55
- function hydrateAnnotated(serialized) {
60
+ const NAME_RE = /^[a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*$/;
61
+
62
+ function isSourceName(name) {
63
+ return NAME_RE.test(name) && !name.startsWith("@") && !name.startsWith(":");
64
+ }
65
+
66
+ function addOrigin(meta, hash, type, origin) {
67
+ if (!hash) return;
68
+ if (!meta[hash]) {
69
+ meta[hash] = { type, origins: [] };
70
+ } else if (meta[hash].type == null) {
71
+ meta[hash].type = type;
72
+ } else if (meta[hash].type !== type) {
73
+ throw new Error(`Metadata type conflict for ${hash}: ${meta[hash].type} vs ${type}`);
74
+ }
75
+ if (meta[hash].origins.some((existing) =>
76
+ existing.source === origin.source &&
77
+ existing.name === origin.name &&
78
+ existing.compiledAt === origin.compiledAt
79
+ )) {
80
+ return;
81
+ }
82
+ meta[hash].origins.push(origin);
83
+ }
84
+
85
+ const TYPE_DERIVATION_STATUSES = new Set(["converged", "not-converged", "unknown"]);
86
+
87
+ function validTypeDerivationStatus(status) {
88
+ return TYPE_DERIVATION_STATUSES.has(status) ? status : "unknown";
89
+ }
90
+
91
+ function compileStatsByRelName(compileStats = {}) {
92
+ const byName = {};
93
+ for (const scc of compileStats.sccs || []) {
94
+ for (const member of scc.members || []) {
95
+ byName[member] = scc;
96
+ }
97
+ }
98
+ return byName;
99
+ }
100
+
101
+ function samePatternId(typePatternGraph, left, right) {
102
+ return typePatternGraph.find(left) === typePatternGraph.find(right);
103
+ }
104
+
105
+ function isBoundaryFilter(exp, typePatternGraph, patternId) {
106
+ return exp?.op === "filter" &&
107
+ exp.patterns?.length === 2 &&
108
+ samePatternId(typePatternGraph, exp.patterns[0], patternId) &&
109
+ samePatternId(typePatternGraph, exp.patterns[1], patternId);
110
+ }
111
+
112
+ function stripRelationBoundaryFilters(exp, typePatternGraph = null) {
113
+ if (!exp) return exp;
114
+ if (typePatternGraph == null) {
115
+ return exp;
116
+ }
117
+ const [inputPattern, outputPattern] = exp.patterns || [];
118
+ if (inputPattern == null || outputPattern == null) return exp;
119
+
120
+ if (isBoundaryFilter(exp, typePatternGraph, inputPattern) && samePatternId(typePatternGraph, inputPattern, outputPattern)) {
121
+ return {
122
+ op: "identity",
123
+ patterns: [...exp.patterns],
124
+ ...(exp.start ? { start: exp.start } : {}),
125
+ ...(exp.end ? { end: exp.end } : {})
126
+ };
127
+ }
128
+
129
+ if (exp.op !== "comp") return exp;
130
+ let parts = exp.comp || [];
131
+ if (parts.length > 0 && isBoundaryFilter(parts[0], typePatternGraph, inputPattern)) {
132
+ parts = parts.slice(1);
133
+ }
134
+ if (parts.length > 0 && isBoundaryFilter(parts[parts.length - 1], typePatternGraph, outputPattern)) {
135
+ parts = parts.slice(0, -1);
136
+ }
137
+ if (parts.length === (exp.comp || []).length) return exp;
138
+ if (parts.length === 0) {
139
+ return {
140
+ op: "identity",
141
+ patterns: [...exp.patterns],
142
+ ...(exp.start ? { start: exp.start } : {}),
143
+ ...(exp.end ? { end: exp.end } : {})
144
+ };
145
+ }
146
+ if (parts.length === 1) return { ...parts[0], patterns: [...exp.patterns] };
147
+ return { ...exp, comp: parts };
148
+ }
149
+
150
+ function stripDebugFieldsFromFilter(filter) {
151
+ if (!filter) return filter;
152
+ const { start, end, fields, ...rest } = filter;
153
+ if (!fields) return rest;
56
154
  return {
57
- ...serialized,
58
- rels: Object.fromEntries(
59
- Object.entries(serialized.rels).map(([name, rel]) => [name, hydrateRelation(rel)])
155
+ ...rest,
156
+ fields: Object.fromEntries(
157
+ Object.entries(fields).map(([label, child]) => [label, stripDebugFieldsFromFilter(child)])
60
158
  )
61
159
  };
62
160
  }
63
161
 
162
+ function stripDebugFieldsFromExp(exp) {
163
+ if (!exp) return exp;
164
+ const { start, end, ...rest } = exp;
165
+ switch (rest.op) {
166
+ case "filter":
167
+ return { ...rest, filter: stripDebugFieldsFromFilter(rest.filter) };
168
+ case "comp":
169
+ return { ...rest, comp: rest.comp.map(stripDebugFieldsFromExp) };
170
+ case "union":
171
+ return { ...rest, union: rest.union.map(stripDebugFieldsFromExp) };
172
+ case "product":
173
+ return {
174
+ ...rest,
175
+ product: rest.product.map(({ label, exp: child }) => ({
176
+ label,
177
+ exp: stripDebugFieldsFromExp(child)
178
+ }))
179
+ };
180
+ default:
181
+ return rest;
182
+ }
183
+ }
184
+
185
+ function stripDebugFieldsFromCode(code) {
186
+ if (!code) return code;
187
+ const { start, end, ...rest } = code;
188
+ return rest;
189
+ }
190
+
191
+ function stripDebugFieldsFromCodes(codeTable) {
192
+ return Object.fromEntries(
193
+ Object.entries(codeTable || {}).map(([hash, code]) => [hash, stripDebugFieldsFromCode(code)])
194
+ );
195
+ }
196
+
197
+ function firstFilterSourceRange(filter) {
198
+ if (!filter) return null;
199
+ if (filter.start || filter.end) {
200
+ return {
201
+ ...(filter.start ? { start: filter.start } : {}),
202
+ ...(filter.end ? { end: filter.end } : {})
203
+ };
204
+ }
205
+ for (const child of Object.values(filter.fields || {})) {
206
+ const range = firstFilterSourceRange(child);
207
+ if (range) return range;
208
+ }
209
+ return null;
210
+ }
211
+
212
+ function firstExpSourceRange(exp) {
213
+ if (!exp) return null;
214
+ if (exp.start || exp.end) {
215
+ return {
216
+ ...(exp.start ? { start: exp.start } : {}),
217
+ ...(exp.end ? { end: exp.end } : {})
218
+ };
219
+ }
220
+ switch (exp.op) {
221
+ case "filter":
222
+ return firstFilterSourceRange(exp.filter);
223
+ case "comp":
224
+ for (const child of exp.comp) {
225
+ const range = firstExpSourceRange(child);
226
+ if (range) return range;
227
+ }
228
+ return null;
229
+ case "union":
230
+ for (const child of exp.union) {
231
+ const range = firstExpSourceRange(child);
232
+ if (range) return range;
233
+ }
234
+ return null;
235
+ case "product":
236
+ for (const { exp: child } of exp.product) {
237
+ const range = firstExpSourceRange(child);
238
+ if (range) return range;
239
+ }
240
+ return null;
241
+ default:
242
+ return null;
243
+ }
244
+ }
245
+
246
+ function originFromSourceNode(source, name, compiledAt, node) {
247
+ return {
248
+ source,
249
+ name,
250
+ compiledAt,
251
+ ...(node?.start ? { start: node.start } : {}),
252
+ ...(node?.end ? { end: node.end } : {})
253
+ };
254
+ }
255
+
256
+ function relationOrigin(source, name, compiledAt, rel) {
257
+ const printableDef = stripRelationBoundaryFilters(rel.def, rel.typePatternGraph);
258
+ const range = firstExpSourceRange(printableDef);
259
+ return {
260
+ source,
261
+ name,
262
+ compiledAt,
263
+ ...(range?.start ? { start: range.start } : {}),
264
+ ...(range?.end ? { end: range.end } : {})
265
+ };
266
+ }
267
+
268
+ function buildRelTypeDerivation(rel, sccStats) {
269
+ const existing = rel?.typeDerivation || {};
270
+ const status = sccStats
271
+ ? (sccStats.converged ? "converged" : "not-converged")
272
+ : validTypeDerivationStatus(existing.status);
273
+ return { status };
274
+ }
275
+
276
+ function buildRelTypeDerivations(rels, compileStats) {
277
+ const statsByName = compileStatsByRelName(compileStats);
278
+ return Object.fromEntries(
279
+ Object.entries(rels || {}).map(([name, rel]) => [
280
+ name,
281
+ buildRelTypeDerivation(rel, statsByName[name])
282
+ ])
283
+ );
284
+ }
285
+
286
+ function mergeMetaEntries(...metas) {
287
+ const merged = {};
288
+ for (const meta of metas) {
289
+ for (const [hash, entry] of Object.entries(meta || {})) {
290
+ const type = entry?.type;
291
+ if (type !== "code" && type !== "rel") continue;
292
+ for (const origin of entry.origins || []) {
293
+ if (!origin?.name) continue;
294
+ addOrigin(merged, hash, type, { ...origin });
295
+ }
296
+ }
297
+ }
298
+ return merged;
299
+ }
300
+
64
301
  function buildMeta(annotated, source) {
65
302
  const meta = {};
303
+ const representatives = annotated.representatives || {};
66
304
  const relAlias = annotated.relAlias || {};
67
305
  const now = new Date().toISOString();
68
- // Relation metadata: source name → canonical hash
306
+
307
+ for (const [name, hash] of Object.entries(representatives)) {
308
+ if (!isSourceName(name)) continue;
309
+ addOrigin(meta, hash, "code", originFromSourceNode(
310
+ source,
311
+ name,
312
+ now,
313
+ annotated.sourceDefs?.codes?.[name]
314
+ ));
315
+ }
316
+
69
317
  for (const [name, hash] of Object.entries(relAlias)) {
70
- if (name === "__main__") continue;
71
- if (!meta[hash]) meta[hash] = { origins: [] };
72
- meta[hash].origins.push({ source, name, compiledAt: now });
318
+ const rel = annotated.rels?.[name];
319
+ if (!rel) continue;
320
+ if (name === "__main__" || isSourceName(name)) {
321
+ addOrigin(meta, hash, "rel", relationOrigin(source, name, now, rel));
322
+ }
73
323
  }
74
324
  return meta;
75
325
  }
76
326
 
327
+ function importedLibraryMeta(libraries = []) {
328
+ return mergeMetaEntries(...libraries.map((library) => library.meta || {}));
329
+ }
330
+
331
+ function isEofParseError(error) {
332
+ return error?.hash?.token === "EOF" || /got 'EOF'|Expecting/.test(error?.message || "");
333
+ }
334
+
335
+ function annotateLibrary(script, options = {}) {
336
+ try {
337
+ return annotate(script, options);
338
+ } catch (error) {
339
+ if (!isEofParseError(error)) throw error;
340
+ return annotate(`${script}\n()`, options);
341
+ }
342
+ }
343
+
77
344
  function collectReachable(mainName, rels, allCodes) {
78
345
  const reachableRels = new Set();
79
346
  const reachableCodes = new Set();
@@ -129,7 +396,7 @@ function collectReachable(mainName, rels, allCodes) {
129
396
  const rel = rels[relName];
130
397
  if (rel.typePatternGraph) {
131
398
  const codeId = rel.typePatternGraph.codeId || (rel.typePatternGraph.codeId);
132
- if (codeId) Object.values(codeId).forEach(walkCode);
399
+ if (codeId) Object.keys(codeId).forEach(walkCode);
133
400
  }
134
401
  }
135
402
 
@@ -138,9 +405,12 @@ function collectReachable(mainName, rels, allCodes) {
138
405
 
139
406
  function compileObject(script, options = {}) {
140
407
  const annotated = annotate(script, options);
141
- const allCodes = codes.dump();
408
+ const allCodes = stripDebugFieldsFromCodes(codes.dump());
142
409
  const serialized = serializeAnnotated(annotated);
143
- const meta = buildMeta(annotated, options.source || null);
410
+ const meta = mergeMetaEntries(
411
+ importedLibraryMeta(options.libraries),
412
+ buildMeta(annotated, options.source || null)
413
+ );
144
414
 
145
415
  // Prune to reachable from main
146
416
  const { reachableRels, reachableCodes } = collectReachable("__main__", serialized.rels, allCodes);
@@ -150,13 +420,15 @@ function compileObject(script, options = {}) {
150
420
  const prunedRels = Object.fromEntries(
151
421
  Object.entries(serialized.rels).filter(([name]) => reachableRels.has(name))
152
422
  );
423
+ const reachableRelHashes = new Set(
424
+ [...reachableRels].map((name) => serialized.relAlias?.[name] || name)
425
+ );
153
426
  const prunedMeta = Object.fromEntries(
154
- Object.entries(meta).filter(([h]) => reachableRels.has(h) || reachableCodes.has(h))
427
+ Object.entries(meta).filter(([h]) => reachableRels.has(h) || reachableRelHashes.has(h) || reachableCodes.has(h))
155
428
  );
156
429
 
157
430
  return {
158
431
  format: "k-object",
159
- version: 2,
160
432
  codes: prunedCodes,
161
433
  rels: prunedRels,
162
434
  relAlias: serialized.relAlias,
@@ -189,9 +461,13 @@ function rewriteRefsToCanonical(exp, relAlias) {
189
461
  }
190
462
 
191
463
  function compileLibrary(script, options = {}) {
192
- const annotated = annotate(script, options);
464
+ const annotated = annotateLibrary(script, options);
465
+ const allCodes = stripDebugFieldsFromCodes(codes.dump());
193
466
  const serialized = serializeAnnotated(annotated);
194
- const meta = buildMeta(annotated, options.source || null);
467
+ const meta = mergeMetaEntries(
468
+ importedLibraryMeta(options.libraries),
469
+ buildMeta(annotated, options.source || null)
470
+ );
195
471
  const relAlias = serialized.relAlias || {};
196
472
 
197
473
  // Key rels by canonical hash, rewrite internal refs to canonical hashes
@@ -205,15 +481,21 @@ function compileLibrary(script, options = {}) {
205
481
  def: rewriteRefsToCanonical(rel.def, relAlias)
206
482
  };
207
483
  }
484
+ const libraryMetaHashes = new Set([
485
+ ...Object.keys(allCodes),
486
+ ...Object.keys(libRels)
487
+ ]);
488
+ const libMeta = Object.fromEntries(
489
+ Object.entries(meta).filter(([hash]) => libraryMetaHashes.has(hash))
490
+ );
208
491
 
209
492
  return {
210
493
  format: "k-object",
211
- version: 2,
212
- codes: codes.dump(),
494
+ codes: allCodes,
213
495
  rels: libRels,
214
496
  relAlias: serialized.relAlias,
215
497
  compileStats: serialized.compileStats,
216
- meta,
498
+ meta: libMeta,
217
499
  main: null
218
500
  };
219
501
  }
@@ -222,15 +504,6 @@ function hydrateObject(object) {
222
504
  if (object?.format !== "k-object") {
223
505
  throw new Error("Unsupported k object file");
224
506
  }
225
- // v1 compat: defs wrapper
226
- if (object.version === 1) {
227
- codes.load(object.codes);
228
- return {
229
- ...object,
230
- defs: hydrateAnnotated(object.defs)
231
- };
232
- }
233
- // v2: flat rels
234
507
  codes.load(object.codes);
235
508
  const hydratedRels = Object.fromEntries(
236
509
  Object.entries(object.rels).map(([name, rel]) => [name, hydrateRelation(rel)])
@@ -247,25 +520,23 @@ function loadLibrary(object) {
247
520
  }
248
521
 
249
522
  function isHydratedObject(object) {
250
- // v2 format
251
523
  if (object?.rels) {
252
524
  const firstRel = Object.values(object.rels)[0];
253
525
  return firstRel?.typePatternGraph instanceof TypePatternGraph;
254
526
  }
255
- // v1 format
256
- return object?.defs?.rels?.[object.main]?.typePatternGraph instanceof TypePatternGraph;
527
+ return false;
257
528
  }
258
529
 
259
530
  function encodeObject(object) {
260
- const magic = object.main == null ? LIB_MAGIC : MAGIC;
531
+ if (object.main == null) return encodeLibrary(object);
261
532
  const payload = Buffer.from(JSON.stringify(object), "utf8");
262
533
  if (payload.length > 0xffffffff) {
263
534
  throw new Error("k object payload is too large");
264
535
  }
265
536
 
266
- const header = Buffer.alloc(magic.length + 4);
267
- magic.copy(header, 0);
268
- header.writeUInt32BE(payload.length, magic.length);
537
+ const header = Buffer.alloc(OBJECT_MAGIC.length + 4);
538
+ OBJECT_MAGIC.copy(header, 0);
539
+ header.writeUInt32BE(payload.length, OBJECT_MAGIC.length);
269
540
  return Buffer.concat([header, payload]);
270
541
  }
271
542
 
@@ -273,15 +544,16 @@ function decodeObject(buffer) {
273
544
  if (!Buffer.isBuffer(buffer)) {
274
545
  throw new Error("k object input must be a Buffer");
275
546
  }
276
- const isObj = buffer.length >= MAGIC.length && buffer.subarray(0, MAGIC.length).equals(MAGIC);
277
- const isLib = buffer.length >= LIB_MAGIC.length && buffer.subarray(0, LIB_MAGIC.length).equals(LIB_MAGIC);
278
- if (!isObj && !isLib) {
547
+ const trimmed = buffer.toString("utf8").trimStart();
548
+ if (trimmed.startsWith("{")) {
549
+ return hydrateObject(JSON.parse(trimmed));
550
+ }
551
+ if (buffer.length < OBJECT_MAGIC.length || !buffer.subarray(0, OBJECT_MAGIC.length).equals(OBJECT_MAGIC)) {
279
552
  throw new Error("Invalid k object file header");
280
553
  }
281
554
 
282
- const magic = isLib ? LIB_MAGIC : MAGIC;
283
- const length = buffer.readUInt32BE(magic.length);
284
- const payloadStart = magic.length + 4;
555
+ const length = buffer.readUInt32BE(OBJECT_MAGIC.length);
556
+ const payloadStart = OBJECT_MAGIC.length + 4;
285
557
  const payloadEnd = payloadStart + length;
286
558
  if (payloadEnd !== buffer.length) {
287
559
  throw new Error("Invalid k object file length");
@@ -291,7 +563,7 @@ function decodeObject(buffer) {
291
563
  }
292
564
 
293
565
  function encodeLibrary(object) {
294
- return encodeObject(object);
566
+ return Buffer.from(JSON.stringify(object), "utf8");
295
567
  }
296
568
 
297
569
  function decodeLibrary(buffer) {
@@ -307,20 +579,19 @@ function compileObjectBuffer(script, options = {}) {
307
579
  }
308
580
 
309
581
  function compileLibraryBuffer(script, options = {}) {
310
- return encodeObject(compileLibrary(script, options));
582
+ return encodeLibrary(compileLibrary(script, options));
311
583
  }
312
584
 
313
585
  function getRels(hydrated) {
314
- // v2: flat rels; v1: defs.rels
315
- return hydrated.rels || hydrated.defs?.rels;
586
+ return hydrated.rels;
316
587
  }
317
588
 
318
589
  function runObject(object, value) {
319
590
  const hydrated = isHydratedObject(object) ? object : hydrateObject(object);
320
591
  const rels = getRels(hydrated);
321
- run.defs = hydrated.defs || { rels };
592
+ run.defs = { rels };
322
593
  const mainRel = rels[hydrated.main];
323
- return run(codes.find, mainRel.def, value, mainRel.typePatternGraph);
594
+ return run_rel(codes.find, mainRel, value, hydrated.main);
324
595
  }
325
596
 
326
597
  function objectToFunction(object) {
@@ -328,6 +599,29 @@ function objectToFunction(object) {
328
599
  return (value) => runObject(hydrated, value);
329
600
  }
330
601
 
602
+ function assertRelConverged(rel, name) {
603
+ const status = rel?.typeDerivation?.status || "unknown";
604
+ if (status !== "converged") {
605
+ throw new Error(`Cannot run '${name}' without envelopes: type derivation is ${status}`);
606
+ }
607
+ }
608
+
609
+ function runConvergedObject(object, value) {
610
+ const hydrated = isHydratedObject(object) ? object : hydrateObject(object);
611
+ const rels = getRels(hydrated);
612
+ const mainRel = rels[hydrated.main];
613
+ assertRelConverged(mainRel, hydrated.main);
614
+ run_converged.defs = { rels };
615
+ return run_converged(codes.find, mainRel.def, value, mainRel.typePatternGraph, {
616
+ requireConverged: true
617
+ });
618
+ }
619
+
620
+ function objectToConvergedFunction(object) {
621
+ const hydrated = isHydratedObject(object) ? object : hydrateObject(object);
622
+ return (value) => runConvergedObject(hydrated, value);
623
+ }
624
+
331
625
  function hashAlias(hash) {
332
626
  return hash.startsWith("@") ? hash.slice(1) : hash;
333
627
  }
@@ -413,10 +707,23 @@ function boundaryFilterSource(typePatternGraph, patternId, aliases) {
413
707
  return prettyRel({ op: "filter", filter: rewriteCodesInFilter(filter, aliases) });
414
708
  }
415
709
 
710
+ function prettyRelation(rel, aliases = {}, relAliases = {}) {
711
+ const body = rewriteRefsInExp(
712
+ stripRelationBoundaryFilters(stripDebugFieldsFromExp(rel.def), rel.typePatternGraph),
713
+ aliases,
714
+ relAliases
715
+ );
716
+ return [
717
+ boundaryFilterSource(rel.typePatternGraph, rel.def.patterns[0], aliases),
718
+ prettyRel(body),
719
+ boundaryFilterSource(rel.typePatternGraph, rel.def.patterns[1], aliases)
720
+ ].join(" ");
721
+ }
722
+
416
723
  function decompileObject(object) {
417
724
  const hydrated = isHydratedObject(object) ? object : hydrateObject(object);
418
725
  const rels = getRels(hydrated);
419
- const relAliases = hydrated.defs?.relAlias || hydrated.relAlias || {};
726
+ const relAliases = hydrated.relAlias || {};
420
727
  const aliases = shortestUniqueAliases([
421
728
  ...Object.keys(hydrated.codes),
422
729
  ...Object.values(relAliases)
@@ -432,9 +739,9 @@ function decompileObject(object) {
432
739
  const relName = relAliases[name] || name;
433
740
  if (emittedRelAliases.has(relName)) return null;
434
741
  emittedRelAliases.add(relName);
435
- return `${aliases[relName] || relName} = ${prettyRel(rewriteRefsInExp(rel.def, aliases, relAliases))};`;
742
+ return `${aliases[relName] || relName} = ${prettyRelation(rel, aliases, relAliases)};`;
436
743
  };
437
- const compileStats = hydrated.defs?.compileStats || hydrated.compileStats;
744
+ const compileStats = hydrated.compileStats;
438
745
  const sccs = compileStats?.sccs || [];
439
746
  const relGroups = sccs.map(({ members }) =>
440
747
  members.map(relDefForName).filter((line) => line != null)
@@ -482,6 +789,84 @@ function decompileObjectBuffer(buffer) {
482
789
  return decompileObject(decodeObject(buffer));
483
790
  }
484
791
 
792
+ function hashBody(hash) {
793
+ return hash.startsWith("@") ? hash.slice(1) : hash;
794
+ }
795
+
796
+ function compareAliasEntries(left, right) {
797
+ return (
798
+ left.typeOrder - right.typeOrder ||
799
+ left.name.localeCompare(right.name) ||
800
+ compareMaybeTimestamp(left.compiledAt, right.compiledAt) ||
801
+ left.hash.localeCompare(right.hash) ||
802
+ left.source.localeCompare(right.source)
803
+ );
804
+ }
805
+
806
+ function compareMaybeTimestamp(left, right) {
807
+ if (left == null && right == null) return 0;
808
+ if (left == null) return 1;
809
+ if (right == null) return -1;
810
+ return left.localeCompare(right);
811
+ }
812
+
813
+ function originComment(origin) {
814
+ return JSON.stringify({
815
+ ...(origin.source == null ? {} : { source: origin.source }),
816
+ ...(origin.compiledAt == null ? {} : { compiledAt: origin.compiledAt })
817
+ });
818
+ }
819
+
820
+ function extractAliasesFromObject(object) {
821
+ const hydrated = isHydratedObject(object) ? object : hydrateObject(object);
822
+ const entries = [];
823
+
824
+ for (const [hash, entry] of Object.entries(hydrated.meta || {})) {
825
+ const type = entry?.type;
826
+ if (type !== "code" && type !== "rel") continue;
827
+ const typeOrder = type === "code" ? 0 : 1;
828
+ for (const origin of entry.origins || []) {
829
+ if (origin?.name === "__main__") continue;
830
+ if (!origin?.name || !isSourceName(origin.name)) continue;
831
+ entries.push({
832
+ type,
833
+ typeOrder,
834
+ hash,
835
+ name: origin.name,
836
+ source: origin.source || "",
837
+ compiledAt: origin.compiledAt,
838
+ comment: originComment(origin)
839
+ });
840
+ }
841
+ }
842
+
843
+ entries.sort(compareAliasEntries);
844
+
845
+ const activeEntries = new Set();
846
+ for (let i = 0; i < entries.length; i++) {
847
+ const next = entries[i + 1];
848
+ if (!next || next.type !== entries[i].type || next.name !== entries[i].name) {
849
+ activeEntries.add(entries[i]);
850
+ }
851
+ }
852
+
853
+ const lines = [];
854
+ let lastType = null;
855
+ for (const entry of entries) {
856
+ if (lastType != null && lastType !== entry.type) lines.push("");
857
+ lastType = entry.type;
858
+ const prefix = entry.type === "code" ? "$ " : "";
859
+ const line = `${prefix}${entry.name} = @${hashBody(entry.hash)}; # ${entry.comment}`;
860
+ lines.push(activeEntries.has(entry) ? line : `# ${line}`);
861
+ }
862
+
863
+ return `${lines.join("\n")}${lines.length === 0 ? "" : "\n"}`;
864
+ }
865
+
866
+ function extractAliasesFromObjectBuffer(buffer) {
867
+ return extractAliasesFromObject(decodeObject(buffer));
868
+ }
869
+
485
870
  export {
486
871
  compileObject,
487
872
  compileObjectBuffer,
@@ -494,9 +879,14 @@ export {
494
879
  hydrateObject,
495
880
  loadLibrary,
496
881
  runObject,
882
+ runConvergedObject,
497
883
  objectToFunction,
884
+ objectToConvergedFunction,
885
+ prettyRelation,
498
886
  decompileObject,
499
- decompileObjectBuffer
887
+ decompileObjectBuffer,
888
+ extractAliasesFromObject,
889
+ extractAliasesFromObjectBuffer
500
890
  };
501
891
 
502
892
  export default {
@@ -511,7 +901,12 @@ export default {
511
901
  hydrateObject,
512
902
  loadLibrary,
513
903
  runObject,
904
+ runConvergedObject,
514
905
  objectToFunction,
906
+ objectToConvergedFunction,
907
+ prettyRelation,
515
908
  decompileObject,
516
- decompileObjectBuffer
909
+ decompileObjectBuffer,
910
+ extractAliasesFromObject,
911
+ extractAliasesFromObjectBuffer
517
912
  };