@fraczak/k 7.0.1 → 7.1.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 (89) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/DOCS/BIG_PICTURE.md +5 -5
  3. package/DOCS/FILE_FORMATS.md +241 -18
  4. package/DOCS/OBJECT_FILE_AND_PATTERN.md +9 -9
  5. package/DOCS/PATTERNS.md +3 -3
  6. package/DOCS/POLYMORPHIC_IR.md +104 -0
  7. package/DOCS/PROFILING_AND_TRACING.md +60 -72
  8. package/DOCS/REPL.md +3 -3
  9. package/DOCS/book/03-types-as-finite-automata.md +4 -4
  10. package/DOCS/book/06-values-in-memory.md +9 -9
  11. package/DOCS/book/08-operational-semantics-and-execution.md +1 -1
  12. package/DOCS/book/09-compiler-architecture.md +2 -2
  13. package/DOCS/book/10-from-ast-to-intermediate-representation.md +2 -2
  14. package/DOCS/book/14-canonical-serialization.md +1 -1
  15. package/Examples/arithmetics.k +2 -2
  16. package/Examples/bnat.k +1 -1
  17. package/Examples/karatsuba-mult.k +2 -2
  18. package/JOURNAL.md +288 -0
  19. package/README.md +13 -10
  20. package/access-table.mjs +343 -0
  21. package/backend-api.mjs +18 -0
  22. package/backends/llvm/README.md +20 -21
  23. package/backends/llvm/bin/k-llvm-build.mjs +105 -32
  24. package/backends/llvm/bin/k-llvm-compile.mjs +63 -24
  25. package/backends/llvm/bin/k-llvm.mjs +292 -0
  26. package/backends/llvm/runtime/krt.c +41 -0
  27. package/backends/llvm/runtime/krt.h +2 -0
  28. package/backends/llvm/src/cli.mjs +95 -17
  29. package/backends/llvm/src/executable.mjs +132 -15
  30. package/backends/llvm/src/kvm2llvm.mjs +50 -23
  31. package/backends/llvm/src/llvm.mjs +133 -20
  32. package/backends/llvm/tests/integration.sh +11 -11
  33. package/backends/llvm/{bin → tests}/k-llvm-run.mjs +48 -22
  34. package/backends/llvm/tests/perf-support.mjs +25 -58
  35. package/backends/llvm/tests/test-llvm.mjs +25 -4
  36. package/backends/wasm/bin/k-wasm-compile.mjs +46 -20
  37. package/backends/wasm/bin/k-wasm-run.mjs +61 -20
  38. package/backends/wasm/bin/k-wasm.mjs +47 -13
  39. package/backends/wasm/src/cli.mjs +141 -19
  40. package/backends/wasm/src/wasm.mjs +132 -24
  41. package/backends/wasm/tests/test-wasm.mjs +5 -5
  42. package/browser/repl-browser.mjs +16 -5
  43. package/browser/vfs-data.mjs +5 -5
  44. package/codecs/k-parse.mjs +29 -8
  45. package/codecs/k-print.mjs +22 -6
  46. package/codecs/runtime/codec-sdk.mjs +8 -4
  47. package/codecs/show.mjs +20 -6
  48. package/core.k +8 -8
  49. package/harness/format.mjs +2 -2
  50. package/harness/registry.mjs +0 -2
  51. package/k.mjs +56 -16
  52. package/kir.mjs +200 -75
  53. package/kvm.mjs +49 -12
  54. package/objects/compile.mjs +37 -23
  55. package/objects/decompile.mjs +40 -10
  56. package/objects/extract-aliases.mjs +44 -14
  57. package/objects/inspect.mjs +19 -14
  58. package/objects/validate.mjs +18 -11
  59. package/package.json +10 -7
  60. package/pkir.mjs +772 -0
  61. package/repl.mjs +36 -18
  62. package/scripts/benchmark-idempotency.mjs +12 -8
  63. package/scripts/benchmark.mjs +5 -9
  64. package/scripts/build-repl-html.mjs +16 -4
  65. package/scripts/perf-ieee.mjs +253 -103
  66. package/scripts/perf-int.mjs +585 -297
  67. package/scripts/perf-multiplication.mjs +66 -25
  68. package/scripts/perf-poly.mjs +292 -164
  69. package/scripts/run-tests.mjs +3 -0
  70. package/tests/code-derivation/pattern-envelope.mjs +4 -4
  71. package/tests/test-access-table.mjs +213 -0
  72. package/tests/test-kvm-polymorphic.mjs +2 -2
  73. package/tests/test-pkir.mjs +530 -0
  74. package/tests/test-polymorphic-tdd.mjs +196 -0
  75. package/tests/test-repl-html.mjs +15 -2
  76. package/tests/test-repl.mjs +30 -1
  77. package/backends/arm64/README.md +0 -103
  78. package/backends/arm64/bin/k-arm64-compile.mjs +0 -93
  79. package/backends/arm64/bin/k-arm64-run.mjs +0 -58
  80. package/backends/arm64/bin/k-arm64.mjs +0 -88
  81. package/backends/arm64/scripts/run-tests.mjs +0 -57
  82. package/backends/arm64/src/arm64.mjs +0 -229
  83. package/backends/arm64/src/cli.mjs +0 -155
  84. package/backends/arm64/src/kvm2arm64.mjs +0 -906
  85. package/backends/arm64/src/runtime.c +0 -744
  86. package/backends/arm64/tests/integration.sh +0 -112
  87. package/backends/arm64/tests/test-arm64.mjs +0 -240
  88. package/backends/llvm/bin/k-llvm-jit.mjs +0 -160
  89. package/harness/adapters/arm64.mjs +0 -105
package/kir.mjs CHANGED
@@ -337,6 +337,64 @@ function findRelation(rels, relAlias, name) {
337
337
  return null;
338
338
  }
339
339
 
340
+ function findDots(exp, res = []) {
341
+ if (!exp || typeof exp !== "object") return res;
342
+ if (exp.op === "dot") res.push(exp);
343
+ if (Array.isArray(exp.comp)) exp.comp.forEach((c) => findDots(c, res));
344
+ if (Array.isArray(exp.items)) exp.items.forEach((c) => findDots(c, res));
345
+ if (Array.isArray(exp.union)) exp.union.forEach((c) => findDots(c, res));
346
+ if (Array.isArray(exp.product)) {
347
+ exp.product.forEach((c) => findDots(c?.exp || c?.expr, res));
348
+ }
349
+ if (Array.isArray(exp.fields)) {
350
+ exp.fields.forEach((c) => findDots(c?.expr || c?.exp, res));
351
+ }
352
+ return res;
353
+ }
354
+
355
+ function getDotIndex(rel, dotExp) {
356
+ const label = dotExp.dot || dotExp.label;
357
+ if (!label) return -1;
358
+ const propList = expPatternPropertyList(rel, dotExp, 0);
359
+ if (Array.isArray(propList?.[0]?.[1])) {
360
+ return propList[0][1].findIndex(([l]) => l === label);
361
+ }
362
+ return -1;
363
+ }
364
+
365
+ function relationLayoutSignature(rel) {
366
+ const inPropList = relationPatternPropertyList(rel, 0);
367
+ if (!Array.isArray(inPropList)) return "";
368
+
369
+ const dots = findDots(rel.def);
370
+ const dotIndices = dots.map((d) => {
371
+ const lbl = d.dot || d.label || "";
372
+ return `${lbl}:${getDotIndex(rel, d)}`;
373
+ });
374
+
375
+ const productShapes = [];
376
+ for (const [kind, edges] of inPropList) {
377
+ if (kind === "open-product" || kind === "closed-product") {
378
+ const fieldNames = edges.map(([l]) => l).sort().join(",");
379
+ if (fieldNames.length > 0) productShapes.push(fieldNames);
380
+ }
381
+ }
382
+
383
+ return dotIndices.join(",") + "|" + productShapes.sort().join(";");
384
+ }
385
+
386
+ function productShapeSignature(propList) {
387
+ if (!Array.isArray(propList)) return "";
388
+ const shapes = [];
389
+ for (const [kind, edges] of propList) {
390
+ if (kind === "open-product" || kind === "closed-product") {
391
+ const fieldNames = edges.map(([l]) => l).sort().join(",");
392
+ if (fieldNames.length > 0) shapes.push(fieldNames);
393
+ }
394
+ }
395
+ return shapes.sort().join(";");
396
+ }
397
+
340
398
  export function specializeObjectRelations(object, entryRelationName = null, inputPattern = null) {
341
399
  if (object?.format !== "k-object") {
342
400
  throw new Error("specializeObjectRelations requires a k object");
@@ -362,77 +420,105 @@ export function specializeObjectRelations(object, entryRelationName = null, inpu
362
420
  const specializedRels = new Map();
363
421
  const specializedCounts = new Map();
364
422
  const newRels = {};
423
+ const inProgress = new Map();
424
+ let entryCandidate = null;
425
+
426
+ function specializeRelation(relName, argPropList) {
427
+ const target = findRelation(baseRels, object.relAlias, relName);
428
+ if (!target) return relName;
365
429
 
366
- const entryRel = cloneRelation(entry.rel);
367
- newRels[entry.name] = entryRel;
430
+ const baseInSig = relationLayoutSignature(target.rel);
431
+ const shapeSig = productShapeSignature(argPropList);
432
+ const specKey = target.name + "::" + shapeSig;
368
433
 
369
- if (Array.isArray(inputPattern) && inputPattern.length > 0) {
370
- const inRoot = addPropertyListToPatternGraph(entryRel.typePatternGraph, inputPattern);
371
- try {
372
- entryRel.typePatternGraph.unify("specialize", entryRel.def.patterns[0], inRoot);
373
- } catch {
374
- // Best-effort entry unification
434
+ if (specializedRels.has(specKey)) {
435
+ return specializedRels.get(specKey);
436
+ }
437
+ if (inProgress.has(specKey)) {
438
+ return inProgress.get(specKey);
375
439
  }
376
- specializedRels.set(entry.name + "::" + JSON.stringify(inputPattern), entry.name);
377
- }
378
440
 
379
- const worklist = [entry.name];
380
- while (worklist.length > 0) {
381
- const currentName = worklist.shift();
382
- const currentRel = newRels[currentName];
383
- if (!currentRel) continue;
441
+ const count = (specializedCounts.get(target.name) || 0) + 1;
442
+ const specName = `${target.name}$${count}`;
443
+ inProgress.set(specKey, specName);
384
444
 
385
- const calls = findCallSites(currentRel.def);
386
- for (const call of calls) {
387
- const target = findRelation(baseRels, object.relAlias, call.ref);
388
- if (!target) continue;
445
+ const candidateRel = cloneRelation(target.rel);
446
+ if (!candidateRel.typeDerivation) candidateRel.typeDerivation = {};
447
+ if (!candidateRel.typeDerivation.status) {
448
+ candidateRel.typeDerivation.status = target.rel?.typeDerivation?.status || "converged";
449
+ }
450
+
451
+ if (Array.isArray(argPropList) && argPropList.length > 0) {
452
+ const tInRoot = addPropertyListToPatternGraph(candidateRel.typePatternGraph, argPropList);
453
+ try {
454
+ candidateRel.typePatternGraph.unify("specialize", candidateRel.def.patterns[0], tInRoot);
455
+ } catch {
456
+ // Unification failure: keep target unspecialized
457
+ }
458
+ }
459
+
460
+ if (relName === entry.name && (!entryCandidate || Array.isArray(argPropList))) {
461
+ entryCandidate = candidateRel;
462
+ }
389
463
 
464
+ let anyCallChanged = false;
465
+ const calls = findCallSites(candidateRel.def);
466
+ for (const call of calls) {
390
467
  const argPatternId = call.patterns?.[0];
391
468
  if (argPatternId != null) {
392
- const argRoot = currentRel.typePatternGraph.find(argPatternId);
393
- const argPropList = patternToPropertyList(
394
- exportPatternGraph(currentRel.typePatternGraph, argRoot)
469
+ const argRoot = candidateRel.typePatternGraph.find(argPatternId);
470
+ const childArgPropList = patternToPropertyList(
471
+ exportPatternGraph(candidateRel.typePatternGraph, argRoot)
395
472
  );
396
- const argSig = JSON.stringify(argPropList);
397
- const specKey = target.name + "::" + argSig;
398
-
399
- let specName = specializedRels.get(specKey);
400
- if (!specName) {
401
- const count = (specializedCounts.get(target.name) || 0) + 1;
402
- specializedCounts.set(target.name, count);
403
- specName = count === 1 ? target.name : `${target.name}$${count}`;
404
- specializedRels.set(specKey, specName);
405
-
406
- const targetRel = cloneRelation(target.rel);
407
- const tInRoot = addPropertyListToPatternGraph(targetRel.typePatternGraph, argPropList);
408
- try {
409
- targetRel.typePatternGraph.unify("specialize", targetRel.def.patterns[0], tInRoot);
410
- } catch {
411
- // Unification failure: keep target unspecialized
412
- }
413
- newRels[specName] = targetRel;
414
- worklist.push(specName);
473
+ const childSpecName = specializeRelation(call.ref, childArgPropList);
474
+ if (childSpecName !== call.ref) {
475
+ call.ref = childSpecName;
476
+ anyCallChanged = true;
415
477
  }
416
478
 
417
- call.ref = specName;
418
-
419
- const targetRel = newRels[specName];
420
- const outPatternId = targetRel.def.patterns?.[1];
421
- const retPatternId = call.patterns?.[1];
422
- if (outPatternId != null && retPatternId != null) {
423
- const outRoot = targetRel.typePatternGraph.find(outPatternId);
424
- const retPropList = patternToPropertyList(
425
- exportPatternGraph(targetRel.typePatternGraph, outRoot)
426
- );
427
- const rRetRoot = addPropertyListToPatternGraph(currentRel.typePatternGraph, retPropList);
428
- try {
429
- currentRel.typePatternGraph.unify("specialize", retPatternId, rRetRoot);
430
- } catch {
431
- // Keep existing call return pattern on unification error
479
+ const childRel = newRels[childSpecName] || baseRels[childSpecName];
480
+ if (childRel) {
481
+ const outPatternId = childRel.def.patterns?.[1];
482
+ const retPatternId = call.patterns?.[1];
483
+ if (outPatternId != null && retPatternId != null) {
484
+ const outRoot = childRel.typePatternGraph.find(outPatternId);
485
+ const retPropList = patternToPropertyList(
486
+ exportPatternGraph(childRel.typePatternGraph, outRoot)
487
+ );
488
+ const rRetRoot = addPropertyListToPatternGraph(candidateRel.typePatternGraph, retPropList);
489
+ try {
490
+ candidateRel.typePatternGraph.unify("specialize", retPatternId, rRetRoot);
491
+ } catch {
492
+ // Keep existing call return pattern on unification error
493
+ }
432
494
  }
433
495
  }
434
496
  }
435
497
  }
498
+
499
+ inProgress.delete(specKey);
500
+
501
+ const newInSig = relationLayoutSignature(candidateRel);
502
+ const isDifferent = (newInSig !== baseInSig) || anyCallChanged;
503
+
504
+ if (!isDifferent) {
505
+ specializedRels.set(specKey, target.name);
506
+ return target.name;
507
+ }
508
+
509
+ specializedCounts.set(target.name, count);
510
+ specializedRels.set(specKey, specName);
511
+ newRels[specName] = candidateRel;
512
+ return specName;
513
+ }
514
+
515
+ const entrySpecName = specializeRelation(entry.name, inputPattern);
516
+ if (entrySpecName !== entry.name && newRels[entrySpecName]) {
517
+ newRels[entry.name] = newRels[entrySpecName];
518
+ } else if (entryCandidate) {
519
+ newRels[entry.name] = entryCandidate;
520
+ } else if (!newRels[entry.name]) {
521
+ newRels[entry.name] = baseRels[entry.name];
436
522
  }
437
523
 
438
524
  const resultRels = {
@@ -515,20 +601,27 @@ export default {
515
601
  };
516
602
 
517
603
  function helpText() {
604
+ const prog = argv[1] || "kir.mjs";
518
605
  return [
519
606
  "Export the KIR-P JSON view from a k .ko or .klib object.",
520
607
  "",
521
- `Usage: ${argv[1]} [options] [object-file]`,
608
+ `Usage: ${prog} [options] [object-file]`,
522
609
  "",
523
610
  "Arguments:",
524
- " object-file Input .ko or .klib file. Reads from stdin when omitted.",
611
+ " object-file Input .ko or .klib file (default: stdin).",
525
612
  "",
526
613
  "Options:",
614
+ " -o, --output path Specify output file path explicitly.",
527
615
  " --retype rel Export retyped KIR-P for relation rel.",
528
- " --input-pattern json Input pattern property-list JSON, or a file containing it.",
529
- " -h, --help Show this help.",
616
+ " --input-pattern json Input pattern JSON or file for spec.",
617
+ " -h, --help Show this help.",
618
+ "",
619
+ "KIR-P is an inspection/export view; retyping also emits KIR-P.",
530
620
  "",
531
- "KIR-P is an inspection/export view; retyping also emits KIR-P."
621
+ "Examples:",
622
+ ` ${prog} program.ko`,
623
+ ` ${prog} program.ko -o program.kir.json`,
624
+ ` ${prog} --retype main --input-pattern pattern.json program.ko`
532
625
  ].join("\n");
533
626
  }
534
627
 
@@ -557,32 +650,64 @@ async function main() {
557
650
 
558
651
  let retypeRelation = null;
559
652
  let inputPatternArg = null;
560
- while (args.length > 0 && args[0].startsWith("--")) {
561
- const option = args.shift();
562
- if (option === "--retype") {
653
+ let explicitOutput = null;
654
+ const remainingArgs = [];
655
+ while (args.length > 0) {
656
+ const arg = args[0];
657
+ if (arg === "-o" || arg === "--output") {
658
+ args.shift();
659
+ explicitOutput = args.shift();
660
+ if (!explicitOutput) {
661
+ throw new Error("-o/--output requires a file argument");
662
+ }
663
+ } else if (arg === "--retype") {
664
+ args.shift();
563
665
  retypeRelation = args.shift();
564
666
  if (!retypeRelation) throw new Error("--retype requires a relation name");
565
- } else if (option === "--input-pattern") {
667
+ } else if (arg === "--input-pattern") {
668
+ args.shift();
566
669
  inputPatternArg = args.shift();
567
- if (!inputPatternArg) throw new Error("--input-pattern requires JSON or a file path");
670
+ if (!inputPatternArg) {
671
+ throw new Error("--input-pattern requires JSON or a file path");
672
+ }
673
+ } else if (arg.startsWith("-")) {
674
+ throw new Error(`Unknown option: ${arg}`);
568
675
  } else {
569
- throw new Error(`Unknown option: ${option}`);
676
+ remainingArgs.push(args.shift());
570
677
  }
571
678
  }
572
679
 
573
- const inputPath = args.shift() || null;
574
- if (args.length > 0) {
575
- throw new Error(`Unexpected argument: ${args[0]}`);
680
+ const inputPath = remainingArgs.shift() || null;
681
+ if (remainingArgs.length > 0) {
682
+ throw new Error(`Unexpected argument: ${remainingArgs[0]}`);
576
683
  }
577
684
 
578
- const buffer = inputPath == null ? await readStdinBytes() : fs.readFileSync(inputPath);
685
+ const buffer = inputPath == null
686
+ ? await readStdinBytes()
687
+ : fs.readFileSync(inputPath);
579
688
  const object = decodeObject(buffer);
689
+ let outputData;
580
690
  if (retypeRelation != null) {
581
- if (inputPatternArg == null) throw new Error("--retype requires --input-pattern");
582
- const inputPatternText = fs.existsSync(inputPatternArg) ? fs.readFileSync(inputPatternArg, "utf8") : inputPatternArg;
583
- stdout.write(JSON.stringify(retypeObjectRelation(object, retypeRelation, JSON.parse(inputPatternText)), null, 2) + "\n");
691
+ if (inputPatternArg == null) {
692
+ throw new Error("--retype requires --input-pattern");
693
+ }
694
+ const inputPatternText = fs.existsSync(inputPatternArg)
695
+ ? fs.readFileSync(inputPatternArg, "utf8")
696
+ : inputPatternArg;
697
+ outputData = retypeObjectRelation(
698
+ object,
699
+ retypeRelation,
700
+ JSON.parse(inputPatternText)
701
+ );
702
+ } else {
703
+ outputData = objectToKIRP(object);
704
+ }
705
+
706
+ const json = JSON.stringify(outputData, null, 2) + "\n";
707
+ if (explicitOutput) {
708
+ fs.writeFileSync(explicitOutput, json, "utf8");
584
709
  } else {
585
- stdout.write(JSON.stringify(objectToKIRP(object), null, 2) + "\n");
710
+ stdout.write(json);
586
711
  }
587
712
  }
588
713
 
package/kvm.mjs CHANGED
@@ -9,7 +9,12 @@ import {
9
9
  } from "./run.mjs";
10
10
  import { decodeWire, encodeToWire } from "./codecs/runtime/prefix-codec.mjs";
11
11
  import { intersectPropertyListPatterns } from "./codecs/runtime/codec.mjs";
12
- import { compileObjectBuffer, decodeObject, loadLibrary } from "./object.mjs";
12
+ import {
13
+ compileLibrary,
14
+ compileObjectBuffer,
15
+ decodeObject,
16
+ loadLibrary
17
+ } from "./object.mjs";
13
18
  import codes from "./codes.mjs";
14
19
  import { isMainEntrypoint } from "./codecs/runtime/cli-entry.mjs";
15
20
  import { isIntrinsic, unsupportedIntrinsic } from "./intrinsics.mjs";
@@ -964,20 +969,43 @@ export default {
964
969
  specializeKVM
965
970
  };
966
971
 
967
- function usage() {
972
+ function helpText() {
968
973
  const prog = argv[1] || "kvm.mjs";
969
- console.error(`Usage: node ${prog} [ options ] ( k-expr | input-file ) [ input-file ]`);
970
- console.error("Options:");
971
- console.error(" --lib file Load one .klib dependency before compiling.");
972
- console.error(" --export spec Export a library alias into scope. 'name' or 'libname:localname'. May be repeated.");
973
- console.error(" --envelope-free Run the interpreter in envelope-free mode.");
974
- console.error(" -h, --help Show this help.");
974
+ return [
975
+ "Execute a k expression or file via the kVM bytecode interpreter",
976
+ "over a binary pattern+value wire stream.",
977
+ "",
978
+ `Usage: ${prog} [options]`,
979
+ " ( source-snippet | input-file ) [ input-wire-file ]",
980
+ "",
981
+ "Arguments:",
982
+ " source-snippet Inline k source snippet.",
983
+ " input-file Source .k, .ko, or .klib file.",
984
+ " input-wire-file Optional binary wire input (default: stdin).",
985
+ "",
986
+ "Options:",
987
+ " --lib file Load one .klib or .k library before compiling.",
988
+ " --export spec Export library alias into scope. Repeatable.",
989
+ " spec is 'name' or 'libname:localname'.",
990
+ " --envelope-free Run the interpreter in envelope-free mode.",
991
+ " -h, --help Show this help.",
992
+ "",
993
+ "Examples:",
994
+ ` echo '"abc"' | k-parse | ${prog} '{.1 0}' | k-print`,
995
+ ` echo '{"x":1,"y":2}' | k-json --parse | \\`,
996
+ ` ${prog} --lib Examples/poly.k --export concat \\`,
997
+ ` 's={()xs,()ys}concat; s' | k-json --print`
998
+ ].join("\n");
999
+ }
1000
+
1001
+ function usage(stream = console.error) {
1002
+ stream(helpText());
975
1003
  }
976
1004
 
977
1005
  async function main() {
978
1006
  const args = argv.slice(2);
979
1007
  if (args.includes("-h") || args.includes("--help")) {
980
- usage();
1008
+ usage(console.log);
981
1009
  return exit(0);
982
1010
  }
983
1011
 
@@ -994,9 +1022,18 @@ async function main() {
994
1022
  args.shift();
995
1023
  const libPath = args.shift();
996
1024
  if (!libPath) throw new Error("--lib requires a file argument");
997
- if (libraries.length > 0) throw new Error("--lib may be specified at most once");
998
- const libBuffer = fs.readFileSync(libPath);
999
- libraries.push(loadLibrary(decodeObject(libBuffer)));
1025
+ if (libraries.length > 0) {
1026
+ throw new Error("--lib may be specified at most once");
1027
+ }
1028
+ if (libPath.endsWith(".k")) {
1029
+ const raw = fs.readFileSync(libPath, "utf8");
1030
+ libraries.push(
1031
+ loadLibrary(compileLibrary(raw, { source: libPath }))
1032
+ );
1033
+ } else {
1034
+ const libBuffer = fs.readFileSync(libPath);
1035
+ libraries.push(loadLibrary(decodeObject(libBuffer)));
1036
+ }
1000
1037
  } else if (args[0] === "--export") {
1001
1038
  args.shift();
1002
1039
  const spec = args.shift();
@@ -13,38 +13,43 @@ import {
13
13
  import { objectToKVMArtifact } from "../kvm.mjs";
14
14
 
15
15
  function helpText() {
16
+ const prog = argv[1] || "compile.mjs";
16
17
  return [
17
- "Compile a k source, .ko, or .klib into .ko, .klib, or .kvm output.",
18
- "Output format is inferred from the output file extension, or from --format.",
18
+ "Compile k source, .ko, or .klib into .ko, .klib, or .kvm output.",
19
+ "Output format is inferred from the output extension or --format.",
19
20
  "",
20
- `Usage: ${argv[1]} [options] [source-snippet | input-file [output-file]]`,
21
+ `Usage: ${prog} [options]`,
22
+ " [source-snippet | input-file [output-file]]",
21
23
  "",
22
24
  "Arguments:",
23
- " source-snippet Inline k source, in the same style as k.mjs.",
24
- " input-file Source .k, .ko, or .klib file. Reads UTF-8 source from stdin when omitted.",
25
- " output-file Output path (.ko, .klib, or .kvm). Writes to stdout when omitted.",
25
+ " source-snippet Inline k source, in the same style as k.mjs.",
26
+ " input-file Source .k, .ko, or .klib file.",
27
+ " Reads UTF-8 source from stdin when omitted.",
28
+ " output-file Output path (.ko, .klib, or .kvm).",
29
+ " Writes to stdout when omitted.",
26
30
  "",
27
31
  "Options:",
28
- " --lib file Load one .klib or .k library dependency before compiling.",
29
- " --export spec Export a library alias into the source scope. May be repeated.",
30
- " spec is 'name' or 'libname:localname'.",
31
- " --format fmt Output format: ko, klib, or kvm. Overrides extension detection.",
32
- " -h, --help Show this help.",
32
+ " -o, --output path Specify output file path explicitly.",
33
+ " --format fmt Output format: ko, klib, or kvm.",
34
+ " Overrides extension detection.",
35
+ " --lib file Load one .klib or .k library before compiling.",
36
+ " --export spec Export library alias into scope. Repeatable.",
37
+ " spec is 'name' or 'libname:localname'.",
38
+ " -h, --help Show this help.",
33
39
  "",
34
- "Existing input paths are read as files. A non-existing input with .k, .ko,",
35
- "or .klib extension is reported as a missing file; otherwise it is compiled",
36
- "as inline k source.",
40
+ "Existing input paths are read as files. A non-existing input with",
41
+ ".k, .ko, or .klib extension is reported as missing; otherwise it is",
42
+ "compiled as inline k source.",
37
43
  "",
38
44
  ".kvm is a polymorphic kVM template artifact (layer: KVM-P).",
39
45
  "A .klib input can only be copied to .klib output.",
40
46
  "",
41
47
  "Examples:",
42
- ` ${argv[1]} 'x = |x; x x'`,
43
- ` ${argv[1]} program.k program.ko`,
44
- ` ${argv[1]} program.k program.klib`,
45
- ` ${argv[1]} program.k program.kvm`,
46
- ` ${argv[1]} --lib Examples/arithmetics.k --export plus:+ --export succ --export int '{succ int x,int y}+' a.kvm`,
47
- ` ${argv[1]} --lib core.klib --export add --export mul:times program.k program.ko`
48
+ ` ${prog} 'x = |x; x x'`,
49
+ ` ${prog} program.k -o program.ko`,
50
+ ` ${prog} program.k program.klib`,
51
+ ` ${prog} program.k program.kvm`,
52
+ ` ${prog} --lib core.klib --export add program.k program.ko`
48
53
  ].join("\n");
49
54
  }
50
55
 
@@ -144,6 +149,7 @@ try {
144
149
 
145
150
  const libraries = [];
146
151
  let format = null;
152
+ let explicitOutput = null;
147
153
  const exports = [];
148
154
 
149
155
  while (args.length > 0) {
@@ -151,7 +157,13 @@ try {
151
157
  args.shift();
152
158
  break;
153
159
  }
154
- if (args[0] === "--lib") {
160
+ if (args[0] === "-o" || args[0] === "--output") {
161
+ args.shift();
162
+ explicitOutput = args.shift();
163
+ if (!explicitOutput) {
164
+ throw new Error("-o/--output requires a file argument");
165
+ }
166
+ } else if (args[0] === "--lib") {
155
167
  args.shift();
156
168
  const libPath = args.shift();
157
169
  if (!libPath) throw new Error("--lib requires a file argument");
@@ -172,7 +184,7 @@ try {
172
184
  format = args.shift();
173
185
  if (!["ko", "klib", "kvm"].includes(format))
174
186
  throw new Error(`Unknown format: ${format}. Use ko, klib, or kvm.`);
175
- } else if (args[0].startsWith("--")) {
187
+ } else if (args[0].startsWith("-")) {
176
188
  throw new Error(`Unknown option: ${args[0]}`);
177
189
  } else {
178
190
  break;
@@ -180,11 +192,13 @@ try {
180
192
  }
181
193
 
182
194
  const inputArg = args.shift() || null;
183
- const outputPath = args.shift() || null;
195
+ const positionalOutput = args.shift() || null;
184
196
  if (args.length > 0) {
185
197
  throw new Error(`Unexpected argument: ${args[0]}`);
186
198
  }
187
199
 
200
+ const outputPath = explicitOutput || positionalOutput || null;
201
+
188
202
  const outputFormat = format || inferFormat(outputPath);
189
203
  const input = resolveInput(inputArg);
190
204
  const inputType = input.type;
@@ -5,20 +5,24 @@ import { argv, exit, stdin, stdout } from "node:process";
5
5
  import { decompileObjectBuffer } from "../object.mjs";
6
6
 
7
7
  function helpText() {
8
+ const prog = argv[1] || "decompile.mjs";
8
9
  return [
9
10
  "Decompile a .ko or .klib object into readable k source.",
10
11
  "",
11
- `Usage: ${argv[1]} [ object-file [ k-file ] ]`,
12
+ `Usage: ${prog} [options] [ object-file [ k-file ] ]`,
12
13
  "",
13
14
  "Arguments:",
14
- " object-file Input .ko or .klib path. Reads object data from stdin when omitted.",
15
- " k-file Output .k path. Writes source to stdout when omitted.",
15
+ " object-file Input .ko or .klib path.",
16
+ " Reads object data from stdin when omitted.",
17
+ " k-file Output .k path (default: stdout).",
16
18
  "",
17
19
  "Options:",
18
- " -h, --help Show this help.",
20
+ " -o, --output path Specify output file path explicitly.",
21
+ " -h, --help Show this help.",
19
22
  "",
20
- "Example:",
21
- ` ${argv[1]} program.ko program.decompiled.k`
23
+ "Examples:",
24
+ ` ${prog} program.ko -o program.decompiled.k`,
25
+ ` ${prog} program.ko program.decompiled.k`
22
26
  ].join("\n");
23
27
  }
24
28
 
@@ -35,13 +39,39 @@ async function readStdinBuffer() {
35
39
  }
36
40
 
37
41
  try {
38
- const [, , objectPath, outputPath] = argv;
39
- if (objectPath === "-h" || objectPath === "--help") {
42
+ const rawArgs = argv.slice(2);
43
+ if (rawArgs.includes("-h") || rawArgs.includes("--help")) {
40
44
  usage(console.log);
41
45
  exit(0);
42
46
  }
43
47
 
44
- const objectBuffer = objectPath == null ? await readStdinBuffer() : fs.readFileSync(objectPath);
48
+ let explicitOutput = null;
49
+ const remainingArgs = [];
50
+ while (rawArgs.length > 0) {
51
+ const arg = rawArgs[0];
52
+ if (arg === "-o" || arg === "--output") {
53
+ rawArgs.shift();
54
+ explicitOutput = rawArgs.shift();
55
+ if (!explicitOutput) {
56
+ throw new Error("-o/--output requires a file argument");
57
+ }
58
+ } else if (arg.startsWith("-")) {
59
+ throw new Error(`Unknown option: ${arg}`);
60
+ } else {
61
+ remainingArgs.push(rawArgs.shift());
62
+ }
63
+ }
64
+
65
+ const objectPath = remainingArgs.shift() || null;
66
+ const positionalOutput = remainingArgs.shift() || null;
67
+ if (remainingArgs.length > 0) {
68
+ throw new Error(`Unexpected argument: ${remainingArgs[0]}`);
69
+ }
70
+ const outputPath = explicitOutput || positionalOutput || null;
71
+
72
+ const objectBuffer = objectPath == null
73
+ ? await readStdinBuffer()
74
+ : fs.readFileSync(objectPath);
45
75
  const source = decompileObjectBuffer(objectBuffer);
46
76
  if (outputPath == null) {
47
77
  stdout.write(source);
@@ -49,7 +79,7 @@ try {
49
79
  fs.writeFileSync(outputPath, source);
50
80
  }
51
81
  } catch (error) {
52
- console.error(error);
82
+ console.error(error.message || String(error));
53
83
  usage();
54
84
  exit(1);
55
85
  }