@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fraczak/k",
3
- "version": "6.0.2",
3
+ "version": "6.0.4",
4
4
  "description": "k — a language for composing first-order partial functions over algebraic data types (products and tagged unions); includes runtime, type derivation, REPL, and codec toolchain",
5
5
  "main": "index.mjs",
6
6
  "scripts": {
@@ -18,6 +18,7 @@
18
18
  "k": "./k.mjs",
19
19
  "k-compile-object": "./objects/compile.mjs",
20
20
  "k-decompile-object": "./objects/decompile.mjs",
21
+ "k-extract-aliases": "./objects/extract-aliases.mjs",
21
22
  "k-parse": "./codecs/k-parse.mjs",
22
23
  "k-pattern": "./patterns/from-k.mjs",
23
24
  "k-print": "./codecs/k-print.mjs",
package/repl2.mjs CHANGED
@@ -19,7 +19,8 @@ import {
19
19
  encodeObject,
20
20
  encodeLibrary,
21
21
  hydrateObject,
22
- loadLibrary
22
+ loadLibrary,
23
+ prettyRelation
23
24
  } from "./object.mjs";
24
25
  import { propertyListToFilter, valueToK, valueWithEnvelopeToK } from "./codecs/runtime/show-value.mjs";
25
26
 
@@ -58,7 +59,6 @@ function createState() {
58
59
  function stateLibrary(state) {
59
60
  return {
60
61
  format: "k-object",
61
- version: 2,
62
62
  codes: state.codes,
63
63
  rels: state.rels,
64
64
  relAlias: state.relAliases,
@@ -146,21 +146,23 @@ function annotateWithOptionalIdentity(source, options) {
146
146
  function mergeMeta(state, meta = {}) {
147
147
  for (const [hash, entry] of Object.entries(meta)) {
148
148
  const origins = entry?.origins || [];
149
- if (!state.meta[hash]) state.meta[hash] = { origins: [] };
150
- state.meta[hash].origins.push(...origins);
149
+ const entryType = entry?.type;
150
+ if (!state.meta[hash]) state.meta[hash] = { type: entryType, origins: [] };
151
+ if (state.meta[hash].type == null && entryType != null) state.meta[hash].type = entryType;
152
+ state.meta[hash].origins.push(...origins.map((origin) => ({ ...origin })));
151
153
  }
152
154
  }
153
155
 
154
- function rememberOrigin(state, hash, name, kind, source = "<repl>") {
155
- if (!state.meta[hash]) state.meta[hash] = { origins: [] };
156
+ function rememberOrigin(state, hash, name, type, source = "<repl>") {
157
+ if (!state.meta[hash]) state.meta[hash] = { type, origins: [] };
158
+ if (state.meta[hash].type == null) state.meta[hash].type = type;
156
159
  const exists = state.meta[hash].origins.some((origin) =>
157
- origin.name === name && origin.kind === kind && origin.source === source
160
+ origin.name === name && state.meta[hash].type === type && origin.source === source
158
161
  );
159
162
  if (!exists) {
160
163
  state.meta[hash].origins.push({
161
164
  source,
162
165
  name,
163
- kind,
164
166
  compiledAt: new Date().toISOString()
165
167
  });
166
168
  }
@@ -170,44 +172,50 @@ function recoverAliasesFromMeta(state, lib) {
170
172
  for (const [hash, entry] of Object.entries(lib.meta || {})) {
171
173
  for (const origin of entry?.origins || []) {
172
174
  if (!origin?.name || !NAME_RE.test(origin.name)) continue;
173
- if (origin.kind === "code" || (!origin.kind && hash in state.codes && !(hash in state.rels))) {
175
+ const type = entry?.type;
176
+ if (type === "code" || (!type && hash in state.codes && !(hash in state.rels))) {
174
177
  state.typeAliases[origin.name] = hash;
175
- } else if (origin.kind === "rel" || hash in state.rels) {
178
+ } else if (type === "rel" || hash in state.rels) {
176
179
  state.relAliases[origin.name] = hash;
177
180
  }
178
181
  }
179
182
  }
180
183
  }
181
184
 
182
- function mergeLibrary(state, lib, source = "<load>") {
185
+ function mergeLibrary(state, lib, source = "<load>", options = {}) {
186
+ const loadAliases = options.loadAliases ?? true;
183
187
  state.codes = { ...state.codes, ...(lib.codes || {}) };
184
188
  state.rels = { ...state.rels, ...(lib.rels || {}) };
185
- state.relAliases = { ...state.relAliases, ...(lib.relAlias || {}) };
186
189
  mergeMeta(state, lib.meta);
187
190
 
188
- for (const [name, hash] of Object.entries(lib.relAlias || {})) {
189
- if (name !== "__main__" && NAME_RE.test(name) && hash in state.rels) {
190
- state.relAliases[name] = hash;
191
- rememberOrigin(state, hash, name, "rel", source);
191
+ if (loadAliases) {
192
+ state.relAliases = { ...state.relAliases, ...(lib.relAlias || {}) };
193
+ for (const [name, hash] of Object.entries(lib.relAlias || {})) {
194
+ if (name !== "__main__" && NAME_RE.test(name) && hash in state.rels) {
195
+ state.relAliases[name] = hash;
196
+ rememberOrigin(state, hash, name, "rel", source);
197
+ }
192
198
  }
199
+ recoverAliasesFromMeta(state, lib);
193
200
  }
194
- recoverAliasesFromMeta(state, lib);
195
201
  restoreCodes(state);
196
202
  }
197
203
 
198
204
  function savedLibrary(state) {
199
205
  const meta = cloneJSON(state.meta);
206
+ const now = new Date().toISOString();
200
207
  for (const [name, hash] of Object.entries(state.typeAliases)) {
201
- if (!meta[hash]) meta[hash] = { origins: [] };
202
- meta[hash].origins.push({ source: "<repl>", name, kind: "code" });
208
+ if (!meta[hash]) meta[hash] = { type: "code", origins: [] };
209
+ if (meta[hash].type == null) meta[hash].type = "code";
210
+ meta[hash].origins.push({ source: "<repl>", name, compiledAt: now });
203
211
  }
204
212
  for (const [name, hash] of Object.entries(state.relAliases)) {
205
- if (!meta[hash]) meta[hash] = { origins: [] };
206
- meta[hash].origins.push({ source: "<repl>", name, kind: "rel" });
213
+ if (!meta[hash]) meta[hash] = { type: "rel", origins: [] };
214
+ if (meta[hash].type == null) meta[hash].type = "rel";
215
+ meta[hash].origins.push({ source: "<repl>", name, compiledAt: now });
207
216
  }
208
217
  return {
209
218
  format: "k-object",
210
- version: 2,
211
219
  codes: state.codes,
212
220
  rels: state.rels,
213
221
  relAlias: state.relAliases,
@@ -276,6 +284,16 @@ function userDefinedNames(source) {
276
284
  };
277
285
  }
278
286
 
287
+ function originFromSourceNode(source, name, compiledAt, node) {
288
+ return {
289
+ source,
290
+ name,
291
+ compiledAt,
292
+ ...(node?.start ? { start: node.start } : {}),
293
+ ...(node?.end ? { end: node.end } : {})
294
+ };
295
+ }
296
+
279
297
  function libraryOriginsFromSource(source, fullSource, lib, options) {
280
298
  const { typeNames, relNames } = userDefinedNames(source);
281
299
  const annotated = annotateWithOptionalIdentity(fullSource, options);
@@ -285,8 +303,13 @@ function libraryOriginsFromSource(source, fullSource, lib, options) {
285
303
  for (const name of typeNames) {
286
304
  const hash = annotated.representatives?.[name];
287
305
  if (!hash) continue;
288
- if (!meta[hash]) meta[hash] = { origins: [] };
289
- meta[hash].origins.push({ source: options.source || null, name, kind: "code", compiledAt: now });
306
+ if (!meta[hash]) meta[hash] = { type: "code", origins: [] };
307
+ meta[hash].origins.push(originFromSourceNode(
308
+ options.source || null,
309
+ name,
310
+ now,
311
+ annotated.sourceDefs?.codes?.[name]
312
+ ));
290
313
  }
291
314
 
292
315
  const relAlias = Object.fromEntries(
@@ -296,8 +319,13 @@ function libraryOriginsFromSource(source, fullSource, lib, options) {
296
319
  );
297
320
 
298
321
  for (const [name, hash] of Object.entries(relAlias)) {
299
- if (!meta[hash]) meta[hash] = { origins: [] };
300
- meta[hash].origins.push({ source: options.source || null, name, kind: "rel", compiledAt: now });
322
+ if (!meta[hash]) meta[hash] = { type: "rel", origins: [] };
323
+ meta[hash].origins.push(originFromSourceNode(
324
+ options.source || null,
325
+ name,
326
+ now,
327
+ annotated.rels?.[name]?.def
328
+ ));
301
329
  }
302
330
 
303
331
  return { relAlias, meta };
@@ -401,6 +429,26 @@ function commandArgIndex(arg) {
401
429
  return trimmed.split(/\s+/).length - 1;
402
430
  }
403
431
 
432
+ function loadPathCompletionStart(argStart, arg) {
433
+ const leadingWhitespace = arg.length - arg.trimStart().length;
434
+ const trimmed = arg.trimStart();
435
+ if (trimmed === "" || !trimmed.startsWith("--no-alias")) {
436
+ if (commandArgIndex(arg) !== 0) return null;
437
+ return argStart + completionTokenStart(arg);
438
+ }
439
+ if (!"--no-alias".startsWith(trimmed) && !trimmed.startsWith("--no-alias ")) {
440
+ return null;
441
+ }
442
+ if (trimmed !== "--no-alias" && !trimmed.startsWith("--no-alias ")) {
443
+ return null;
444
+ }
445
+ if (trimmed === "--no-alias") return null;
446
+ const restStart = leadingWhitespace + "--no-alias".length;
447
+ const rest = arg.slice(restStart);
448
+ const restLeadingWhitespace = rest.length - rest.trimStart().length;
449
+ return argStart + restStart + restLeadingWhitespace + completionTokenStart(rest.trimStart());
450
+ }
451
+
404
452
  function completeCommandArgument(line, state) {
405
453
  const body = line.slice(1);
406
454
  const firstSpace = body.search(/\s/);
@@ -410,8 +458,13 @@ function completeCommandArgument(line, state) {
410
458
  const argStart = 1 + firstSpace + 1;
411
459
  const arg = line.slice(argStart);
412
460
 
413
- if (PATH_COMMANDS.has(command) && commandArgIndex(arg) === 0) {
414
- const tokenStart = argStart + completionTokenStart(arg);
461
+ if (PATH_COMMANDS.has(command)) {
462
+ const tokenStart = command === "load"
463
+ ? loadPathCompletionStart(argStart, arg)
464
+ : commandArgIndex(arg) === 0
465
+ ? argStart + completionTokenStart(arg)
466
+ : null;
467
+ if (tokenStart == null) return [[], line];
415
468
  return completePath(line, tokenStart);
416
469
  }
417
470
 
@@ -460,6 +513,20 @@ function isEofParseError(error) {
460
513
  return /got 'EOF'|Unexpected end of input/.test(error.message || "");
461
514
  }
462
515
 
516
+ function parseLoadArgs(arg, usagePrefix = ":") {
517
+ const trimmed = arg.trim();
518
+ if (!trimmed) throw new Error(`${usagePrefix}load requires a file path`);
519
+ if (trimmed === "--no-alias") {
520
+ throw new Error(`${usagePrefix}load --no-alias requires a file path`);
521
+ }
522
+ if (trimmed.startsWith("--no-alias ")) {
523
+ const path = trimmed.slice("--no-alias".length).trim();
524
+ if (!path) throw new Error(`${usagePrefix}load --no-alias requires a file path`);
525
+ return { path, loadAliases: false };
526
+ }
527
+ return { path: arg.trim(), loadAliases: true };
528
+ }
529
+
463
530
  function lineForContinuation(line) {
464
531
  return line.replace(/\\\s*$/, "");
465
532
  }
@@ -703,28 +770,28 @@ async function evaluateCommand(line, state) {
703
770
  return [`saved ${path} (${main})`];
704
771
  }
705
772
  case "load": {
706
- if (!arg) throw new Error(`${usagePrefix}load requires a file path`);
707
- if (arg.endsWith(".klib")) {
708
- const lib = loadLibrary(decodeLibrary(fs.readFileSync(arg)));
709
- mergeLibrary(state, lib, arg);
773
+ const { path: loadPath, loadAliases } = parseLoadArgs(arg, usagePrefix);
774
+ if (loadPath.endsWith(".klib")) {
775
+ const lib = loadLibrary(decodeLibrary(fs.readFileSync(loadPath)));
776
+ mergeLibrary(state, lib, loadPath, { loadAliases });
710
777
  } else {
711
778
  restoreCodes(state);
712
- const source = fs.readFileSync(arg, "utf8");
779
+ const source = fs.readFileSync(loadPath, "utf8");
713
780
  const preamble = aliasPreamble(state);
714
781
  const fullSource = [preamble, source].filter(Boolean).join("\n");
715
782
  const options = {
716
- source: arg,
783
+ source: loadPath,
717
784
  libraries: [stateLibrary(state)]
718
785
  };
719
786
  try {
720
787
  const lib = compileWithOptionalIdentity(fullSource, options);
721
788
  const { relAlias, meta } = libraryOriginsFromSource(source, fullSource, lib, options);
722
- mergeLibrary(state, { ...lib, relAlias, meta }, arg);
789
+ mergeLibrary(state, { ...lib, relAlias, meta }, loadPath, { loadAliases });
723
790
  } catch (error) {
724
791
  throw remapError(error, preambleLineCount(preamble));
725
792
  }
726
793
  }
727
- return [`loaded ${arg}`];
794
+ return [`loaded ${loadPath}`];
728
795
  }
729
796
  case "t": {
730
797
  if (!arg) throw new Error(`${usagePrefix}t requires a relation name`);
@@ -736,7 +803,7 @@ async function evaluateCommand(line, state) {
736
803
  if (!arg) throw new Error(`${usagePrefix}d requires a relation name`);
737
804
  const { hash, rel } = resolveRel(state, arg);
738
805
  if (!rel) throw new Error(`Unknown relation '${arg}'`);
739
- return [`${arg} = ${prettyRel(rel.def)}; -- ${hash}`];
806
+ return [`${arg} = ${prettyRelation(rel)}; -- ${hash}`];
740
807
  }
741
808
  case "C": {
742
809
  if (!arg) throw new Error(`${usagePrefix}C requires a type name`);
@@ -759,7 +826,8 @@ function helpText() {
759
826
  ":type name show type definition",
760
827
  ":codes list type aliases",
761
828
  ":rels list relation aliases",
762
- ":load file load .k source or .klib",
829
+ ":load [--no-alias] file",
830
+ " load .k source or .klib",
763
831
  ":klib file export state as a library",
764
832
  ":ko file expr export executable .ko using expr as main",
765
833
  ":val print current value",
package/run.mjs CHANGED
@@ -166,7 +166,7 @@ function compiledExp(findCode, exp, typePatternGraph) {
166
166
  {
167
167
  const defn = run.defs.rels[exp.ref];
168
168
  if (defn != undefined) {
169
- const refFn = compiledExp(findCode, defn.def, defn.typePatternGraph);
169
+ const refFn = compiledRel(findCode, defn, exp.ref);
170
170
  fn = (value) => {
171
171
  return value === undefined ? undefined : refFn(value);
172
172
  };
@@ -266,6 +266,161 @@ function compiledExp(findCode, exp, typePatternGraph) {
266
266
  return compiled.fn;
267
267
  }
268
268
 
269
+ function compiledRel(findCode, relDef, name = "<anonymous>") {
270
+ const cached = relDef._compiledRunRel;
271
+ if (cached?.findCode === findCode && cached?.defs === run.defs) {
272
+ return cached.fn;
273
+ }
274
+
275
+ const compiled = {
276
+ findCode,
277
+ defs: run.defs,
278
+ target: null,
279
+ fn(value) {
280
+ return compiled.target(value);
281
+ }
282
+ };
283
+ relDef._compiledRunRel = compiled;
284
+
285
+ const exp = relDef.def;
286
+ const typePatternGraph = relDef.typePatternGraph || null;
287
+ const inputPattern = staticPattern(typePatternGraph, exp, 0);
288
+ const staticOutputPattern = outputPattern(typePatternGraph, exp);
289
+ const bodyFn = compiledExp(findCode, exp, typePatternGraph);
290
+
291
+ compiled.target = (value) => {
292
+ if (value === undefined) return undefined;
293
+ value = constrainWithPattern(value, inputPattern, exp);
294
+ const result = bodyFn(value);
295
+ if (result === undefined) return undefined;
296
+ try {
297
+ return constrainWithPattern(result, staticOutputPattern, exp);
298
+ } catch (error) {
299
+ error.message = `Type Error in output of '${name}'\n - ${error.message}`;
300
+ throw error;
301
+ }
302
+ };
303
+ return compiled.fn;
304
+ }
305
+
306
+ function assertConvergedRel(rel, name) {
307
+ const status = rel?.typeDerivation?.status || "unknown";
308
+ if (status !== "converged") {
309
+ throw new Error(`Cannot run '${name}' without envelopes: type derivation is ${status}`);
310
+ }
311
+ }
312
+
313
+ function compiledConvergedExp(findCode, exp, typePatternGraph, options = {}) {
314
+ const requireConverged = options.requireConverged === true;
315
+ const cached = exp._compiledConvergedRun;
316
+ if (
317
+ cached?.findCode === findCode &&
318
+ cached?.typePatternGraph === typePatternGraph &&
319
+ cached?.defs === run_converged.defs &&
320
+ cached?.requireConverged === requireConverged
321
+ ) {
322
+ return cached.fn;
323
+ }
324
+
325
+ const compiled = {
326
+ findCode,
327
+ typePatternGraph,
328
+ defs: run_converged.defs,
329
+ requireConverged,
330
+ target: null,
331
+ fn(value) {
332
+ return compiled.target(value);
333
+ }
334
+ };
335
+ exp._compiledConvergedRun = compiled;
336
+
337
+ let fn;
338
+ switch (exp.op) {
339
+ case "code":
340
+ case "filter":
341
+ case "identity":
342
+ fn = (value) => value;
343
+ break;
344
+ case "ref":
345
+ {
346
+ const defn = run_converged.defs?.rels?.[exp.ref];
347
+ if (defn != undefined) {
348
+ if (requireConverged) assertConvergedRel(defn, exp.ref);
349
+ const refFn = compiledConvergedExp(findCode, defn.def, defn.typePatternGraph, options);
350
+ fn = (value) => value === undefined ? undefined : refFn(value);
351
+ } else {
352
+ const builtin_func = builtin[exp.ref];
353
+ if (builtin_func != null) {
354
+ fn = (value) => value === undefined ? undefined : builtin_func(value);
355
+ } else {
356
+ fn = () => {
357
+ throw(`Unknown ref: '${exp.ref}'`);
358
+ };
359
+ }
360
+ }
361
+ }
362
+ break;
363
+ case "dot":
364
+ fn = (value) => {
365
+ if (value === undefined || !(value instanceof Product)) return undefined;
366
+ return Object.hasOwn(value.product, exp.dot) ? value.product[exp.dot] : undefined;
367
+ };
368
+ break;
369
+ case "div":
370
+ fn = (value) => {
371
+ if (value === undefined || !(value instanceof Variant) || value.tag !== exp.div) return undefined;
372
+ return value.value;
373
+ };
374
+ break;
375
+ case "comp": {
376
+ const parts = exp.comp.map((part) => compiledConvergedExp(findCode, part, typePatternGraph, options));
377
+ fn = (value) => {
378
+ for (let i = 0, len = parts.length; i < len; i++) {
379
+ value = parts[i](value);
380
+ if (value === undefined) return undefined;
381
+ }
382
+ return value;
383
+ };
384
+ break;
385
+ }
386
+ case "union": {
387
+ const parts = exp.union.map((part) => compiledConvergedExp(findCode, part, typePatternGraph, options));
388
+ fn = (value) => {
389
+ if (value === undefined) return undefined;
390
+ for (let i = 0, len = parts.length; i < len; i++) {
391
+ const result = parts[i](value);
392
+ if (result !== undefined) return result;
393
+ }
394
+ return undefined;
395
+ };
396
+ break;
397
+ }
398
+ case "product": {
399
+ const labels = exp.product.map(({ label }) => label);
400
+ const fieldFns = exp.product.map(({ exp: e }) => compiledConvergedExp(findCode, e, typePatternGraph, options));
401
+ fn = (value) => {
402
+ if (value === undefined) return undefined;
403
+ const result = {};
404
+ for (let i = 0, len = labels.length; i < len; i++) {
405
+ const r = fieldFns[i](value);
406
+ if (r === undefined) return undefined;
407
+ result[labels[i]] = r;
408
+ }
409
+ return new Product(result);
410
+ };
411
+ break;
412
+ }
413
+ case "vid":
414
+ fn = (value) => value === undefined ? undefined : new Variant(exp.vid, value);
415
+ break;
416
+ default:
417
+ assert(false,`Unknown operation: '${exp.op}'`);
418
+ }
419
+
420
+ compiled.target = fn;
421
+ return compiled.fn;
422
+ }
423
+
269
424
  function verify(findCode, code, value) {
270
425
  "use strict";
271
426
  if (code == null) return false;
@@ -296,7 +451,22 @@ function run(findCode, exp, value, typePatternGraph) {
296
451
  return compiledExp(findCode, exp, typePatternGraph || null)(value);
297
452
  }
298
453
 
454
+ function run_rel(findCode, relDef, value, name) {
455
+ return compiledRel(findCode, relDef, name)(value);
456
+ }
457
+
458
+ function run_converged(findCode, exp, value, typePatternGraph, options = {}) {
459
+ const graph = typePatternGraph || null;
460
+ const result = compiledConvergedExp(findCode, exp, graph, options)(value);
461
+ if (result === undefined) return undefined;
462
+ return withPattern(result, outputPattern(graph, exp));
463
+ }
464
+
299
465
  run.builtin = builtin;
466
+ run_converged.builtin = builtin;
467
+ run_converged.defs = null;
468
+
469
+ const run_coverged = run_converged;
300
470
 
301
471
  export default run;
302
- export { run };
472
+ export { run, run_rel, run_converged, run_coverged };
package/test-k-object.mjs CHANGED
@@ -6,7 +6,15 @@ import { decodeWire, encodeToWire, CORE_PATTERN_PROPERTY_LIST } from "./codecs/r
6
6
  import { Product, Variant } from "./Value.mjs";
7
7
  import { exportPatternGraph } from "./codecs/runtime/codec.mjs";
8
8
  import { patternToPropertyList } from "./codecs/runtime/pattern-json.mjs";
9
- import { compileObjectBuffer, decodeObject, objectToFunction, decompileObjectBuffer } from "./object.mjs";
9
+ import {
10
+ compileObjectBuffer,
11
+ compileLibraryBuffer,
12
+ decodeObject,
13
+ objectToFunction,
14
+ runConvergedObject,
15
+ decompileObjectBuffer,
16
+ extractAliasesFromObject
17
+ } from "./object.mjs";
10
18
 
11
19
  const value = parseValue("{a:{b:x,c:{}}}");
12
20
  const wire = encodeToWire(value, null);
@@ -47,10 +55,30 @@ const objectBuffer = compileObjectBuffer(`
47
55
  $ int = < bits +, bits - >;
48
56
  {}|_|0|1|+ $int
49
57
  `);
50
- const objectFn = objectToFunction(decodeObject(objectBuffer));
58
+ assert.equal(objectBuffer.subarray(0, 5).toString("utf8"), "KOBJ\n");
59
+ const object = decodeObject(objectBuffer);
60
+ assert.equal("version" in object, false);
61
+ assert(Object.values(object.codes).every((code) => code.typeDerivation == null));
62
+ assert.equal(JSON.stringify(object.codes).includes('"start"'), false);
63
+ assert.equal(JSON.stringify(object.codes).includes('"end"'), false);
64
+ assert.equal(JSON.stringify(object.meta).includes('"spans"'), false);
65
+ assert(Object.values(object.rels).every((rel) =>
66
+ JSON.stringify(Object.keys(rel.typeDerivation || {}).sort()) === JSON.stringify(["status"]) &&
67
+ rel.typeDerivation.status === "converged"
68
+ ));
69
+ assert.notEqual(object.rels.__main__.def.comp?.[0]?.op, "filter");
70
+ assert.equal(JSON.stringify(object.rels.__main__.def).includes('"start"'), false);
71
+ assert(object.meta[object.relAlias.__main__]?.origins?.some((origin) =>
72
+ origin.name === "__main__" && origin.start && origin.end
73
+ ));
74
+
75
+ const objectFn = objectToFunction(object);
51
76
  const objectResult = objectFn(new Product({}));
52
77
  assert.deepEqual(objectResult.pattern, INT_PATTERN);
53
78
  assert.deepEqual(objectResult.toJSON(), { "+": { "1": { "0": "_" } } });
79
+ const convergedObjectResult = runConvergedObject(object, new Product({}));
80
+ assert.deepEqual(convergedObjectResult.pattern, INT_PATTERN);
81
+ assert.deepEqual(convergedObjectResult.toJSON(), objectResult.toJSON());
54
82
 
55
83
  const decompiledSource = decompileObjectBuffer(objectBuffer);
56
84
  const decompiledResult = k.compile(decompiledSource)(new Product({}));
@@ -71,4 +99,103 @@ const sccSource = decompileObjectBuffer(compileObjectBuffer("c = {}; b = c |x; a
71
99
  assert.match(sccSource, /----- rels -----\nPQgV = .+\n\naQAD = .+\n\nN9UH = /s);
72
100
  assert.deepEqual(k.compile(sccSource)(new Product({})).toJSON(), { y: "x" });
73
101
 
102
+ const libraryBuffer = compileLibraryBuffer("$ nat = <{} zero, nat succ>;\nsucc = |succ;\n", { source: "defs-only.k" });
103
+ assert.equal(libraryBuffer.subarray(0, 1).toString("utf8"), "{");
104
+ const library = decodeObject(libraryBuffer);
105
+ assert.equal("version" in library, false);
106
+ assert.equal(library.main, null);
107
+ assert(Object.values(library.codes).every((code) => code.typeDerivation == null));
108
+ assert.equal(JSON.stringify(library.codes).includes('"start"'), false);
109
+ assert.equal(JSON.stringify(library.codes).includes('"end"'), false);
110
+ assert.equal(JSON.stringify(library.meta).includes('"spans"'), false);
111
+ assert(Object.values(library.rels).every((rel) =>
112
+ JSON.stringify(Object.keys(rel.typeDerivation || {}).sort()) === JSON.stringify(["status"]) &&
113
+ rel.typeDerivation.status === "converged"
114
+ ));
115
+ assert(Object.values(library.rels).every((rel) => rel.def.op !== "comp" || rel.def.comp[0]?.op !== "filter"));
116
+ assert(Object.values(library.meta).some(({ type, origins }) =>
117
+ type === "code" &&
118
+ origins.some((origin) =>
119
+ origin.source === "defs-only.k" &&
120
+ origin.name === "nat" &&
121
+ typeof origin.compiledAt === "string" &&
122
+ origin.start &&
123
+ origin.end
124
+ )
125
+ ));
126
+ assert(Object.values(library.meta).some(({ type, origins }) =>
127
+ type === "rel" &&
128
+ origins.some((origin) =>
129
+ origin.source === "defs-only.k" &&
130
+ origin.name === "succ" &&
131
+ typeof origin.compiledAt === "string" &&
132
+ origin.start &&
133
+ origin.end
134
+ )
135
+ ));
136
+
137
+ const derivedLibrary = decodeObject(compileLibraryBuffer("other = |other;\n", {
138
+ source: "derived.k",
139
+ libraries: [library]
140
+ }));
141
+ const derivedAliases = extractAliasesFromObject(derivedLibrary);
142
+ assert.match(derivedAliases, /^\$ nat = @/m);
143
+ assert.match(derivedAliases, /^succ = @/m);
144
+ assert.match(derivedAliases, /^other = @/m);
145
+ assert(derivedAliases.indexOf("$ nat = @") < derivedAliases.indexOf("other = @"));
146
+ assert(derivedAliases.indexOf("other = @") < derivedAliases.indexOf("succ = @"));
147
+
148
+ {
149
+ const warn = console.warn;
150
+ console.warn = () => {};
151
+ try {
152
+ const nonConverged = decodeObject(compileObjectBuffer("f = .x f; f", {
153
+ convergence: { strategy: "fixed_point", maxIterations: 1 }
154
+ }));
155
+ assert.equal(nonConverged.rels.__main__.typeDerivation.status, "not-converged");
156
+ assert.throws(
157
+ () => runConvergedObject(nonConverged, new Product({ x: new Product({}) })),
158
+ /type derivation is not-converged/
159
+ );
160
+ } finally {
161
+ console.warn = warn;
162
+ }
163
+ }
164
+
165
+ const aliasSnippet = extractAliasesFromObject({
166
+ format: "k-object",
167
+ codes: {},
168
+ rels: {},
169
+ meta: {
170
+ "@relB": {
171
+ type: "rel",
172
+ origins: [
173
+ { source: "new.k", name: "times", compiledAt: "2026-04-12T06:08:10.390Z" },
174
+ { source: "core.k", name: "times", compiledAt: "2026-03-09T06:17:23.390Z" },
175
+ { source: "other.k", name: "plus", compiledAt: "2026-05-17T06:10:23.390Z" }
176
+ ]
177
+ },
178
+ "@codeB": {
179
+ type: "code",
180
+ origins: [
181
+ { source: "z.k", name: "edges", compiledAt: "2026-03-09T06:17:23.390Z" }
182
+ ]
183
+ },
184
+ "@codeA": {
185
+ type: "code",
186
+ origins: [
187
+ { source: "a.k", name: "bits", compiledAt: "2026-05-17T06:10:23.390Z" }
188
+ ]
189
+ }
190
+ },
191
+ main: null
192
+ });
193
+ assert.deepEqual(aliasSnippet.split("\n").filter(Boolean), [
194
+ '$ bits = @codeA; # {"source":"a.k","compiledAt":"2026-05-17T06:10:23.390Z"}',
195
+ '$ edges = @codeB; # {"source":"z.k","compiledAt":"2026-03-09T06:17:23.390Z"}',
196
+ 'plus = @relB; # {"source":"other.k","compiledAt":"2026-05-17T06:10:23.390Z"}',
197
+ '# times = @relB; # {"source":"core.k","compiledAt":"2026-03-09T06:17:23.390Z"}',
198
+ 'times = @relB; # {"source":"new.k","compiledAt":"2026-04-12T06:08:10.390Z"}'
199
+ ]);
200
+
74
201
  console.log("OK");
package/test-repl2.mjs CHANGED
@@ -88,6 +88,8 @@ assert.equal(output[0], `saved ${libPath}`);
88
88
 
89
89
  completions = completeInput(`:load ${tmpDir}/ses`, state)[0];
90
90
  assert(completions.includes(`:load ${libPath}`));
91
+ completions = completeInput(`:load --no-alias ${tmpDir}/ses`, state)[0];
92
+ assert(completions.includes(`:load --no-alias ${libPath}`));
91
93
 
92
94
  output = await evaluateInput(`:ko ${koPath} succ`, state);
93
95
  assert.equal(output[0], `saved ${koPath} (succ)`);
@@ -125,6 +127,14 @@ assert.match(output[0], /^succ = @/m);
125
127
  output = await evaluateInput("succ", reloaded);
126
128
  assert.equal(output[0], "{}|succ ?<{} succ, ...>");
127
129
 
130
+ const noAliasReloaded = createState();
131
+ output = await evaluateInput(`:load --no-alias ${libPath}`, noAliasReloaded);
132
+ assert.equal(output[0], `loaded ${libPath}`);
133
+ output = await evaluateInput(":codes", noAliasReloaded);
134
+ assert.equal(output[0], "(none)");
135
+ output = await evaluateInput(":rels", noAliasReloaded);
136
+ assert.equal(output[0], "(none)");
137
+
128
138
  const shorthand = createState();
129
139
  output = await evaluateInput("$ nat = <{} zero, nat succ>\n; succ = |succ\n;", shorthand);
130
140
  assert.deepEqual(output, []);