@fraczak/k 6.8.0 → 6.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/CLAUDE.md +1 -1
  3. package/DOCS/BIG_PICTURE.md +3 -5
  4. package/DOCS/CODECS.md +80 -181
  5. package/DOCS/CODEC_ARCHITECTURE.md +194 -0
  6. package/DOCS/REPL.md +69 -51
  7. package/Examples/arithmetics.k +1 -0
  8. package/Examples/poly.k +46 -0
  9. package/backends/llvm/runtime/krt.c +4 -0
  10. package/backends/llvm/src/kvm2llvm.mjs +2 -2
  11. package/backends/llvm/tests/test-llvm.mjs +19 -0
  12. package/backends/wasm/src/kvm2wasm.mjs +4 -0
  13. package/backends/wasm/tests/test-wasm.mjs +4 -0
  14. package/browser/repl-browser.mjs +131 -779
  15. package/browser/shims/process.mjs +7 -0
  16. package/browser/tree-view.mjs +5 -2
  17. package/browser/vfs-data.mjs +14 -14
  18. package/codecs/README.md +7 -0
  19. package/codecs/ieee.mjs +50 -75
  20. package/codecs/int.mjs +83 -62
  21. package/codecs/json-codec.mjs +5 -77
  22. package/codecs/json.mjs +12 -56
  23. package/codecs/k-parse.mjs +3 -19
  24. package/codecs/runtime/codec-sdk.mjs +94 -0
  25. package/codecs/runtime/pattern-k.mjs +45 -0
  26. package/codecs/string-codec.mjs +48 -74
  27. package/codecs/unit.mjs +13 -61
  28. package/codecs/utf16.mjs +13 -56
  29. package/codecs/utf8.mjs +11 -55
  30. package/package.json +1 -1
  31. package/patterns/from-k.mjs +2 -22
  32. package/repl-codecs.mjs +69 -196
  33. package/repl.mjs +177 -281
  34. package/scripts/build-repl-html.mjs +114 -385
  35. package/scripts/run-tests.mjs +1 -0
  36. package/tests/test-codecs.mjs +186 -0
  37. package/tests/test-poly.mjs +44 -0
  38. package/tests/test-repl-html.mjs +163 -68
  39. package/tests/test-repl.mjs +210 -156
  40. package/tests/test-structural-values.mjs +17 -0
  41. package/tests/test-tree-view.mjs +3 -0
  42. package/codecs/example-pipeline.mjs +0 -163
  43. package/codecs/runtime/ieee-pattern.mjs +0 -30
  44. package/codecs/runtime/test-codec.mjs +0 -89
  45. package/codecs/runtime/typeFromValue.mjs +0 -34
package/DOCS/REPL.md CHANGED
@@ -33,20 +33,18 @@ complete.
33
33
  | Command | Meaning |
34
34
  | --- | --- |
35
35
  | `:help` | Show command summary |
36
+ | `:engine [wasm\|js]` | Display or switch evaluation engine (`wasm` or `js`) |
37
+ | `:wasm` / `:js` | Shortcut to switch engine |
38
+ | `:rel name` | Show relation definition |
36
39
  | `:type name` | Show the canonical definition of a type |
40
+ | `:types` | List type aliases |
41
+ | `:rels` | List relation aliases |
37
42
  | `:code name` | Alias for `:C name` |
38
- | `:run expr` | Evaluate `expr` on the current value |
39
- | `:eval expr` | Alias for `:run` |
40
- | `:time expr` | Evaluate `expr` and report compilation and execution time |
41
- | `:t name` | Show relation input/output filters |
42
- | `:d name` | Show relation definition |
43
43
  | `:C name` | Show canonical code definition |
44
- | `:codes` | List type aliases |
45
- | `:rels` | List relation aliases |
46
- | `:codec load name\|file` | Load a built-in codec (`int`, `utf8`, `json`, `ieee`, `unit`) or an ES module file |
44
+ | `:codec load file` | Load a codec from an ES module file (e.g. `:codec load codecs/int.mjs`) |
47
45
  | `:codec unload name` | Unload a registered codec |
48
46
  | `:codec list` | List loaded REPL codecs (alias: `:codecs`) |
49
- | `:input [<filter=(...)> [codec]]` | Read next input line using specified codec (opens dialog in browser) |
47
+ | `:input <codec.mjs> [text]` | Read next input line or parse inline text using specified codec |
50
48
  | `:load [--no-alias] file` | Load `.k` source or `.klib` into the current state |
51
49
  | `:klib file` | Export the active relation closure as a `.klib` library |
52
50
  | `:ko file expr` | Export a `.ko` executable with `expr` as main |
@@ -136,9 +134,8 @@ Tab completion covers:
136
134
 
137
135
  Type aliases also complete in `$name` position inside raw k input.
138
136
 
139
- For codec commands, completion covers `:codec load`, `:codec list`, file paths
140
- after `:codec load`, type names or codec names after `:input`, and loaded codec names
141
- after the input pattern or type.
137
+ For codec commands, completion covers `:codec load`, `:codec unload`, `:codec list`,
138
+ file paths after `:codec load`, and codec names after `:input`.
142
139
 
143
140
  ## Loading
144
141
 
@@ -156,71 +153,92 @@ unless `--no-alias` is used.
156
153
 
157
154
  ## Codecs
158
155
 
159
- REPL codecs are usually keyed by canonical code hashes. Patterns are not used
160
- for codec dispatch. A codec may also export `universal = true` to make it
161
- available for any type selected by `:input`; the parsed value is still validated
162
- against the requested type before it enters the session.
156
+ A codec is a recipe for translating between external text and enveloped *k* values:
157
+ - `parse(text)`: parses an external string into an enveloped *k* value.
158
+ - `print(value)`: serializes an enveloped *k* value to external string, throwing an `Error` if the value is not representable.
159
+
160
+ Codecs can define a family of target patterns (e.g. `int` parses/prints single integers as well as lists like `[0,1,2]`).
161
+
162
+ For printing in the REPL, every value is printed with the standard *k* envelope representation, followed by lines for all loaded codecs that can format it:
163
+ ```text
164
+ > 42 int
165
+ {}|_|0|1|0|1|0|1|+ ?<{} _, ...>
166
+ int: 42
167
+ ```
163
168
 
164
169
  For a complete guide to writing a new codec module, see
165
170
  [`CODECS.md`](./CODECS.md).
166
171
 
167
- ### `:codec load name|file`
172
+ ### `:codec load file`
168
173
 
169
- Loads either a built-in codec by name (`int`, `utf8`, `json`, `ieee`, `unit`) or an ES module file. Loading a built-in codec automatically loads its corresponding `.k` definition file if not already present:
170
- - `int` auto-loads `Examples/arithmetics.k`
171
- - `ieee` auto-loads `Examples/ieee.k`
172
- - `utf8`, `json`, `unit` auto-load `Examples/core.k`
174
+ Loads a codec module from an ES module file (e.g. `:codec load codecs/int.mjs`). Codecs are identified by their file name, and export `parse(text)` and `print(value)` functions.
173
175
 
174
- Loading a codec also creates code aliases matching the codec name (e.g. `$ int = @...`, `$ float64 = @...`, `$ utf8 = @...`).
176
+ ### `:codec unload name`
175
177
 
176
- An external codec module exports:
178
+ Unloads a previously loaded codec by name (e.g. `:codec unload int.mjs`).
177
179
 
178
- ```js
179
- export const name = "utf8";
180
- export const codes = ["@..."];
181
- export function parse(text) { /* text -> Value */ }
182
- export function print(value) { /* Value -> text */ }
183
- ```
180
+ ### `:codec list` (or `:codecs`)
184
181
 
185
- A codec may export `patterns` instead of `codes`; each pattern must be a closed
186
- property-list pattern that can be canonicalized to a code hash, and that hash is
187
- recalculated by the REPL. A universal codec exports `universal = true` instead
188
- of `codes` or `patterns`.
182
+ Lists all currently loaded codecs and their source files.
189
183
 
184
+ ### `:input <codec.mjs> [text]`
190
185
 
191
- ### `:codec unload name`
186
+ Parses external input using the specified codec file.
187
+
188
+ - **Interactive mode**: `:input <codec.mjs>` switches the REPL prompt to `<codec.mjs>> `, and the next input line is parsed using that codec:
189
+ ```text
190
+ > :input json.mjs
191
+ json.mjs> {"hello": "world"}
192
+ {...} ?<{...}>
193
+ json.mjs: {"hello":"world"}
194
+ ```
192
195
 
193
- Unloads a previously registered codec by name.
196
+ - **One-line mode**: `:input <codec.mjs> <text>` immediately parses `<text>`:
197
+ ```text
198
+ > :input int.mjs 42
199
+ {}|_|0|1|0|1|0|1|+ ?<{} _, ...>
200
+ int.mjs: 42
194
201
 
195
- ### `:input [<filter=(...)> [codec]]`
202
+ > :input int.mjs [0,1,2]
203
+ {...} ?<{...}>
204
+ int.mjs: [0,1,2]
205
+ ```
196
206
 
197
- Resolves a pattern expression (e.g. `? {string a, float64 n}`, `? (...)`, `? <{} true, {} false>`), a type alias, or a canonical code hash, selects the registered codec, and consumes the next line verbatim as codec input.
207
+ Entering `:input` with no arguments displays usage and a list of loaded codecs.
198
208
 
199
- If only a universal codec is specified (e.g. `:input json`), no pre-declared type or pattern is required; input is parsed directly and derives its pattern from the input payload.
209
+ ## Evaluation Engines: WASM and JS
200
210
 
201
- When a pattern expression or type is provided, the parsed value is validated and constrained against that pattern before becoming the current value:
211
+ The REPL supports two evaluation engines:
202
212
 
203
- ```text
204
- > :input ? {string a, float64 n} json
205
- input ? {string a, float64 n} using json: enter value text
206
- json> {"a": "Woj", "n": 123}
213
+ - **`wasm`** (default): Compiles k expressions into WebAssembly bytecode via polymorphic kVM lowering. Provides near-native execution speed, tail-call optimization (TCO), and stack safety on deep recursion.
214
+ - **`js`**: The tree-walking JavaScript interpreter. Provides near-zero compilation latency (interprets parsed definitions directly) and convenient runtime inspection.
215
+
216
+ ### Engine Commands
217
+
218
+ - `:engine` &mdash; displays active engine (`wasm` or `js`)
219
+ - `:engine wasm` (or `:wasm`) &mdash; switches to the WebAssembly engine
220
+ - `:engine js` (or `:js`) &mdash; switches to the JavaScript engine
221
+
222
+ ### CLI Options
223
+
224
+ When starting `k-repl`, choose the engine with:
225
+
226
+ ```bash
227
+ k-repl --engine=js # or k-repl --js
228
+ k-repl --engine=wasm # or k-repl --wasm (default)
207
229
  ```
208
230
 
209
231
  ## Timing and Profiling
210
232
 
211
233
  Timing reporting is enabled by default for all evaluations, reporting elapsed time
212
- broken down into compilation (type derivation + WebAssembly lowering/compilation) and runtime execution:
234
+ broken down into compilation and runtime execution, annotated with the active engine:
213
235
 
214
236
  ```text
215
237
  > {10 int x, 5 int y} plus
216
238
  {}|_|1|1|1|1|+ ?<{} _, ...>
217
- /* comp: 12.4ms, exec: 0.8ms */
239
+ /* comp: 12.4ms, exec: 0.8ms (wasm) */
218
240
  ```
219
241
 
220
- ### `:time expr`
221
-
222
- Evaluates `expr` with explicit timing and records performance metrics in session state.
223
-
224
242
  ## Web REPL (Studio)
225
243
 
226
244
  The repository provides a standalone, single-file browser REPL: `repl.html`.
@@ -272,8 +290,8 @@ Evaluated values print in k syntax together with the inferred envelope:
272
290
  > inc = | +1;
273
291
  > {} | 0
274
292
  {}|0 ?<{} 0, ...>
275
- > :t inc
276
- inc : ?X0 --> ?<X0 +1, ...> (@...)
293
+ > :rel inc
294
+ inc = | +1; -- @...
277
295
  > :klib nat.klib
278
296
  saved nat.klib
279
297
  > :ko inc.ko inc
@@ -92,6 +92,7 @@ _fromBitList =
92
92
 
93
93
  --------------------------------------
94
94
  $ int = < bits '+', bits '-' >;
95
+ $ ints = <{int car, ints cdr} cons, {} nil>;
95
96
 
96
97
  remove_leading_zeros = <
97
98
  /0 remove_leading_zeros,
package/Examples/poly.k CHANGED
@@ -65,3 +65,49 @@ zip = <
65
65
  {.xs cdr xs, .ys cdr ys} zip cdr
66
66
  } cons
67
67
  >;
68
+
69
+ -- Gen list of intgers: n -> [0, 1, ..., n]
70
+ gen_list = {() n, nil list} gen_list_;
71
+ gen_list_ = <
72
+ {.n neg? if, .list then} .then,
73
+ {.n dec n, {.n car, .list cdr} cons list} gen_list_
74
+ >;
75
+
76
+ -- Identity filter on {x, y} when x is greater than y: ?{X x, X y}=Y -> ?Y
77
+ gt? =
78
+ <
79
+ {.x/- if1, .y/+ if2, () then} .then,
80
+ {{.x/- y, .y/- x} GT if, () then} .then,
81
+ {{.x/+ x, .y/+ y} GT if, () then} .then
82
+ >
83
+ ;
84
+
85
+ -- Split list into two halves by alternating elements: xs -> {first, second}
86
+ split = {() xs, nil first, nil second} _split;
87
+ _split = <
88
+ {.xs /nil if, {.first first, .second second} then} .then,
89
+ {.xs cdr /nil if, {{.xs car car, .first cdr} cons first, .second second} then} .then,
90
+ {.xs cdr cdr xs, {.xs car car, .first cdr} cons first, {.xs cdr car car, .second cdr} cons second} _split
91
+ >;
92
+
93
+ -- Merge two sorted lists: {xs, ys} -> list
94
+ merge = <
95
+ {.xs /nil if, .ys then} .then,
96
+ {.ys /nil if, .xs then} .then,
97
+ {
98
+ {.xs car x, .ys car y} gt? if,
99
+ {.ys car car, {.xs xs, .ys cdr ys} merge cdr} cons then
100
+ } .then,
101
+ {.xs car car, {.xs cdr xs, .ys ys} merge cdr} cons
102
+ >;
103
+
104
+ -- Sort a list in ascending order (merge sort): xs -> list
105
+ sort = list? <
106
+ /nil nil,
107
+ /cons <
108
+ {.cdr /nil if, .car singleton then} .then,
109
+ |cons split {.first sort xs, .second sort ys} merge
110
+ >
111
+ > list?;
112
+
113
+
@@ -563,6 +563,7 @@ k_value *k_rt_compact(k_rt *rt, k_value *root, k_rt_checkpoint *mark_ptr) {
563
563
 
564
564
  if (!is_after_mark(rt, root, *mark_ptr)) {
565
565
  rt_rewind_and_free(rt, *mark_ptr);
566
+ *mark_ptr = k_rt_mark(rt);
566
567
  return root;
567
568
  }
568
569
 
@@ -580,6 +581,7 @@ k_value *k_rt_compact(k_rt *rt, k_value *root, k_rt_checkpoint *mark_ptr) {
580
581
  if (staging_used == 0) {
581
582
  free(staging);
582
583
  if (fixups.offsets != NULL) free(fixups.offsets);
584
+ *mark_ptr = k_rt_mark(rt);
583
585
  return staged_root;
584
586
  }
585
587
 
@@ -603,6 +605,8 @@ k_value *k_rt_compact(k_rt *rt, k_value *root, k_rt_checkpoint *mark_ptr) {
603
605
  free(staging);
604
606
  if (fixups.offsets != NULL) free(fixups.offsets);
605
607
 
608
+ *mark_ptr = k_rt_mark(rt);
609
+
606
610
  return new_root;
607
611
  }
608
612
 
@@ -438,7 +438,7 @@ function hasSelfTailCall(insts, funcName, isTail = true) {
438
438
  for (let i = 0; i < insts.length; i++) {
439
439
  const inst = insts[i];
440
440
  if (isTail && isSelfTailCall(insts, i, funcName)) return true;
441
- if (inst.branches) {
441
+ if (inst.op === "union" && inst.branches) {
442
442
  const unionIsTail = isTail && tailValueAfter(insts, i + 1, inst.dest);
443
443
  for (let bIdx = 0; bIdx < inst.branches.length; bIdx++) {
444
444
  if (hasSelfTailCall(inst.branches[bIdx].body, funcName, unionIsTail)) return true;
@@ -721,7 +721,7 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
721
721
  }
722
722
  case "call": {
723
723
  const v = getReg(inst.src);
724
- if (isSelfTailCall(insts, i, tailRef)) {
724
+ if (isTopLevel && !returnTarget && isSelfTailCall(insts, i, tailRef)) {
725
725
  if (ctx.catchTail) {
726
726
  const compacted = lowerRtCompact(ctx, v, "%tail_mark_slot");
727
727
  ctx.lines.push(` store ptr ${compacted}, ptr %tail_input_slot`);
@@ -237,4 +237,23 @@ assert.match(nestedUnionLLVM, /store ptr %union_value\d+, ptr %ptr_slot\d+/);
237
237
  assert.doesNotMatch(nestedUnionLLVM, /call void @k_product_set_at\(ptr %product\d+/);
238
238
  assert.doesNotMatch(nestedUnionLLVM, /ptr false/);
239
239
 
240
+ const productRecurseObject = decodeObject(compileObjectBuffer(`
241
+ $ list = < {} nil, { {} car, list cdr } cons >;
242
+ foo = $list <
243
+ /nil |nil,
244
+ /cons { .car car, .cdr foo cdr } |cons
245
+ > $list;
246
+ foo
247
+ `, { source: "llvm-product-recurse.k" }));
248
+ const { llvm: productRecurseLLVM } = compileObjectToLLVM(productRecurseObject, {
249
+ relation: "foo",
250
+ inputPattern: [
251
+ ["closed-union", [["nil", 1], ["cons", 2]]],
252
+ ["closed-product", []],
253
+ ["closed-product", [["car", 1], ["cdr", 0]]]
254
+ ]
255
+ });
256
+ // Calls inside product fields must not return tail status (i32 2)
257
+ assert.doesNotMatch(productRecurseLLVM, /insertvalue %k_result undef, i32 2, 0/);
258
+
240
259
  console.log("OK");
@@ -397,6 +397,8 @@ export function lowerToWasm(relDef, name, options = {}) {
397
397
  }
398
398
  lines.push(` local.get $tail_loop_mark`);
399
399
  lines.push(` call $compact_finish`);
400
+ lines.push(` global.get $arena_free`);
401
+ lines.push(` local.set $tail_loop_mark`);
400
402
  lines.push(` end`);
401
403
  for (let i = 0; i < N; i++) {
402
404
  const branch = sortedBranches[i];
@@ -598,6 +600,8 @@ export function lowerToWasm(relDef, name, options = {}) {
598
600
  lines.push(` local.set $${callSrc}`);
599
601
  lines.push(` local.get $tail_loop_mark`);
600
602
  lines.push(` call $compact_finish`);
603
+ lines.push(` global.get $arena_free`);
604
+ lines.push(` local.set $tail_loop_mark`);
601
605
  lines.push(` end`);
602
606
  lines.push(` local.get $${callSrc}`);
603
607
  lines.push(` local.set $in`);
@@ -619,6 +619,10 @@ console.log("==> Running End-to-End Peano Addition & Serialization Tests");
619
619
  const deepRes = exports.add(deepInputPtr);
620
620
  assert.equal(deepRes[1], 1, "Tail-recursive Peano addition must handle deep inputs without overflowing the Wasm call stack");
621
621
 
622
+ const deeperInputPtr = writeAddInputPtrToArena(writeNatPtrToArena(20000), writeNatPtrToArena(0));
623
+ const deeperRes = exports.add(deeperInputPtr);
624
+ assert.equal(deeperRes[1], 1, "Tail-recursive Peano addition must handle 20000 inputs with compaction without call stack overflow");
625
+
622
626
  console.log("End-to-End Peano Addition & Serialization tests passed successfully!");
623
627
  }
624
628