@fraczak/k 6.6.0 → 6.7.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.
@@ -78,7 +78,6 @@ All tools accept `--help`.
78
78
 
79
79
  - `-S, --assembly`: Emit GNU ARM64 assembly text (`.s`) instead of an ELF binary.
80
80
  - `-o, --output <file>`: Specify the output file path.
81
- - `--entry <name>`: Specify the entry relation (defaults to `__main__`).
82
81
  - `--lib <file>`: Load one `.klib` or `.k` library dependency.
83
82
  - `--export <spec>`: Export library symbols into source scope (`name` or `libname:localname`).
84
83
  - `--json`: Format output as JSON instead of binary wire format.
@@ -22,7 +22,6 @@ function usage(stream = console.error) {
22
22
  stream("Options:");
23
23
  stream(" -S, --assembly Emit GNU ARM64 assembly (.s) instead of an executable binary.");
24
24
  stream(" -o, --output Specify output file path explicitly.");
25
- stream(" --entry name Specify entry relation name.");
26
25
  stream(" --lib file Load one .klib dependency before compiling.");
27
26
  stream(" --export spec Export a library alias into source scope. May be repeated.");
28
27
  stream(" spec is 'name' or 'libname:localname'.");
@@ -38,8 +37,6 @@ try {
38
37
 
39
38
  let emitAssembly = false;
40
39
  let explicitOutput = null;
41
- let entry = null;
42
-
43
40
  // Extract ARM64-specific flags
44
41
  const remainingArgs = [];
45
42
  while (args.length > 0) {
@@ -51,10 +48,6 @@ try {
51
48
  args.shift();
52
49
  explicitOutput = args.shift();
53
50
  if (!explicitOutput) throw new Error("-o/--output requires a file argument");
54
- } else if (arg === "--entry") {
55
- args.shift();
56
- entry = args.shift();
57
- if (!entry) throw new Error("--entry requires a name argument");
58
51
  } else {
59
52
  remainingArgs.push(args.shift());
60
53
  }
@@ -71,7 +64,6 @@ try {
71
64
  libraries,
72
65
  exportSpecs,
73
66
  stdin,
74
- entry,
75
67
  emit: emitAssembly ? "assembly" : null,
76
68
  outputPath: (!emitAssembly && outputPath) ? outputPath : null
77
69
  });
@@ -23,7 +23,7 @@ backend from `backends/llvm`:
23
23
  npm install
24
24
  cd backends/llvm
25
25
  node ../../objects/compile.mjs '()' /tmp/id.ko
26
- node ./bin/k-llvm-compile.mjs --input-pattern '[["open-product",[]]]' /tmp/id.ko /tmp/id.ll
26
+ node ./bin/k-llvm-compile.mjs /tmp/id.ko /tmp/id.ll
27
27
  ```
28
28
 
29
29
  The generated `.ll` embeds the envelope-specialized KIR-P contract as module
@@ -44,24 +44,26 @@ status in `k_result`. These operations can compose through KIR `comp`.
44
44
  ## CLI
45
45
 
46
46
  ```sh
47
- k-llvm-build [options] object.ko output-exe
48
- k-llvm-compile [options] object.ko [output.ll]
47
+ k-llvm-build [options] [source-snippet | input-file [output-exe]]
48
+ k-llvm-compile [options] [source-snippet | input-file [output.ll]]
49
49
  k-llvm-jit [options] object.ko
50
50
  k-llvm-run [options] object.ko [input.kv]
51
51
  ```
52
52
 
53
53
  Options:
54
54
 
55
- - `--retype rel`: current option spelling for the relation to
56
- envelope-specialize; defaults to the object's `main`.
57
- - `--input-pattern json-or-file`: KIR property-list input pattern; required
58
- for `k-llvm-build`.
59
- - `--expect value-or-file`: for `k-llvm-run`, compare the output value against
60
- expected value text.
55
+ - `-o, --output path`: specify output file path explicitly.
56
+ - `--lib file`: load one `.klib` or `.k` dependency before compiling.
57
+ - `--export spec`: export a library alias into source scope (`name` or `libname:localname`). May be repeated.
58
+ - `--input-pattern json-or-file`: for `k-llvm-run`, KIR property-list input pattern.
59
+ - `--retype rel`: for `k-llvm-run`, relation to specialize (defaults to object main).
60
+ - `--expect value-or-file`: for `k-llvm-run`, compare the output value against expected value text.
61
61
  - `-h`, `--help`: show usage.
62
62
 
63
- Only `.ko` / `.klib` object input is supported in this first prototype.
64
- Source compilation remains owned by core `k`.
63
+ The compile commands follow the unified k CLI input convention: pass inline
64
+ source, an existing `.k` source file, or a `.ko`/`.klib` object. Reads UTF-8
65
+ source from stdin when input is omitted. Output paths can be specified as a
66
+ positional argument or explicitly with `-o, --output`.
65
67
  Without `--expect`, `k-llvm-run` prints the result value as compact JSON.
66
68
  `k-llvm-build` emits a native executable that reads and writes the binary
67
69
  k pattern+value envelope used by `codecs/k-parse.mjs` and `codecs/k-print.mjs`.
@@ -1,14 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import fs from "node:fs";
4
- import { argv, exit } from "node:process";
5
- import { decodeObject } from "@fraczak/k/object.mjs";
3
+ import { argv, exit, stdin } from "node:process";
4
+ import {
5
+ compileProgramInputToObject,
6
+ parseCompileOptions,
7
+ resolveProgramInput
8
+ } from "../src/cli.mjs";
6
9
  import { compileObjectToExecutable } from "../src/executable.mjs";
7
10
 
8
11
  function usage(stream = console.error) {
9
12
  const prog = argv[1] || "k-llvm-build.mjs";
10
- stream(`Usage: node ${prog} [options] object-file output-exe`);
11
- stream("Compile a k .ko/.klib object into a native executable.");
13
+ stream(`Usage: node ${prog} [options] [source-snippet | input-file [output-exe]]`);
14
+ stream("Compile k source, .ko, or .klib input into a native executable.");
15
+ stream("");
16
+ stream("Arguments:");
17
+ stream(" source-snippet Inline k source, in the same style as k.mjs.");
18
+ stream(" input-file Source .k, .ko, or .klib file. Reads UTF-8 source from stdin when omitted.");
19
+ stream(" output-exe Output executable path.");
12
20
  stream("");
13
21
  stream("The executable reads a binary k pattern+value envelope from stdin and");
14
22
  stream("writes a binary k pattern+value envelope to stdout.");
@@ -16,18 +24,11 @@ function usage(stream = console.error) {
16
24
  stream("stdout is encoded with the compiled output pattern.");
17
25
  stream("");
18
26
  stream("Options:");
19
- stream(" --retype rel Relation to specialize. Defaults to object main.");
20
- stream(" --input-pattern value Required input pattern property-list JSON, or a file containing it.");
21
- stream(" -h, --help Show this help.");
22
- }
23
-
24
- function readMaybeFile(textOrPath) {
25
- return fs.existsSync(textOrPath) ? fs.readFileSync(textOrPath, "utf8") : textOrPath;
26
- }
27
-
28
- function readPattern(inputPattern) {
29
- if (inputPattern == null) return null;
30
- return JSON.parse(readMaybeFile(inputPattern));
27
+ stream(" -o, --output path Specify output file path explicitly.");
28
+ stream(" --lib file Load one .klib dependency before compiling.");
29
+ stream(" --export spec Export a library alias into source scope. May be repeated.");
30
+ stream(" spec is 'name' or 'libname:localname'.");
31
+ stream(" -h, --help Show this help.");
31
32
  }
32
33
 
33
34
  try {
@@ -37,33 +38,34 @@ try {
37
38
  exit(0);
38
39
  }
39
40
 
40
- let relation = null;
41
- let inputPattern = null;
42
- while (args.length > 0 && args[0].startsWith("--")) {
43
- const option = args.shift();
44
- if (option === "--retype") {
45
- relation = args.shift();
46
- if (!relation) throw new Error("--retype requires a relation name");
47
- } else if (option === "--input-pattern") {
48
- inputPattern = args.shift();
49
- if (!inputPattern) throw new Error("--input-pattern requires JSON or a file path");
41
+ let explicitOutput = null;
42
+ const remainingArgs = [];
43
+ while (args.length > 0) {
44
+ const arg = args[0];
45
+ if (arg === "-o" || arg === "--output") {
46
+ args.shift();
47
+ explicitOutput = args.shift();
48
+ if (!explicitOutput) throw new Error("-o/--output requires a file argument");
50
49
  } else {
51
- throw new Error(`Unknown option: ${option}`);
50
+ remainingArgs.push(args.shift());
52
51
  }
53
52
  }
54
53
 
55
- const objectPath = args.shift() || null;
56
- const outputPath = args.shift() || null;
57
- if (objectPath == null) throw new Error("object-file is required");
58
- if (outputPath == null) throw new Error("output-exe is required");
59
- if (args.length > 0) throw new Error(`Unexpected argument: ${args[0]}`);
60
- if (inputPattern == null) throw new Error("--input-pattern is required for k-llvm-build");
54
+ const { libraries, exportSpecs } = parseCompileOptions(remainingArgs);
55
+ const input = resolveProgramInput(remainingArgs, { allowStdinSource: true });
56
+ const positionalOutput = remainingArgs.shift();
57
+ if (remainingArgs.length > 0) throw new Error("Too many arguments");
58
+
59
+ const outputPath = explicitOutput || positionalOutput || null;
60
+ if (outputPath == null) throw new Error("Output executable path is required (pass output-exe or -o/--output)");
61
61
 
62
- const object = decodeObject(fs.readFileSync(objectPath));
63
- compileObjectToExecutable(object, outputPath, {
64
- relation: relation || object.main,
65
- inputPattern: readPattern(inputPattern)
62
+ const object = await compileProgramInputToObject(input, {
63
+ libraries,
64
+ exportSpecs,
65
+ stdin
66
66
  });
67
+
68
+ compileObjectToExecutable(object, outputPath);
67
69
  } catch (error) {
68
70
  console.error(error.stack || error.message || String(error));
69
71
  usage();
@@ -2,29 +2,30 @@
2
2
 
3
3
  import fs from "node:fs";
4
4
  import { argv, exit, stdin, stdout } from "node:process";
5
- import { compileBufferToLLVM } from "../src/llvm.mjs";
5
+ import {
6
+ compileProgramInputToObject,
7
+ parseCompileOptions,
8
+ resolveProgramInput
9
+ } from "../src/cli.mjs";
10
+ import { compileObjectToLLVM } from "../src/llvm.mjs";
11
+ import { inputPatternForObjectRelation } from "../src/executable.mjs";
6
12
 
7
13
  function usage(stream = console.error) {
8
14
  const prog = argv[1] || "k-llvm-compile.mjs";
9
- stream(`Usage: node ${prog} [options] object-file [output.ll]`);
15
+ stream(`Usage: node ${prog} [options] [source-snippet | input-file [output.ll]]`);
10
16
  stream("Compile a k .ko/.klib object into prototype LLVM IR.");
11
17
  stream("");
12
18
  stream("Arguments:");
13
- stream(" object-file Input .ko or .klib file. Reads bytes from stdin when omitted.");
19
+ stream(" source-snippet Inline k source, in the same style as k.mjs.");
20
+ stream(" input-file Source .k, .ko, or .klib file. Reads UTF-8 source from stdin when omitted.");
14
21
  stream(" output.ll Output LLVM IR path. Writes to stdout when omitted.");
15
22
  stream("");
16
23
  stream("Options:");
17
- stream(" --retype rel Relation to specialize. Defaults to object main.");
18
- stream(" --input-pattern value Input pattern property-list JSON, or a file containing it.");
19
- stream(" -h, --help Show this help.");
20
- }
21
-
22
- async function readStdinBytes() {
23
- const chunks = [];
24
- for await (const chunk of stdin) {
25
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
26
- }
27
- return Buffer.concat(chunks);
24
+ stream(" -o, --output path Specify output file path explicitly.");
25
+ stream(" --lib file Load one .klib dependency before compiling.");
26
+ stream(" --export spec Export a library alias into source scope. May be repeated.");
27
+ stream(" spec is 'name' or 'libname:localname'.");
28
+ stream(" -h, --help Show this help.");
28
29
  }
29
30
 
30
31
  try {
@@ -34,32 +35,41 @@ try {
34
35
  exit(0);
35
36
  }
36
37
 
37
- let relation = null;
38
- let inputPattern = null;
39
- while (args.length > 0 && args[0].startsWith("--")) {
40
- const option = args.shift();
41
- if (option === "--retype") {
42
- relation = args.shift();
43
- if (!relation) throw new Error("--retype requires a relation name");
44
- } else if (option === "--input-pattern") {
45
- inputPattern = args.shift();
46
- if (!inputPattern) throw new Error("--input-pattern requires JSON or a file path");
38
+ let explicitOutput = null;
39
+ const remainingArgs = [];
40
+ while (args.length > 0) {
41
+ const arg = args[0];
42
+ if (arg === "-o" || arg === "--output") {
43
+ args.shift();
44
+ explicitOutput = args.shift();
45
+ if (!explicitOutput) throw new Error("-o/--output requires a file argument");
47
46
  } else {
48
- throw new Error(`Unknown option: ${option}`);
47
+ remainingArgs.push(args.shift());
49
48
  }
50
49
  }
51
- if (inputPattern == null) throw new Error("--input-pattern is required");
52
50
 
53
- const inputPath = args.shift() || null;
54
- const outputPath = args.shift() || null;
55
- if (args.length > 0) throw new Error(`Unexpected argument: ${args[0]}`);
51
+ const { libraries, exportSpecs } = parseCompileOptions(remainingArgs);
52
+ const input = resolveProgramInput(remainingArgs, { allowStdinSource: true });
53
+ const positionalOutput = remainingArgs.shift();
54
+ if (remainingArgs.length > 0) throw new Error("Too many arguments");
55
+
56
+ const outputPath = explicitOutput || positionalOutput || null;
57
+
58
+ const object = await compileProgramInputToObject(input, {
59
+ libraries,
60
+ exportSpecs,
61
+ stdin
62
+ });
63
+
64
+ const { llvm } = compileObjectToLLVM(object, {
65
+ relation: object.main,
66
+ inputPattern: inputPatternForObjectRelation(object, object.main)
67
+ });
56
68
 
57
- const input = inputPath == null ? await readStdinBytes() : fs.readFileSync(inputPath);
58
- const { llvm } = compileBufferToLLVM(input, { relation, inputPattern });
59
69
  if (outputPath == null) {
60
70
  stdout.write(llvm);
61
71
  } else {
62
- fs.writeFileSync(outputPath, llvm);
72
+ fs.writeFileSync(outputPath, llvm, "utf8");
63
73
  }
64
74
  } catch (error) {
65
75
  console.error(error.stack || error.message || String(error));
@@ -0,0 +1,153 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { compileLibrary, compileObjectBuffer, decodeObject, loadLibrary } from "@fraczak/k/object.mjs";
4
+
5
+ export function readAll(stream) {
6
+ return new Promise((resolve, reject) => {
7
+ const chunks = [];
8
+ stream.on("data", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
9
+ stream.on("end", () => resolve(Buffer.concat(chunks)));
10
+ stream.on("error", reject);
11
+ });
12
+ }
13
+
14
+ export function parseCompileOptions(args) {
15
+ const libraries = [];
16
+ const exportSpecs = [];
17
+ let loadedLibrary = false;
18
+
19
+ while (args.length > 0) {
20
+ if (args[0] === "--") {
21
+ args.shift();
22
+ break;
23
+ }
24
+ if (args[0] === "--lib") {
25
+ if (loadedLibrary) throw new Error("--lib may only be specified once");
26
+ args.shift();
27
+ const libPath = args.shift();
28
+ if (!libPath) throw new Error("--lib requires a file argument");
29
+ const ext = path.extname(libPath).toLowerCase();
30
+ if (ext === ".k") {
31
+ libraries.push(loadLibrary(compileLibrary(fs.readFileSync(libPath, "utf8"), { source: libPath })));
32
+ } else {
33
+ libraries.push(loadLibrary(decodeObject(fs.readFileSync(libPath))));
34
+ }
35
+ loadedLibrary = true;
36
+ } else if (args[0] === "--export") {
37
+ args.shift();
38
+ const spec = args.shift();
39
+ if (!spec) throw new Error("--export requires a spec argument (name or libname:localname)");
40
+ exportSpecs.push(spec);
41
+ } else if (args[0].startsWith("--")) {
42
+ break;
43
+ } else {
44
+ break;
45
+ }
46
+ }
47
+
48
+ return { libraries, exportSpecs };
49
+ }
50
+
51
+ export function fileInputType(filePath) {
52
+ const ext = path.extname(filePath).toLowerCase();
53
+ if (ext === ".ko") return "ko";
54
+ if (ext === ".kvm") return "kvm";
55
+ if (ext === ".klib") return "klib";
56
+ return "source-file";
57
+ }
58
+
59
+ export function resolveProgramInput(args, { allowStdinSource = false } = {}) {
60
+ const inputArg = args.shift() ?? null;
61
+ if (inputArg == null) {
62
+ if (!allowStdinSource) throw new Error("Missing script argument");
63
+ return { kind: "stdin-source", sourceName: "<stdin>" };
64
+ }
65
+
66
+ const ext = path.extname(inputArg).toLowerCase();
67
+ if (fs.existsSync(inputArg)) {
68
+ const kind = fileInputType(inputArg);
69
+ return { kind, path: inputArg, sourceName: inputArg };
70
+ }
71
+
72
+ if ([".k", ".ko", ".kvm", ".klib"].includes(ext)) {
73
+ throw new Error(`Input file not found: ${inputArg}`);
74
+ }
75
+
76
+ return { kind: "inline-source", source: inputArg, sourceName: "<inline>" };
77
+ }
78
+
79
+ export function buildExportPreamble(exports = [], libraries = []) {
80
+ if (exports.length === 0) return "";
81
+ const aliasMap = {};
82
+ for (const lib of libraries) {
83
+ for (const [name, hash] of Object.entries(lib.relAlias || {})) {
84
+ if (name !== "__main__") aliasMap[name] = hash;
85
+ }
86
+ for (const [hash, entry] of Object.entries(lib.meta || {})) {
87
+ if (entry?.type !== "rel") continue;
88
+ for (const origin of entry?.origins || []) {
89
+ if (origin?.name && origin.name !== "__main__") {
90
+ aliasMap[origin.name] = hash;
91
+ }
92
+ }
93
+ }
94
+ }
95
+ const lines = [];
96
+ for (const spec of exports) {
97
+ const [libName, localName] = spec.includes(":") ? spec.split(":", 2) : [spec, spec];
98
+ const hash = aliasMap[libName];
99
+ if (!hash) throw new Error(`--export: '${libName}' not found in loaded libraries`);
100
+ const body = hash.startsWith("@") ? hash.slice(1) : hash;
101
+ lines.push(`${localName} = @${body};`);
102
+ }
103
+ return lines.join("\n") + "\n";
104
+ }
105
+
106
+ export async function compileProgramInputToObject(
107
+ input,
108
+ { libraries = [], exportSpecs = [], stdin = null } = {}
109
+ ) {
110
+ let object;
111
+ if (input.kind === "inline-source") {
112
+ const preamble = buildExportPreamble(exportSpecs, libraries);
113
+ const objBuffer = compileObjectBuffer(preamble + input.source, {
114
+ libraries,
115
+ source: input.sourceName
116
+ });
117
+ object = decodeObject(objBuffer);
118
+ } else if (input.kind === "stdin-source") {
119
+ if (!stdin) throw new Error("stdin source requested without a stdin stream");
120
+ const source = (await readAll(stdin)).toString("utf8");
121
+ const preamble = buildExportPreamble(exportSpecs, libraries);
122
+ const objBuffer = compileObjectBuffer(preamble + source, {
123
+ libraries,
124
+ source: input.sourceName
125
+ });
126
+ object = decodeObject(objBuffer);
127
+ } else if (input.kind === "source-file") {
128
+ const source = fs.readFileSync(input.path, "utf8");
129
+ const preamble = buildExportPreamble(exportSpecs, libraries);
130
+ const objBuffer = compileObjectBuffer(preamble + source, {
131
+ libraries,
132
+ source: input.sourceName
133
+ });
134
+ object = decodeObject(objBuffer);
135
+ } else if (input.kind === "ko" || input.kind === "klib") {
136
+ object = decodeObject(fs.readFileSync(input.path));
137
+ } else if (input.kind === "kvm") {
138
+ throw new Error("Compiling directly from .kvm to LLVM is not yet supported; use .k or .ko input.");
139
+ } else {
140
+ throw new Error(`Unsupported input kind: ${input.kind}`);
141
+ }
142
+
143
+ return object;
144
+ }
145
+
146
+ export default {
147
+ buildExportPreamble,
148
+ compileProgramInputToObject,
149
+ fileInputType,
150
+ parseCompileOptions,
151
+ readAll,
152
+ resolveProgramInput
153
+ };
@@ -531,7 +531,7 @@ export function compileLLVMToExecutable(llvm, outputPath, { driver = stdioDriver
531
531
  driverPath,
532
532
  llPath,
533
533
  "-o",
534
- outputPath
534
+ path.resolve(outputPath)
535
535
  ]);
536
536
  } finally {
537
537
  fs.rmSync(tmpDir, { recursive: true, force: true });
@@ -757,6 +757,7 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
757
757
  const fieldValues = [];
758
758
  const pSrc = getReg(inst.src);
759
759
  const pPattern = regPatterns.get(cleanReg(inst.src)) || (cleanReg(inst.src) === "in" ? regPatterns.get("in") : null);
760
+ let productFailed = false;
760
761
 
761
762
  for (let bIdx = 0; bIdx < N; bIdx++) {
762
763
  const branch = inst.branches[bIdx];
@@ -783,12 +784,21 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
783
784
  nextBlock: branchDone
784
785
  });
785
786
 
787
+ if (!branchRegValues.has(cleanReg(branchRes))) {
788
+ productFailed = true;
789
+ break;
790
+ }
791
+
786
792
  ctx.lines.push(`${branchDone}:`);
787
793
  ctx.currentBlock = branchDone;
788
- const fVal = branchRegValues.get(cleanReg(branchRes)) || branchRes;
794
+ const fVal = branchRegValues.get(cleanReg(branchRes));
789
795
  fieldValues.push({ label: branch.label, val: fVal });
790
796
  }
791
797
 
798
+ if (productFailed) {
799
+ break;
800
+ }
801
+
792
802
  const requestedSize = K_PRODUCT_HEADER_SIZE + (K_FIELD_SIZE * N);
793
803
  const product = lowerRawAlloc(ctx, requestedSize, alignArenaSize(requestedSize));
794
804
  nullCheck(ctx, product, failTarget);
@@ -10,8 +10,7 @@ command -v clang >/dev/null || {
10
10
  }
11
11
 
12
12
  node ../../objects/compile.mjs '()' "$TMP_DIR/id.ko"
13
- printf '[["open-product",[]]]' > "$TMP_DIR/input.pattern.json"
14
- node ./bin/k-llvm-compile.mjs --input-pattern "$TMP_DIR/input.pattern.json" "$TMP_DIR/id.ko" "$TMP_DIR/id.ll"
13
+ node ./bin/k-llvm-compile.mjs "$TMP_DIR/id.ko" "$TMP_DIR/id.ll"
15
14
  grep -q '@k_llvm_metadata' "$TMP_DIR/id.ll"
16
15
  grep -q 'define %k_result @k_main' "$TMP_DIR/id.ll"
17
16
 
@@ -19,32 +18,28 @@ clang -Wno-override-module -Iruntime runtime/krt.c tests/identity-driver.c "$TMP
19
18
  "$TMP_DIR/id"
20
19
 
21
20
  node ../../objects/compile.mjs '.x' "$TMP_DIR/projection.ko"
22
- printf '[["closed-product",[["x",1]]],["closed-product",[]]]' > "$TMP_DIR/projection.pattern.json"
23
- node ./bin/k-llvm-compile.mjs --input-pattern "$TMP_DIR/projection.pattern.json" "$TMP_DIR/projection.ko" "$TMP_DIR/projection.ll"
21
+ node ./bin/k-llvm-compile.mjs "$TMP_DIR/projection.ko" "$TMP_DIR/projection.ll"
24
22
  grep -q 'product_fields_ptr' "$TMP_DIR/projection.ll"
25
23
  ! grep -q 'call ptr @k_product_get_at' "$TMP_DIR/projection.ll"
26
24
  clang -Wno-override-module -Iruntime runtime/krt.c tests/projection-driver.c "$TMP_DIR/projection.ll" -o "$TMP_DIR/projection"
27
25
  "$TMP_DIR/projection"
28
26
 
29
27
  node ../../objects/compile.mjs '{ .x fieldA, .y fieldB }' "$TMP_DIR/product.ko"
30
- printf '[["closed-product",[["x",1],["y",2]]],["closed-product",[["valA",1]]],["closed-product",[["valB",2]]]]' > "$TMP_DIR/product.pattern.json"
31
- node ./bin/k-llvm-compile.mjs --input-pattern "$TMP_DIR/product.pattern.json" "$TMP_DIR/product.ko" "$TMP_DIR/product.ll"
28
+ node ./bin/k-llvm-compile.mjs "$TMP_DIR/product.ko" "$TMP_DIR/product.ll"
32
29
  grep -q 'store i32 1, ptr %i32_slot' "$TMP_DIR/product.ll"
33
30
  grep -q 'store ptr %product_fields' "$TMP_DIR/product.ll"
34
31
  clang -Wno-override-module -Iruntime runtime/krt.c tests/product-driver.c "$TMP_DIR/product.ll" -o "$TMP_DIR/product"
35
32
  "$TMP_DIR/product"
36
33
 
37
34
  node ../../objects/compile.mjs '|tag' "$TMP_DIR/variant.ko"
38
- printf '[["closed-product",[]]]' > "$TMP_DIR/variant.pattern.json"
39
- node ./bin/k-llvm-compile.mjs --input-pattern "$TMP_DIR/variant.pattern.json" "$TMP_DIR/variant.ko" "$TMP_DIR/variant.ll"
35
+ node ./bin/k-llvm-compile.mjs "$TMP_DIR/variant.ko" "$TMP_DIR/variant.ll"
40
36
  grep -q 'call ptr @k_rt_alloc' "$TMP_DIR/variant.ll"
41
37
  grep -q 'store i32 2, ptr %i32_slot' "$TMP_DIR/variant.ll"
42
38
  clang -Wno-override-module -Iruntime runtime/krt.c tests/variant-driver.c "$TMP_DIR/variant.ll" -o "$TMP_DIR/variant"
43
39
  "$TMP_DIR/variant"
44
40
 
45
41
  node ../../objects/compile.mjs '/tag' "$TMP_DIR/variant-projection.ko"
46
- printf '[["closed-union",[["tag",1]]],["closed-product",[]]]' > "$TMP_DIR/variant-projection.pattern.json"
47
- node ./bin/k-llvm-compile.mjs --input-pattern "$TMP_DIR/variant-projection.pattern.json" "$TMP_DIR/variant-projection.ko" "$TMP_DIR/variant-projection.ll"
42
+ node ./bin/k-llvm-compile.mjs "$TMP_DIR/variant-projection.ko" "$TMP_DIR/variant-projection.ll"
48
43
  grep -q 'tag_byte_ptr' "$TMP_DIR/variant-projection.ll"
49
44
  grep -q 'payload_slot' "$TMP_DIR/variant-projection.ll"
50
45
  ! grep -q 'call i32 @k_variant_tag_matches' "$TMP_DIR/variant-projection.ll"
@@ -53,24 +48,21 @@ clang -Wno-override-module -Iruntime runtime/krt.c tests/variant-projection-driv
53
48
  "$TMP_DIR/variant-projection"
54
49
 
55
50
  node ../../objects/compile.mjs '(.x .y)' "$TMP_DIR/composition.ko"
56
- printf '[["closed-product",[["x",1]]],["closed-product",[["y",2]]],["closed-product",[]]]' > "$TMP_DIR/composition.pattern.json"
57
- node ./bin/k-llvm-compile.mjs --input-pattern "$TMP_DIR/composition.pattern.json" "$TMP_DIR/composition.ko" "$TMP_DIR/composition.ll"
51
+ node ./bin/k-llvm-compile.mjs "$TMP_DIR/composition.ko" "$TMP_DIR/composition.ll"
58
52
  grep -q 'product_fields_ptr' "$TMP_DIR/composition.ll"
59
53
  ! grep -q 'call ptr @k_product_get_at' "$TMP_DIR/composition.ll"
60
54
  clang -Wno-override-module -Iruntime runtime/krt.c tests/composition-driver.c "$TMP_DIR/composition.ll" -o "$TMP_DIR/composition"
61
55
  "$TMP_DIR/composition"
62
56
 
63
57
  node ../../objects/compile.mjs 'pick = .x; {.a pick left, .b pick right}' "$TMP_DIR/relation.ko"
64
- printf '[["open-product",[["a",1],["b",2]]],["open-product",[["x",3]]],["open-product",[["x",4]]],["closed-product",[]],["closed-product",[]]]' > "$TMP_DIR/relation.pattern.json"
65
- node ./bin/k-llvm-compile.mjs --input-pattern "$TMP_DIR/relation.pattern.json" "$TMP_DIR/relation.ko" "$TMP_DIR/relation.ll"
58
+ node ./bin/k-llvm-compile.mjs "$TMP_DIR/relation.ko" "$TMP_DIR/relation.ll"
66
59
  grep -q 'define internal %k_result @k_rel_pick' "$TMP_DIR/relation.ll"
67
60
  grep -q 'call %k_result @k_rel_pick' "$TMP_DIR/relation.ll"
68
61
  clang -Wno-override-module -Iruntime runtime/krt.c tests/relation-driver.c "$TMP_DIR/relation.ll" -o "$TMP_DIR/relation"
69
62
  "$TMP_DIR/relation"
70
63
 
71
64
  node ../../objects/compile.mjs '< /x |left, /y |right >' "$TMP_DIR/union.ko"
72
- printf '[["closed-union",[["x",1],["y",2]]],["closed-product",[]],["closed-product",[]]]' > "$TMP_DIR/union.pattern.json"
73
- node ./bin/k-llvm-compile.mjs --input-pattern "$TMP_DIR/union.pattern.json" "$TMP_DIR/union.ko" "$TMP_DIR/union.ll"
65
+ node ./bin/k-llvm-compile.mjs "$TMP_DIR/union.ko" "$TMP_DIR/union.ll"
74
66
  grep -q 'define internal %k_result @k_union_arm_0' "$TMP_DIR/union.ll"
75
67
  grep -q 'call %k_result @k_union_arm_1' "$TMP_DIR/union.ll"
76
68
  clang -Wno-override-module -Iruntime runtime/krt.c tests/union-driver.c "$TMP_DIR/union.ll" -o "$TMP_DIR/union"
@@ -94,8 +86,7 @@ node ../../objects/compile.mjs '{ .x fieldA, .y fieldB }' "$TMP_DIR/run-product.
94
86
  printf '{"x":"left","y":"right"}' > "$TMP_DIR/run-product-input.kv"
95
87
  node ./bin/k-llvm-run.mjs "$TMP_DIR/run-product.ko" "$TMP_DIR/run-product-input.kv" | grep -qx '{"fieldA":"left","fieldB":"right"}'
96
88
 
97
- printf '[["closed-product",[["x",1],["y",3]]],["open-union",[["left",2]]],["closed-product",[]],["open-union",[["right",2]]]]' > "$TMP_DIR/run-envelope.pattern.json"
98
- node ./bin/k-llvm-build.mjs --input-pattern "$TMP_DIR/run-envelope.pattern.json" "$TMP_DIR/run.ko" "$TMP_DIR/run-exe"
89
+ node ./bin/k-llvm-build.mjs '?{<{}=X0 left, ...> x, <X0 right, ...> y} .x' -o "$TMP_DIR/run-exe"
99
90
  printf '{"x":"left","y":"right"}' \
100
91
  | node ../../codecs/k-parse.mjs \
101
92
  | "$TMP_DIR/run-exe" \
@@ -104,15 +95,13 @@ printf '{"x":"left","y":"right"}' \
104
95
 
105
96
  printf '{"x":"left"}' | node ../../codecs/k-parse.mjs | "$TMP_DIR/run-exe" && exit 1 || test "$?" -eq 5
106
97
 
107
- printf '[["open-union",[["left",1],["right",1]]],["closed-product",[]]]' > "$TMP_DIR/identity-variant.pattern.json"
108
- node ./bin/k-llvm-build.mjs --input-pattern "$TMP_DIR/identity-variant.pattern.json" "$TMP_DIR/id.ko" "$TMP_DIR/id-exe"
98
+ node ./bin/k-llvm-build.mjs '?<{} left, {} right, ...>' -o "$TMP_DIR/id-exe"
109
99
  node --input-type=module -e "import { stdout } from 'node:process'; import { encodeToWire } from '../../codecs/runtime/prefix-codec.mjs'; import { Value } from '../../Value.mjs'; const pattern = [[\"open-union\",[[\"left\",1],[\"right\",1]]],[\"closed-product\",[]]]; stdout.write(encodeToWire(Value.variant(\"left\", Value.product({}), pattern), pattern));" \
110
100
  | "$TMP_DIR/id-exe" \
111
101
  | node --input-type=module -e "import { stdin } from 'node:process'; import { decodeWire } from '../../codecs/runtime/prefix-codec.mjs'; const chunks = []; stdin.on('data', (chunk) => chunks.push(chunk)); stdin.on('end', () => console.log(JSON.stringify(decodeWire(Buffer.concat(chunks)).pattern)));" \
112
102
  | grep -Fqx '[["open-union",[["left",1],["right",1]]],["closed-product",[]]]'
113
103
 
114
- printf '[["closed-union",[["+",1],["-",1]]],["closed-union",[["0",1],["1",1],["_",2]]],["closed-product",[]]]' > "$TMP_DIR/identity-int.pattern.json"
115
- node ./bin/k-llvm-build.mjs --input-pattern "$TMP_DIR/identity-int.pattern.json" "$TMP_DIR/id.ko" "$TMP_DIR/id-int-exe"
104
+ node ./bin/k-llvm-build.mjs '$bits = < {} _, bits 0, bits 1 >; $int = < bits "+", bits "-" >; $int' -o "$TMP_DIR/id-int-exe"
116
105
  printf '2' \
117
106
  | node ../../codecs/int.mjs --parse \
118
107
  | "$TMP_DIR/id-int-exe" \
@@ -121,6 +110,13 @@ printf '2' \
121
110
 
122
111
  node --input-type=module -e "import assert from 'node:assert/strict'; import { spawn } from 'node:child_process'; import { encodeToWire, decodeWire } from '../../codecs/runtime/prefix-codec.mjs'; import { parse, INT_PATTERN } from '../../codecs/int.mjs'; const input = encodeToWire(parse('2'), INT_PATTERN); const header = Buffer.alloc(4); header.writeUInt32BE(input.length); const child = spawn(process.argv[1], ['--server']); const chunks = []; const stderr = []; child.stdout.on('data', (chunk) => chunks.push(chunk)); child.stderr.on('data', (chunk) => stderr.push(chunk)); child.stdin.end(Buffer.concat([header, input])); const status = await new Promise((resolve, reject) => { child.on('error', reject); child.on('close', resolve); }); assert.equal(status, 0, Buffer.concat(stderr).toString('utf8')); const output = Buffer.concat(chunks); const length = output.readUInt32BE(0); assert.equal(output.length, 4 + length); assert.deepEqual(decodeWire(output.subarray(4)).value.toJSON(), parse('2').toJSON());" "$TMP_DIR/id-int-exe"
123
112
 
113
+ node ./bin/k-llvm-build.mjs --lib ../../Examples/arithmetics.k --export times 's = {()x, ()y} times; s' -o "$TMP_DIR/square-exe"
114
+ printf '987654321' \
115
+ | node ../../codecs/int.mjs --parse \
116
+ | "$TMP_DIR/square-exe" \
117
+ | node ../../codecs/int.mjs --print \
118
+ | grep -qx '975461057789971041'
119
+
124
120
  printf '{"x":"left","y":"right"}' \
125
121
  | node ../../codecs/k-parse.mjs \
126
122
  | node ./bin/k-llvm-jit.mjs --cache-dir "$TMP_DIR/jit-cache" "$TMP_DIR/run.ko" \
@@ -20,6 +20,7 @@ function usage(stream = console.error) {
20
20
  stream(" wasm-file Output .wasm path. Writes the binary artifact to stdout when omitted.");
21
21
  stream("");
22
22
  stream("Options:");
23
+ stream(" -o, --output Specify output file path explicitly.");
23
24
  stream(" --lib file Load one .klib dependency before compiling.");
24
25
  stream(" --export spec Export a library alias into source scope. May be repeated.");
25
26
  stream(" spec is 'name' or 'libname:localname'.");
@@ -33,10 +34,24 @@ try {
33
34
  exit(0);
34
35
  }
35
36
 
36
- const { libraries, exportSpecs } = parseCompileOptions(args);
37
- const input = resolveProgramInput(args, { allowStdinSource: true });
38
- const outputPath = args.shift();
39
- if (args.length > 0) throw new Error("Too many arguments");
37
+ let explicitOutput = null;
38
+ const remainingArgs = [];
39
+ while (args.length > 0) {
40
+ const arg = args[0];
41
+ if (arg === "-o" || arg === "--output") {
42
+ args.shift();
43
+ explicitOutput = args.shift();
44
+ if (!explicitOutput) throw new Error("-o/--output requires a file argument");
45
+ } else {
46
+ remainingArgs.push(args.shift());
47
+ }
48
+ }
49
+
50
+ const { libraries, exportSpecs } = parseCompileOptions(remainingArgs);
51
+ const input = resolveProgramInput(remainingArgs, { allowStdinSource: true });
52
+ const positionalOutput = remainingArgs.shift();
53
+ if (remainingArgs.length > 0) throw new Error("Too many arguments");
54
+ const outputPath = explicitOutput || positionalOutput || null;
40
55
  const artifact = await compileProgramInput(input, { libraries, exportSpecs, stdin });
41
56
  if (outputPath == null) {
42
57
  stdout.write(artifact);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fraczak/k",
3
- "version": "6.6.0",
3
+ "version": "6.7.0",
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
  "exports": {