@fraczak/k 6.0.2 → 6.0.3
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/DOCS/OBJECT_FILE_AND_PATTERN.md +4 -0
- package/DOCS/REPL.md +6 -5
- package/README.md +8 -4
- package/UU/ta.k +1 -0
- package/UU/ta.k~ +0 -0
- package/UU/tb.k +0 -0
- package/UU/tc.k +0 -0
- package/codecs/README.md +4 -0
- package/core.klib +0 -0
- package/core.klib~ +0 -0
- package/ieee.klib +0 -0
- package/kkk.k +197 -0
- package/kkk.klib +0 -0
- package/kkk.klib~ +0 -0
- package/object.mjs +162 -8
- package/objects/README.md +10 -0
- package/objects/compile-lib.mjs +26 -2
- package/objects/compile.mjs +26 -2
- package/objects/decompile.mjs +25 -2
- package/objects/extract-aliases.mjs +59 -0
- package/package.json +2 -1
- package/repl2.mjs +87 -34
- package/test-k-object.mjs +77 -1
- package/test-repl2.mjs +10 -0
|
@@ -29,6 +29,10 @@ $ pattern-node = <
|
|
|
29
29
|
$ pattern = < {} nil, { pattern-node car, pattern cdr } cons >;
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
This does not mean `core.k` is pre-loaded into ordinary program execution. The
|
|
33
|
+
special role of `core.k` here is that the wire codec relies on the canonical
|
|
34
|
+
`$pattern` definition from that file.
|
|
35
|
+
|
|
32
36
|
In this schema:
|
|
33
37
|
|
|
34
38
|
- the first pattern-list element is node `0`, the root,
|
package/DOCS/REPL.md
CHANGED
|
@@ -44,7 +44,7 @@ complete.
|
|
|
44
44
|
| `:C name` | Show canonical code definition |
|
|
45
45
|
| `:codes` | List type aliases |
|
|
46
46
|
| `:rels` | List relation aliases |
|
|
47
|
-
| `:load file` | Load `.k` source or `.klib` into the current state |
|
|
47
|
+
| `:load [--no-alias] file` | Load `.k` source or `.klib` into the current state |
|
|
48
48
|
| `:klib file` | Export current state as a `.klib` library |
|
|
49
49
|
| `:ko file expr` | Export a `.ko` executable with `expr` as main |
|
|
50
50
|
| `:val` | Print current value and JSON form |
|
|
@@ -134,16 +134,17 @@ Type aliases also complete in `$name` position inside raw k input.
|
|
|
134
134
|
|
|
135
135
|
## Loading
|
|
136
136
|
|
|
137
|
-
### `:load file.k`
|
|
137
|
+
### `:load [--no-alias] file.k`
|
|
138
138
|
|
|
139
139
|
Compiles the source in the current library context, merges the resulting codes
|
|
140
140
|
and relations into the session, and recovers aliases from user-defined names in
|
|
141
|
-
the file.
|
|
141
|
+
the file unless `--no-alias` is used.
|
|
142
142
|
|
|
143
|
-
### `:load file.klib`
|
|
143
|
+
### `:load [--no-alias] file.klib`
|
|
144
144
|
|
|
145
145
|
Decodes the library file and merges its codes, relations, and metadata into the
|
|
146
|
-
session.
|
|
146
|
+
session. Aliases are recovered from library metadata unless `--no-alias` is
|
|
147
|
+
used.
|
|
147
148
|
|
|
148
149
|
## Export
|
|
149
150
|
|
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ k-repl # start the interactive REPL (or: node repl2.mjs)
|
|
|
34
34
|
| `k-pattern` | `patterns/from-k.mjs` | Extract the canonical root pattern from a k script |
|
|
35
35
|
| `k-compile-object` | `objects/compile.mjs` | Compile a `.k` source to an executable `.ko` object |
|
|
36
36
|
| `k-decompile-object` | `objects/decompile.mjs` | Decompile a `.ko` or `.klib` file back to k source |
|
|
37
|
+
| `k-extract-aliases` | `objects/extract-aliases.mjs` | Extract `.klib` metadata aliases as k source |
|
|
37
38
|
|
|
38
39
|
---
|
|
39
40
|
|
|
@@ -161,7 +162,7 @@ Start with `k-repl` (or `node repl2.mjs`). The prompt is `> `.
|
|
|
161
162
|
| `:d name` | Show relation definition |
|
|
162
163
|
| `:C name` | Show canonical code definition |
|
|
163
164
|
| `:codes` / `:rels` | List type or relation aliases |
|
|
164
|
-
| `:load file` | Load `.k` or `.klib` |
|
|
165
|
+
| `:load [--no-alias] file` | Load `.k` or `.klib`; aliases are loaded unless `--no-alias` is used |
|
|
165
166
|
| `:klib file` | Export current state as `.klib` |
|
|
166
167
|
| `:ko file expr` | Export executable `.ko` |
|
|
167
168
|
| `:val` | Print current value |
|
|
@@ -239,8 +240,9 @@ console.log(annotated.compileStats); // per-SCC strategy and iteration counts
|
|
|
239
240
|
|
|
240
241
|
## Standard Library — `core.k`
|
|
241
242
|
|
|
242
|
-
`core.k` is the k standard core library. It is loaded
|
|
243
|
-
|
|
243
|
+
`core.k` is the k standard core library. It is **not** pre-loaded into ordinary k
|
|
244
|
+
programs or relation evaluation. Instead, it is the reference source for a few
|
|
245
|
+
canonical definitions that the binary codec depends on, especially `$pattern`.
|
|
244
246
|
It defines four things, in order:
|
|
245
247
|
|
|
246
248
|
### §1 & §2 — `$bits` and arithmetic
|
|
@@ -277,7 +279,8 @@ $ pattern = < {} nil, { pattern-node car, pattern cdr } cons >;
|
|
|
277
279
|
The singleton pattern of `$pattern` itself is the fixed framing constant for the wire format:
|
|
278
280
|
every k binary stream starts with a `$pattern` encoded under that constant,
|
|
279
281
|
followed by the value encoded under the pattern just decoded.
|
|
280
|
-
This is why `core.k` ends with a bare `$pattern` expression — it pins that
|
|
282
|
+
This is why `core.k` ends with a bare `$pattern` expression — it pins that
|
|
283
|
+
canonical code/hash used by the wire codec for the leading pattern payload.
|
|
281
284
|
|
|
282
285
|
---
|
|
283
286
|
|
|
@@ -310,6 +313,7 @@ Typical CLI usage:
|
|
|
310
313
|
```bash
|
|
311
314
|
./objects/compile-lib.mjs Examples/ieee.k ieee.klib
|
|
312
315
|
./objects/compile.mjs --lib ieee.klib Examples/nat.k nat.ko
|
|
316
|
+
./objects/extract-aliases.mjs ieee.klib aliases.k
|
|
313
317
|
./objects/decompile.mjs nat.ko
|
|
314
318
|
```
|
|
315
319
|
|
package/UU/ta.k
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
$bit
|
package/UU/ta.k~
ADDED
|
File without changes
|
package/UU/tb.k
ADDED
|
File without changes
|
package/UU/tc.k
ADDED
|
File without changes
|
package/codecs/README.md
CHANGED
|
@@ -10,6 +10,10 @@ The default concrete format is now the binary encoding of a `$pattern` value,
|
|
|
10
10
|
using the `$pattern` type from [`../core.k`](../core.k), immediately followed by
|
|
11
11
|
the binary encoding of the value under that decoded pattern.
|
|
12
12
|
|
|
13
|
+
`core.k` is not loaded automatically as a library when user programs run. The
|
|
14
|
+
codec only depends on the canonical `$pattern` definition from that file as the
|
|
15
|
+
schema for the leading pattern value.
|
|
16
|
+
|
|
13
17
|
There is no separate JSON container format.
|
|
14
18
|
|
|
15
19
|
## Objectives
|
package/core.klib
ADDED
|
Binary file
|
package/core.klib~
ADDED
|
Binary file
|
package/ieee.klib
CHANGED
|
Binary file
|
package/kkk.k
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
-- core.k — the k standard core library
|
|
2
|
+
--
|
|
3
|
+
-- Defines:
|
|
4
|
+
-- 1. $bits — the canonical binary number / bit-path type
|
|
5
|
+
-- 2. $string — unicode strings (used for labels)
|
|
6
|
+
-- 3. $pattern — the type of pattern graphs
|
|
7
|
+
-- 4. Basic $bits arithmetic: succ, plus, times
|
|
8
|
+
|
|
9
|
+
---------------------------------------------------------------
|
|
10
|
+
-- §1. Bits
|
|
11
|
+
---------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
$ bit = < {} 0, {} 1 >;
|
|
14
|
+
b0 = {}|0 $bit;
|
|
15
|
+
b1 = {}|1 $bit;
|
|
16
|
+
|
|
17
|
+
$ bits = < {} _, bits 0, bits 1 >;
|
|
18
|
+
|
|
19
|
+
_ = {}|_;
|
|
20
|
+
|
|
21
|
+
0 = _ |0;
|
|
22
|
+
1 = _ |1;
|
|
23
|
+
|
|
24
|
+
inv = {() x, _ r} _inv;
|
|
25
|
+
_inv = ${bits x, bits r} < {.x /0 x, .r |0 r} _inv, {.x /1 x, .r |1 r} _inv, .r >;
|
|
26
|
+
|
|
27
|
+
concat = ${bits x, bits y} {.x inv x, .y y} concat_;
|
|
28
|
+
concat_ = ${bits x, bits y}
|
|
29
|
+
<
|
|
30
|
+
{.x /0 x, .y |0 y} concat_,
|
|
31
|
+
{.x /1 x, .y |1 y} concat_,
|
|
32
|
+
.y
|
|
33
|
+
>;
|
|
34
|
+
|
|
35
|
+
---------------------------------------------------------------
|
|
36
|
+
-- §2. Bits arithmetic
|
|
37
|
+
---------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
-- successor (increment by 1)
|
|
40
|
+
succ = {inv x, _ y} _inc;
|
|
41
|
+
|
|
42
|
+
_inc = ${bits x, bits y} <
|
|
43
|
+
{.x /0 x, .y |1 y} concat_,
|
|
44
|
+
{.x /1 x, .y |0 y} _inc,
|
|
45
|
+
.y |1
|
|
46
|
+
>;
|
|
47
|
+
|
|
48
|
+
-- constants
|
|
49
|
+
2 = 1 succ;
|
|
50
|
+
3 = 2 succ;
|
|
51
|
+
4 = 3 succ;
|
|
52
|
+
5 = 4 succ;
|
|
53
|
+
6 = 5 succ;
|
|
54
|
+
7 = 6 succ;
|
|
55
|
+
8 = 7 succ;
|
|
56
|
+
9 = 8 succ;
|
|
57
|
+
10 = 9 succ;
|
|
58
|
+
|
|
59
|
+
-- addition on $bits (unsigned, LSB-first with carry)
|
|
60
|
+
plus = ${bits x, bits y} {.x inv x, .y inv y, b0 c} _plus;
|
|
61
|
+
_plus = ${bits x, bits y, bit c}
|
|
62
|
+
<
|
|
63
|
+
{.x /_ if, .y /_ and, <.c/0 0, 1> then} .then,
|
|
64
|
+
{.x /_ if, .c/0 and, .y then} .then,
|
|
65
|
+
{.x y, .y /_|_ x, .c c} _plus,
|
|
66
|
+
{<.c/0 0, 1> x, .y y, .x /_ b0 c} _plus,
|
|
67
|
+
{.x /0 x, .y /0 y, .c/0 b0 c} _plus |0,
|
|
68
|
+
{.x /0 x, .y /0 y, .c/1 b0 c} _plus |1,
|
|
69
|
+
{.x /0 x, .y /1 y, .c/0 b0 c} _plus |1,
|
|
70
|
+
{.x /0 x, .y /1 y, .c/1 b1 c} _plus |0,
|
|
71
|
+
{.x /1 x, .y /0 y, .c/0 b0 c} _plus |1,
|
|
72
|
+
{.x /1 x, .y /0 y, .c/1 b1 c} _plus |0,
|
|
73
|
+
{.x /1 x, .y /1 y, .c/0 b1 c} _plus |0,
|
|
74
|
+
{.x /1 x, .y /1 y, .c/1 b1 c} _plus |1
|
|
75
|
+
>;
|
|
76
|
+
|
|
77
|
+
-- multiplication on $bits (unsigned, shift-and-add)
|
|
78
|
+
times = ${bits x, bits y} {0 result, .x inv _x, .y y} _times;
|
|
79
|
+
_times = ${bits result, bits _x, bits y}
|
|
80
|
+
<
|
|
81
|
+
{ .y /_ if, .result then } .then,
|
|
82
|
+
{
|
|
83
|
+
.result |0 result,
|
|
84
|
+
._x _x,
|
|
85
|
+
.y /0 y
|
|
86
|
+
} _times,
|
|
87
|
+
{
|
|
88
|
+
{._x x, .result |0 y, b0 c} _plus result,
|
|
89
|
+
._x _x,
|
|
90
|
+
.y /1 y
|
|
91
|
+
} _times
|
|
92
|
+
>;
|
|
93
|
+
|
|
94
|
+
---------------------------------------------------------------
|
|
95
|
+
-- §3. Unicode and String
|
|
96
|
+
---------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
-- 8-bit octet used as a building block for UTF-8 payload pieces.
|
|
99
|
+
$ byte = {
|
|
100
|
+
bit 0, bit 1, bit 2, bit 3,
|
|
101
|
+
bit 4, bit 5, bit 6, bit 7
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
-- 7-bit ASCII code point payload (U+0000..U+007F).
|
|
105
|
+
$ ascii = { bit 6, bit 5, bit 4, bit 3, bit 2, bit 1, bit 0 };
|
|
106
|
+
|
|
107
|
+
-- 11-bit payload used for Plane 0 code points U+0080..U+07FF.
|
|
108
|
+
-- Plane 0 is the Basic Multilingual Plane (BMP): U+0000..U+FFFF.
|
|
109
|
+
$ u0080_07FF = { bit 10, bit 9, bit 8, bit 7, bit 6, bit 5, bit 4, bit 3, bit 2, bit 1, bit 0 };
|
|
110
|
+
|
|
111
|
+
-- High-byte classes for common BMP ranges, excluding surrogate/private-use partitions.
|
|
112
|
+
-- BMP (Basic Multilingual Plane) is Plane 0: the first 65,536 code points.
|
|
113
|
+
$ bmp_hi_common = <
|
|
114
|
+
{ bit 2, bit 1, bit 0 } h08_0F,
|
|
115
|
+
{ bit 6, bit 5, bit 4, bit 3, bit 2, bit 1, bit 0 } h10_7F,
|
|
116
|
+
{ bit 6, bit 5, bit 4, bit 3, bit 2, bit 1, bit 0 } h80_CF,
|
|
117
|
+
{ bit 2, bit 1, bit 0 } hD0_D7,
|
|
118
|
+
{} hF9,
|
|
119
|
+
{ bit 0 } hFA_FB,
|
|
120
|
+
{ bit 0 } hFC_FD,
|
|
121
|
+
{ bit 0 } hFE_FF
|
|
122
|
+
>;
|
|
123
|
+
|
|
124
|
+
-- High-byte classes for BMP private-use/high-reserved slices.
|
|
125
|
+
-- These partitions keep Plane 0 subsets explicit in the type encoding.
|
|
126
|
+
$ bmp_hi_private_use = <
|
|
127
|
+
{ bit 3, bit 2, bit 1, bit 0 } hE0_EF,
|
|
128
|
+
{ bit 2, bit 1, bit 0 } hF0_F7,
|
|
129
|
+
{} hF8
|
|
130
|
+
>;
|
|
131
|
+
|
|
132
|
+
-- Plane selector for supplementary code points in Planes 2..16.
|
|
133
|
+
-- Supplementary planes are all planes above BMP (Plane 0).
|
|
134
|
+
$ plane_02_10 = <
|
|
135
|
+
{ bit 0 } p02_03,
|
|
136
|
+
{ bit 1, bit 0 } p04_07,
|
|
137
|
+
{ bit 2, bit 1, bit 0 } p08_0F,
|
|
138
|
+
{} p10
|
|
139
|
+
>;
|
|
140
|
+
|
|
141
|
+
-- Canonical Unicode scalar-value partition used by string encoding.
|
|
142
|
+
-- Branch meaning:
|
|
143
|
+
-- - ascii: U+0000..U+007F (subset of BMP / Plane 0)
|
|
144
|
+
-- - plane0: U+0080..U+07FF (still Plane 0)
|
|
145
|
+
-- - bmp_common, bmp_private_use: U+0800..U+FFFF partitions (Plane 0)
|
|
146
|
+
-- - supplementary_plane1: U+10000..U+1FFFF (Plane 1)
|
|
147
|
+
-- - supplementary_planes2_16: U+20000..U+10FFFF (Planes 2..16)
|
|
148
|
+
$ unicode = <
|
|
149
|
+
ascii ascii,
|
|
150
|
+
u0080_07FF plane0,
|
|
151
|
+
{ bmp_hi_common hi, byte lo } bmp_common,
|
|
152
|
+
{ bmp_hi_private_use hi, byte lo } bmp_private_use,
|
|
153
|
+
{ byte mid, byte lo } supplementary_plane1,
|
|
154
|
+
{ plane_02_10 plane, byte mid, byte lo } supplementary_planes2_16
|
|
155
|
+
>;
|
|
156
|
+
|
|
157
|
+
-- Linked-list string: nil or (unicode car, string cdr).
|
|
158
|
+
$ string = < {} nil, { unicode car, string cdr } cons >;
|
|
159
|
+
|
|
160
|
+
---------------------------------------------------------------
|
|
161
|
+
-- §4. Pattern
|
|
162
|
+
---------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
-- Edge: a labeled edge in a pattern graph.
|
|
165
|
+
-- 'label' is the field name or tag name.
|
|
166
|
+
-- 'target' is the index (as $bits) of the target node in the pattern list.
|
|
167
|
+
|
|
168
|
+
$ edge = { string label, bits target };
|
|
169
|
+
|
|
170
|
+
-- Edge list: the edges of a pattern node, sorted by label.
|
|
171
|
+
|
|
172
|
+
$ edges = < {} nil, { edge car, edges cdr } cons >;
|
|
173
|
+
|
|
174
|
+
-- Pattern node: one of five kinds.
|
|
175
|
+
-- "any" is the unconstrained position (...), carrying no edges.
|
|
176
|
+
-- The four others carry their edge list directly.
|
|
177
|
+
|
|
178
|
+
$ pattern-node = <
|
|
179
|
+
{} any,
|
|
180
|
+
edges open-product,
|
|
181
|
+
edges open-union,
|
|
182
|
+
edges closed-product,
|
|
183
|
+
edges closed-union
|
|
184
|
+
>;
|
|
185
|
+
|
|
186
|
+
-- Pattern: a cons-list of pattern nodes.
|
|
187
|
+
-- Node 0 (first element) is the root.
|
|
188
|
+
-- Edges reference nodes by their index in this list, encoded as $bits.
|
|
189
|
+
--
|
|
190
|
+
-- The singleton pattern of $pattern itself is a fixed constant.
|
|
191
|
+
-- That constant is the implicit framing for all k wire formats:
|
|
192
|
+
-- to decode any value, first decode a $pattern under that constant,
|
|
193
|
+
-- then decode the value under the pattern just obtained.
|
|
194
|
+
|
|
195
|
+
$ pattern = < {} nil, { pattern-node car, pattern cdr } cons >;
|
|
196
|
+
|
|
197
|
+
$pattern
|
package/kkk.klib
ADDED
|
Binary file
|
package/kkk.klib~
ADDED
|
Binary file
|
package/object.mjs
CHANGED
|
@@ -61,19 +61,86 @@ function hydrateAnnotated(serialized) {
|
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
const NAME_RE = /^[a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*$/;
|
|
65
|
+
|
|
66
|
+
function isSourceName(name) {
|
|
67
|
+
return NAME_RE.test(name) && !name.startsWith("@") && !name.startsWith(":");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function addOrigin(meta, hash, type, origin) {
|
|
71
|
+
if (!hash) return;
|
|
72
|
+
if (!meta[hash]) {
|
|
73
|
+
meta[hash] = { type, origins: [] };
|
|
74
|
+
} else if (meta[hash].type == null) {
|
|
75
|
+
meta[hash].type = type;
|
|
76
|
+
} else if (meta[hash].type !== type) {
|
|
77
|
+
throw new Error(`Metadata type conflict for ${hash}: ${meta[hash].type} vs ${type}`);
|
|
78
|
+
}
|
|
79
|
+
if (meta[hash].origins.some((existing) =>
|
|
80
|
+
existing.source === origin.source &&
|
|
81
|
+
existing.name === origin.name &&
|
|
82
|
+
existing.compiledAt === origin.compiledAt
|
|
83
|
+
)) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
meta[hash].origins.push(origin);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function mergeMetaEntries(...metas) {
|
|
90
|
+
const merged = {};
|
|
91
|
+
for (const meta of metas) {
|
|
92
|
+
for (const [hash, entry] of Object.entries(meta || {})) {
|
|
93
|
+
const type = entry?.type;
|
|
94
|
+
if (type !== "code" && type !== "rel") continue;
|
|
95
|
+
for (const origin of entry.origins || []) {
|
|
96
|
+
if (!origin?.name) continue;
|
|
97
|
+
addOrigin(merged, hash, type, {
|
|
98
|
+
...(origin.source == null ? {} : { source: origin.source }),
|
|
99
|
+
name: origin.name,
|
|
100
|
+
...(origin.compiledAt == null ? {} : { compiledAt: origin.compiledAt })
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return merged;
|
|
106
|
+
}
|
|
107
|
+
|
|
64
108
|
function buildMeta(annotated, source) {
|
|
65
109
|
const meta = {};
|
|
110
|
+
const representatives = annotated.representatives || {};
|
|
66
111
|
const relAlias = annotated.relAlias || {};
|
|
67
112
|
const now = new Date().toISOString();
|
|
68
|
-
|
|
113
|
+
|
|
114
|
+
for (const [name, hash] of Object.entries(representatives)) {
|
|
115
|
+
if (!isSourceName(name)) continue;
|
|
116
|
+
addOrigin(meta, hash, "code", { source, name, compiledAt: now });
|
|
117
|
+
}
|
|
118
|
+
|
|
69
119
|
for (const [name, hash] of Object.entries(relAlias)) {
|
|
70
120
|
if (name === "__main__") continue;
|
|
71
|
-
if (!
|
|
72
|
-
meta
|
|
121
|
+
if (!isSourceName(name)) continue;
|
|
122
|
+
addOrigin(meta, hash, "rel", { source, name, compiledAt: now });
|
|
73
123
|
}
|
|
74
124
|
return meta;
|
|
75
125
|
}
|
|
76
126
|
|
|
127
|
+
function importedLibraryMeta(libraries = []) {
|
|
128
|
+
return mergeMetaEntries(...libraries.map((library) => library.meta || {}));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function isEofParseError(error) {
|
|
132
|
+
return error?.hash?.token === "EOF" || /got 'EOF'|Expecting/.test(error?.message || "");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function annotateLibrary(script, options = {}) {
|
|
136
|
+
try {
|
|
137
|
+
return annotate(script, options);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
if (!isEofParseError(error)) throw error;
|
|
140
|
+
return annotate(`${script}\n()`, options);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
77
144
|
function collectReachable(mainName, rels, allCodes) {
|
|
78
145
|
const reachableRels = new Set();
|
|
79
146
|
const reachableCodes = new Set();
|
|
@@ -140,7 +207,10 @@ function compileObject(script, options = {}) {
|
|
|
140
207
|
const annotated = annotate(script, options);
|
|
141
208
|
const allCodes = codes.dump();
|
|
142
209
|
const serialized = serializeAnnotated(annotated);
|
|
143
|
-
const meta =
|
|
210
|
+
const meta = mergeMetaEntries(
|
|
211
|
+
importedLibraryMeta(options.libraries),
|
|
212
|
+
buildMeta(annotated, options.source || null)
|
|
213
|
+
);
|
|
144
214
|
|
|
145
215
|
// Prune to reachable from main
|
|
146
216
|
const { reachableRels, reachableCodes } = collectReachable("__main__", serialized.rels, allCodes);
|
|
@@ -189,9 +259,12 @@ function rewriteRefsToCanonical(exp, relAlias) {
|
|
|
189
259
|
}
|
|
190
260
|
|
|
191
261
|
function compileLibrary(script, options = {}) {
|
|
192
|
-
const annotated =
|
|
262
|
+
const annotated = annotateLibrary(script, options);
|
|
193
263
|
const serialized = serializeAnnotated(annotated);
|
|
194
|
-
const meta =
|
|
264
|
+
const meta = mergeMetaEntries(
|
|
265
|
+
importedLibraryMeta(options.libraries),
|
|
266
|
+
buildMeta(annotated, options.source || null)
|
|
267
|
+
);
|
|
195
268
|
const relAlias = serialized.relAlias || {};
|
|
196
269
|
|
|
197
270
|
// Key rels by canonical hash, rewrite internal refs to canonical hashes
|
|
@@ -482,6 +555,83 @@ function decompileObjectBuffer(buffer) {
|
|
|
482
555
|
return decompileObject(decodeObject(buffer));
|
|
483
556
|
}
|
|
484
557
|
|
|
558
|
+
function hashBody(hash) {
|
|
559
|
+
return hash.startsWith("@") ? hash.slice(1) : hash;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function compareAliasEntries(left, right) {
|
|
563
|
+
return (
|
|
564
|
+
left.typeOrder - right.typeOrder ||
|
|
565
|
+
left.name.localeCompare(right.name) ||
|
|
566
|
+
compareMaybeTimestamp(left.compiledAt, right.compiledAt) ||
|
|
567
|
+
left.hash.localeCompare(right.hash) ||
|
|
568
|
+
left.source.localeCompare(right.source)
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function compareMaybeTimestamp(left, right) {
|
|
573
|
+
if (left == null && right == null) return 0;
|
|
574
|
+
if (left == null) return 1;
|
|
575
|
+
if (right == null) return -1;
|
|
576
|
+
return left.localeCompare(right);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function originComment(origin) {
|
|
580
|
+
return JSON.stringify({
|
|
581
|
+
...(origin.source == null ? {} : { source: origin.source }),
|
|
582
|
+
...(origin.compiledAt == null ? {} : { compiledAt: origin.compiledAt })
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function extractAliasesFromObject(object) {
|
|
587
|
+
const hydrated = isHydratedObject(object) ? object : hydrateObject(object);
|
|
588
|
+
const entries = [];
|
|
589
|
+
|
|
590
|
+
for (const [hash, entry] of Object.entries(hydrated.meta || {})) {
|
|
591
|
+
const type = entry?.type;
|
|
592
|
+
if (type !== "code" && type !== "rel") continue;
|
|
593
|
+
const typeOrder = type === "code" ? 0 : 1;
|
|
594
|
+
for (const origin of entry.origins || []) {
|
|
595
|
+
if (!origin?.name || !isSourceName(origin.name)) continue;
|
|
596
|
+
entries.push({
|
|
597
|
+
type,
|
|
598
|
+
typeOrder,
|
|
599
|
+
hash,
|
|
600
|
+
name: origin.name,
|
|
601
|
+
source: origin.source || "",
|
|
602
|
+
compiledAt: origin.compiledAt,
|
|
603
|
+
comment: originComment(origin)
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
entries.sort(compareAliasEntries);
|
|
609
|
+
|
|
610
|
+
const activeEntries = new Set();
|
|
611
|
+
for (let i = 0; i < entries.length; i++) {
|
|
612
|
+
const next = entries[i + 1];
|
|
613
|
+
if (!next || next.type !== entries[i].type || next.name !== entries[i].name) {
|
|
614
|
+
activeEntries.add(entries[i]);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const lines = [];
|
|
619
|
+
let lastType = null;
|
|
620
|
+
for (const entry of entries) {
|
|
621
|
+
if (lastType != null && lastType !== entry.type) lines.push("");
|
|
622
|
+
lastType = entry.type;
|
|
623
|
+
const prefix = entry.type === "code" ? "$ " : "";
|
|
624
|
+
const line = `${prefix}${entry.name} = @${hashBody(entry.hash)}; # ${entry.comment}`;
|
|
625
|
+
lines.push(activeEntries.has(entry) ? line : `# ${line}`);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
return `${lines.join("\n")}${lines.length === 0 ? "" : "\n"}`;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function extractAliasesFromObjectBuffer(buffer) {
|
|
632
|
+
return extractAliasesFromObject(decodeObject(buffer));
|
|
633
|
+
}
|
|
634
|
+
|
|
485
635
|
export {
|
|
486
636
|
compileObject,
|
|
487
637
|
compileObjectBuffer,
|
|
@@ -496,7 +646,9 @@ export {
|
|
|
496
646
|
runObject,
|
|
497
647
|
objectToFunction,
|
|
498
648
|
decompileObject,
|
|
499
|
-
decompileObjectBuffer
|
|
649
|
+
decompileObjectBuffer,
|
|
650
|
+
extractAliasesFromObject,
|
|
651
|
+
extractAliasesFromObjectBuffer
|
|
500
652
|
};
|
|
501
653
|
|
|
502
654
|
export default {
|
|
@@ -513,5 +665,7 @@ export default {
|
|
|
513
665
|
runObject,
|
|
514
666
|
objectToFunction,
|
|
515
667
|
decompileObject,
|
|
516
|
-
decompileObjectBuffer
|
|
668
|
+
decompileObjectBuffer,
|
|
669
|
+
extractAliasesFromObject,
|
|
670
|
+
extractAliasesFromObjectBuffer
|
|
517
671
|
};
|
package/objects/README.md
CHANGED
|
@@ -31,6 +31,16 @@ Decompiles a `.ko` or `.klib` back into human-readable k source.
|
|
|
31
31
|
./objects/decompile.mjs [object-file [k-file]]
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
### extract-aliases.mjs
|
|
35
|
+
|
|
36
|
+
Extracts metadata aliases from a `.ko` or `.klib` as a valid k definition
|
|
37
|
+
snippet. Output is grouped by metadata type (`code`, then `rel`), then sorted by
|
|
38
|
+
alias name and `compiledAt`.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
./objects/extract-aliases.mjs [object-file [k-file]]
|
|
42
|
+
```
|
|
43
|
+
|
|
34
44
|
## Further reading
|
|
35
45
|
|
|
36
46
|
- [DOCS/OBJECT_FILE_AND_PATTERN.md](../DOCS/OBJECT_FILE_AND_PATTERN.md) — object file format and pattern encoding
|
package/objects/compile-lib.mjs
CHANGED
|
@@ -4,8 +4,27 @@ import fs from "node:fs";
|
|
|
4
4
|
import { argv, exit, stdin, stdout } from "node:process";
|
|
5
5
|
import { compileLibraryBuffer, decodeObject, loadLibrary } from "../object.mjs";
|
|
6
6
|
|
|
7
|
-
function
|
|
8
|
-
|
|
7
|
+
function helpText() {
|
|
8
|
+
return [
|
|
9
|
+
"Compile a k source file into a .klib library object.",
|
|
10
|
+
"",
|
|
11
|
+
`Usage: ${argv[1]} [ --lib lib-file ]... [ k-file [ lib-file ] ]`,
|
|
12
|
+
"",
|
|
13
|
+
"Arguments:",
|
|
14
|
+
" k-file Source .k file to compile. Reads UTF-8 source from stdin when omitted.",
|
|
15
|
+
" lib-file Output .klib path. Writes binary library data to stdout when omitted.",
|
|
16
|
+
"",
|
|
17
|
+
"Options:",
|
|
18
|
+
" --lib file Load a .klib dependency before compiling. May be repeated.",
|
|
19
|
+
" -h, --help Show this help.",
|
|
20
|
+
"",
|
|
21
|
+
"Example:",
|
|
22
|
+
` ${argv[1]} --lib core.klib library.k library.klib`
|
|
23
|
+
].join("\n");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function usage(stream = console.error) {
|
|
27
|
+
stream(helpText());
|
|
9
28
|
}
|
|
10
29
|
|
|
11
30
|
async function readStdinText() {
|
|
@@ -18,6 +37,11 @@ async function readStdinText() {
|
|
|
18
37
|
|
|
19
38
|
try {
|
|
20
39
|
const args = argv.slice(2);
|
|
40
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
41
|
+
usage(console.log);
|
|
42
|
+
exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
21
45
|
const libraries = [];
|
|
22
46
|
while (args.length > 0 && args[0] === "--lib") {
|
|
23
47
|
args.shift();
|
package/objects/compile.mjs
CHANGED
|
@@ -4,8 +4,27 @@ import fs from "node:fs";
|
|
|
4
4
|
import { argv, exit, stdin, stdout } from "node:process";
|
|
5
5
|
import { compileObjectBuffer, decodeObject, loadLibrary } from "../object.mjs";
|
|
6
6
|
|
|
7
|
-
function
|
|
8
|
-
|
|
7
|
+
function helpText() {
|
|
8
|
+
return [
|
|
9
|
+
"Compile a k source file into an executable .ko object.",
|
|
10
|
+
"",
|
|
11
|
+
`Usage: ${argv[1]} [ --lib lib-file ]... [ k-file [ object-file ] ]`,
|
|
12
|
+
"",
|
|
13
|
+
"Arguments:",
|
|
14
|
+
" k-file Source .k file to compile. Reads UTF-8 source from stdin when omitted.",
|
|
15
|
+
" object-file Output .ko path. Writes binary object data to stdout when omitted.",
|
|
16
|
+
"",
|
|
17
|
+
"Options:",
|
|
18
|
+
" --lib file Load a .klib dependency before compiling. May be repeated.",
|
|
19
|
+
" -h, --help Show this help.",
|
|
20
|
+
"",
|
|
21
|
+
"Example:",
|
|
22
|
+
` ${argv[1]} --lib core.klib program.k program.ko`
|
|
23
|
+
].join("\n");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function usage(stream = console.error) {
|
|
27
|
+
stream(helpText());
|
|
9
28
|
}
|
|
10
29
|
|
|
11
30
|
async function readStdinText() {
|
|
@@ -18,6 +37,11 @@ async function readStdinText() {
|
|
|
18
37
|
|
|
19
38
|
try {
|
|
20
39
|
const args = argv.slice(2);
|
|
40
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
41
|
+
usage(console.log);
|
|
42
|
+
exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
21
45
|
const libraries = [];
|
|
22
46
|
while (args.length > 0 && args[0] === "--lib") {
|
|
23
47
|
args.shift();
|
package/objects/decompile.mjs
CHANGED
|
@@ -4,8 +4,26 @@ import fs from "node:fs";
|
|
|
4
4
|
import { argv, exit, stdin, stdout } from "node:process";
|
|
5
5
|
import { decompileObjectBuffer } from "../object.mjs";
|
|
6
6
|
|
|
7
|
-
function
|
|
8
|
-
|
|
7
|
+
function helpText() {
|
|
8
|
+
return [
|
|
9
|
+
"Decompile a .ko or .klib object into readable k source.",
|
|
10
|
+
"",
|
|
11
|
+
`Usage: ${argv[1]} [ object-file [ k-file ] ]`,
|
|
12
|
+
"",
|
|
13
|
+
"Arguments:",
|
|
14
|
+
" object-file Input .ko or .klib path. Reads binary object data from stdin when omitted.",
|
|
15
|
+
" k-file Output .k path. Writes source to stdout when omitted.",
|
|
16
|
+
"",
|
|
17
|
+
"Options:",
|
|
18
|
+
" -h, --help Show this help.",
|
|
19
|
+
"",
|
|
20
|
+
"Example:",
|
|
21
|
+
` ${argv[1]} program.ko program.decompiled.k`
|
|
22
|
+
].join("\n");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function usage(stream = console.error) {
|
|
26
|
+
stream(helpText());
|
|
9
27
|
}
|
|
10
28
|
|
|
11
29
|
async function readStdinBuffer() {
|
|
@@ -18,6 +36,11 @@ async function readStdinBuffer() {
|
|
|
18
36
|
|
|
19
37
|
try {
|
|
20
38
|
const [, , objectPath, outputPath] = argv;
|
|
39
|
+
if (objectPath === "-h" || objectPath === "--help") {
|
|
40
|
+
usage(console.log);
|
|
41
|
+
exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
21
44
|
const objectBuffer = objectPath == null ? await readStdinBuffer() : fs.readFileSync(objectPath);
|
|
22
45
|
const source = decompileObjectBuffer(objectBuffer);
|
|
23
46
|
if (outputPath == null) {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import { argv, exit, stdin, stdout } from "node:process";
|
|
5
|
+
import { extractAliasesFromObjectBuffer } from "../object.mjs";
|
|
6
|
+
|
|
7
|
+
function helpText() {
|
|
8
|
+
return [
|
|
9
|
+
"Extract metadata aliases from a .ko or .klib as a valid k source snippet.",
|
|
10
|
+
"",
|
|
11
|
+
`Usage: ${argv[1]} [ object-file [ k-file ] ]`,
|
|
12
|
+
"",
|
|
13
|
+
"Output is grouped by metadata type (code, then rel), then sorted by alias",
|
|
14
|
+
"name and compiledAt. Earlier definitions of the same alias are commented",
|
|
15
|
+
"out so only the latest one remains active.",
|
|
16
|
+
"",
|
|
17
|
+
"Arguments:",
|
|
18
|
+
" object-file Input .ko or .klib path. Reads binary object data from stdin when omitted.",
|
|
19
|
+
" k-file Output .k path. Writes source to stdout when omitted.",
|
|
20
|
+
"",
|
|
21
|
+
"Options:",
|
|
22
|
+
" -h, --help Show this help.",
|
|
23
|
+
"",
|
|
24
|
+
"Example:",
|
|
25
|
+
` ${argv[1]} core.klib aliases.k`
|
|
26
|
+
].join("\n");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function usage(stream = console.error) {
|
|
30
|
+
stream(helpText());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function readStdinBuffer() {
|
|
34
|
+
const chunks = [];
|
|
35
|
+
for await (const chunk of stdin) {
|
|
36
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
37
|
+
}
|
|
38
|
+
return Buffer.concat(chunks);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const [, , objectPath, outputPath] = argv;
|
|
43
|
+
if (objectPath === "-h" || objectPath === "--help") {
|
|
44
|
+
usage(console.log);
|
|
45
|
+
exit(0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const objectBuffer = objectPath == null ? await readStdinBuffer() : fs.readFileSync(objectPath);
|
|
49
|
+
const source = extractAliasesFromObjectBuffer(objectBuffer);
|
|
50
|
+
if (outputPath == null) {
|
|
51
|
+
stdout.write(source);
|
|
52
|
+
} else {
|
|
53
|
+
fs.writeFileSync(outputPath, source);
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error(error);
|
|
57
|
+
usage();
|
|
58
|
+
exit(1);
|
|
59
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fraczak/k",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.3",
|
|
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
|
@@ -146,21 +146,27 @@ 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
|
-
|
|
150
|
-
state.meta[hash].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(({ source, name, compiledAt }) => ({
|
|
153
|
+
source,
|
|
154
|
+
name,
|
|
155
|
+
...(compiledAt == null ? {} : { compiledAt })
|
|
156
|
+
})));
|
|
151
157
|
}
|
|
152
158
|
}
|
|
153
159
|
|
|
154
|
-
function rememberOrigin(state, hash, name,
|
|
155
|
-
if (!state.meta[hash]) state.meta[hash] = { origins: [] };
|
|
160
|
+
function rememberOrigin(state, hash, name, type, source = "<repl>") {
|
|
161
|
+
if (!state.meta[hash]) state.meta[hash] = { type, origins: [] };
|
|
162
|
+
if (state.meta[hash].type == null) state.meta[hash].type = type;
|
|
156
163
|
const exists = state.meta[hash].origins.some((origin) =>
|
|
157
|
-
origin.name === name &&
|
|
164
|
+
origin.name === name && state.meta[hash].type === type && origin.source === source
|
|
158
165
|
);
|
|
159
166
|
if (!exists) {
|
|
160
167
|
state.meta[hash].origins.push({
|
|
161
168
|
source,
|
|
162
169
|
name,
|
|
163
|
-
kind,
|
|
164
170
|
compiledAt: new Date().toISOString()
|
|
165
171
|
});
|
|
166
172
|
}
|
|
@@ -170,40 +176,47 @@ function recoverAliasesFromMeta(state, lib) {
|
|
|
170
176
|
for (const [hash, entry] of Object.entries(lib.meta || {})) {
|
|
171
177
|
for (const origin of entry?.origins || []) {
|
|
172
178
|
if (!origin?.name || !NAME_RE.test(origin.name)) continue;
|
|
173
|
-
|
|
179
|
+
const type = entry?.type;
|
|
180
|
+
if (type === "code" || (!type && hash in state.codes && !(hash in state.rels))) {
|
|
174
181
|
state.typeAliases[origin.name] = hash;
|
|
175
|
-
} else if (
|
|
182
|
+
} else if (type === "rel" || hash in state.rels) {
|
|
176
183
|
state.relAliases[origin.name] = hash;
|
|
177
184
|
}
|
|
178
185
|
}
|
|
179
186
|
}
|
|
180
187
|
}
|
|
181
188
|
|
|
182
|
-
function mergeLibrary(state, lib, source = "<load>") {
|
|
189
|
+
function mergeLibrary(state, lib, source = "<load>", options = {}) {
|
|
190
|
+
const loadAliases = options.loadAliases ?? true;
|
|
183
191
|
state.codes = { ...state.codes, ...(lib.codes || {}) };
|
|
184
192
|
state.rels = { ...state.rels, ...(lib.rels || {}) };
|
|
185
|
-
state.relAliases = { ...state.relAliases, ...(lib.relAlias || {}) };
|
|
186
193
|
mergeMeta(state, lib.meta);
|
|
187
194
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
195
|
+
if (loadAliases) {
|
|
196
|
+
state.relAliases = { ...state.relAliases, ...(lib.relAlias || {}) };
|
|
197
|
+
for (const [name, hash] of Object.entries(lib.relAlias || {})) {
|
|
198
|
+
if (name !== "__main__" && NAME_RE.test(name) && hash in state.rels) {
|
|
199
|
+
state.relAliases[name] = hash;
|
|
200
|
+
rememberOrigin(state, hash, name, "rel", source);
|
|
201
|
+
}
|
|
192
202
|
}
|
|
203
|
+
recoverAliasesFromMeta(state, lib);
|
|
193
204
|
}
|
|
194
|
-
recoverAliasesFromMeta(state, lib);
|
|
195
205
|
restoreCodes(state);
|
|
196
206
|
}
|
|
197
207
|
|
|
198
208
|
function savedLibrary(state) {
|
|
199
209
|
const meta = cloneJSON(state.meta);
|
|
210
|
+
const now = new Date().toISOString();
|
|
200
211
|
for (const [name, hash] of Object.entries(state.typeAliases)) {
|
|
201
|
-
if (!meta[hash]) meta[hash] = { origins: [] };
|
|
202
|
-
meta[hash].
|
|
212
|
+
if (!meta[hash]) meta[hash] = { type: "code", origins: [] };
|
|
213
|
+
if (meta[hash].type == null) meta[hash].type = "code";
|
|
214
|
+
meta[hash].origins.push({ source: "<repl>", name, compiledAt: now });
|
|
203
215
|
}
|
|
204
216
|
for (const [name, hash] of Object.entries(state.relAliases)) {
|
|
205
|
-
if (!meta[hash]) meta[hash] = { origins: [] };
|
|
206
|
-
meta[hash].
|
|
217
|
+
if (!meta[hash]) meta[hash] = { type: "rel", origins: [] };
|
|
218
|
+
if (meta[hash].type == null) meta[hash].type = "rel";
|
|
219
|
+
meta[hash].origins.push({ source: "<repl>", name, compiledAt: now });
|
|
207
220
|
}
|
|
208
221
|
return {
|
|
209
222
|
format: "k-object",
|
|
@@ -285,8 +298,8 @@ function libraryOriginsFromSource(source, fullSource, lib, options) {
|
|
|
285
298
|
for (const name of typeNames) {
|
|
286
299
|
const hash = annotated.representatives?.[name];
|
|
287
300
|
if (!hash) continue;
|
|
288
|
-
if (!meta[hash]) meta[hash] = { origins: [] };
|
|
289
|
-
meta[hash].origins.push({ source: options.source || null, name,
|
|
301
|
+
if (!meta[hash]) meta[hash] = { type: "code", origins: [] };
|
|
302
|
+
meta[hash].origins.push({ source: options.source || null, name, compiledAt: now });
|
|
290
303
|
}
|
|
291
304
|
|
|
292
305
|
const relAlias = Object.fromEntries(
|
|
@@ -296,8 +309,8 @@ function libraryOriginsFromSource(source, fullSource, lib, options) {
|
|
|
296
309
|
);
|
|
297
310
|
|
|
298
311
|
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,
|
|
312
|
+
if (!meta[hash]) meta[hash] = { type: "rel", origins: [] };
|
|
313
|
+
meta[hash].origins.push({ source: options.source || null, name, compiledAt: now });
|
|
301
314
|
}
|
|
302
315
|
|
|
303
316
|
return { relAlias, meta };
|
|
@@ -401,6 +414,26 @@ function commandArgIndex(arg) {
|
|
|
401
414
|
return trimmed.split(/\s+/).length - 1;
|
|
402
415
|
}
|
|
403
416
|
|
|
417
|
+
function loadPathCompletionStart(argStart, arg) {
|
|
418
|
+
const leadingWhitespace = arg.length - arg.trimStart().length;
|
|
419
|
+
const trimmed = arg.trimStart();
|
|
420
|
+
if (trimmed === "" || !trimmed.startsWith("--no-alias")) {
|
|
421
|
+
if (commandArgIndex(arg) !== 0) return null;
|
|
422
|
+
return argStart + completionTokenStart(arg);
|
|
423
|
+
}
|
|
424
|
+
if (!"--no-alias".startsWith(trimmed) && !trimmed.startsWith("--no-alias ")) {
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
if (trimmed !== "--no-alias" && !trimmed.startsWith("--no-alias ")) {
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
if (trimmed === "--no-alias") return null;
|
|
431
|
+
const restStart = leadingWhitespace + "--no-alias".length;
|
|
432
|
+
const rest = arg.slice(restStart);
|
|
433
|
+
const restLeadingWhitespace = rest.length - rest.trimStart().length;
|
|
434
|
+
return argStart + restStart + restLeadingWhitespace + completionTokenStart(rest.trimStart());
|
|
435
|
+
}
|
|
436
|
+
|
|
404
437
|
function completeCommandArgument(line, state) {
|
|
405
438
|
const body = line.slice(1);
|
|
406
439
|
const firstSpace = body.search(/\s/);
|
|
@@ -410,8 +443,13 @@ function completeCommandArgument(line, state) {
|
|
|
410
443
|
const argStart = 1 + firstSpace + 1;
|
|
411
444
|
const arg = line.slice(argStart);
|
|
412
445
|
|
|
413
|
-
if (PATH_COMMANDS.has(command)
|
|
414
|
-
const tokenStart =
|
|
446
|
+
if (PATH_COMMANDS.has(command)) {
|
|
447
|
+
const tokenStart = command === "load"
|
|
448
|
+
? loadPathCompletionStart(argStart, arg)
|
|
449
|
+
: commandArgIndex(arg) === 0
|
|
450
|
+
? argStart + completionTokenStart(arg)
|
|
451
|
+
: null;
|
|
452
|
+
if (tokenStart == null) return [[], line];
|
|
415
453
|
return completePath(line, tokenStart);
|
|
416
454
|
}
|
|
417
455
|
|
|
@@ -460,6 +498,20 @@ function isEofParseError(error) {
|
|
|
460
498
|
return /got 'EOF'|Unexpected end of input/.test(error.message || "");
|
|
461
499
|
}
|
|
462
500
|
|
|
501
|
+
function parseLoadArgs(arg, usagePrefix = ":") {
|
|
502
|
+
const trimmed = arg.trim();
|
|
503
|
+
if (!trimmed) throw new Error(`${usagePrefix}load requires a file path`);
|
|
504
|
+
if (trimmed === "--no-alias") {
|
|
505
|
+
throw new Error(`${usagePrefix}load --no-alias requires a file path`);
|
|
506
|
+
}
|
|
507
|
+
if (trimmed.startsWith("--no-alias ")) {
|
|
508
|
+
const path = trimmed.slice("--no-alias".length).trim();
|
|
509
|
+
if (!path) throw new Error(`${usagePrefix}load --no-alias requires a file path`);
|
|
510
|
+
return { path, loadAliases: false };
|
|
511
|
+
}
|
|
512
|
+
return { path: arg.trim(), loadAliases: true };
|
|
513
|
+
}
|
|
514
|
+
|
|
463
515
|
function lineForContinuation(line) {
|
|
464
516
|
return line.replace(/\\\s*$/, "");
|
|
465
517
|
}
|
|
@@ -703,28 +755,28 @@ async function evaluateCommand(line, state) {
|
|
|
703
755
|
return [`saved ${path} (${main})`];
|
|
704
756
|
}
|
|
705
757
|
case "load": {
|
|
706
|
-
|
|
707
|
-
if (
|
|
708
|
-
const lib = loadLibrary(decodeLibrary(fs.readFileSync(
|
|
709
|
-
mergeLibrary(state, lib,
|
|
758
|
+
const { path: loadPath, loadAliases } = parseLoadArgs(arg, usagePrefix);
|
|
759
|
+
if (loadPath.endsWith(".klib")) {
|
|
760
|
+
const lib = loadLibrary(decodeLibrary(fs.readFileSync(loadPath)));
|
|
761
|
+
mergeLibrary(state, lib, loadPath, { loadAliases });
|
|
710
762
|
} else {
|
|
711
763
|
restoreCodes(state);
|
|
712
|
-
const source = fs.readFileSync(
|
|
764
|
+
const source = fs.readFileSync(loadPath, "utf8");
|
|
713
765
|
const preamble = aliasPreamble(state);
|
|
714
766
|
const fullSource = [preamble, source].filter(Boolean).join("\n");
|
|
715
767
|
const options = {
|
|
716
|
-
source:
|
|
768
|
+
source: loadPath,
|
|
717
769
|
libraries: [stateLibrary(state)]
|
|
718
770
|
};
|
|
719
771
|
try {
|
|
720
772
|
const lib = compileWithOptionalIdentity(fullSource, options);
|
|
721
773
|
const { relAlias, meta } = libraryOriginsFromSource(source, fullSource, lib, options);
|
|
722
|
-
mergeLibrary(state, { ...lib, relAlias, meta },
|
|
774
|
+
mergeLibrary(state, { ...lib, relAlias, meta }, loadPath, { loadAliases });
|
|
723
775
|
} catch (error) {
|
|
724
776
|
throw remapError(error, preambleLineCount(preamble));
|
|
725
777
|
}
|
|
726
778
|
}
|
|
727
|
-
return [`loaded ${
|
|
779
|
+
return [`loaded ${loadPath}`];
|
|
728
780
|
}
|
|
729
781
|
case "t": {
|
|
730
782
|
if (!arg) throw new Error(`${usagePrefix}t requires a relation name`);
|
|
@@ -759,7 +811,8 @@ function helpText() {
|
|
|
759
811
|
":type name show type definition",
|
|
760
812
|
":codes list type aliases",
|
|
761
813
|
":rels list relation aliases",
|
|
762
|
-
":load file
|
|
814
|
+
":load [--no-alias] file",
|
|
815
|
+
" load .k source or .klib",
|
|
763
816
|
":klib file export state as a library",
|
|
764
817
|
":ko file expr export executable .ko using expr as main",
|
|
765
818
|
":val print current value",
|
package/test-k-object.mjs
CHANGED
|
@@ -6,7 +6,14 @@ 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 {
|
|
9
|
+
import {
|
|
10
|
+
compileObjectBuffer,
|
|
11
|
+
compileLibraryBuffer,
|
|
12
|
+
decodeObject,
|
|
13
|
+
objectToFunction,
|
|
14
|
+
decompileObjectBuffer,
|
|
15
|
+
extractAliasesFromObject
|
|
16
|
+
} from "./object.mjs";
|
|
10
17
|
|
|
11
18
|
const value = parseValue("{a:{b:x,c:{}}}");
|
|
12
19
|
const wire = encodeToWire(value, null);
|
|
@@ -71,4 +78,73 @@ const sccSource = decompileObjectBuffer(compileObjectBuffer("c = {}; b = c |x; a
|
|
|
71
78
|
assert.match(sccSource, /----- rels -----\nPQgV = .+\n\naQAD = .+\n\nN9UH = /s);
|
|
72
79
|
assert.deepEqual(k.compile(sccSource)(new Product({})).toJSON(), { y: "x" });
|
|
73
80
|
|
|
81
|
+
const library = decodeObject(compileLibraryBuffer("$ nat = <{} zero, nat succ>;\nsucc = |succ;\n", { source: "defs-only.k" }));
|
|
82
|
+
assert.equal(library.main, null);
|
|
83
|
+
assert(Object.values(library.meta).some(({ type, origins }) =>
|
|
84
|
+
type === "code" &&
|
|
85
|
+
origins.some((origin) =>
|
|
86
|
+
origin.source === "defs-only.k" &&
|
|
87
|
+
origin.name === "nat" &&
|
|
88
|
+
JSON.stringify(Object.keys(origin).sort()) === JSON.stringify(["compiledAt", "name", "source"]) &&
|
|
89
|
+
typeof origin.compiledAt === "string"
|
|
90
|
+
)
|
|
91
|
+
));
|
|
92
|
+
assert(Object.values(library.meta).some(({ type, origins }) =>
|
|
93
|
+
type === "rel" &&
|
|
94
|
+
origins.some((origin) =>
|
|
95
|
+
origin.source === "defs-only.k" &&
|
|
96
|
+
origin.name === "succ" &&
|
|
97
|
+
JSON.stringify(Object.keys(origin).sort()) === JSON.stringify(["compiledAt", "name", "source"]) &&
|
|
98
|
+
typeof origin.compiledAt === "string"
|
|
99
|
+
)
|
|
100
|
+
));
|
|
101
|
+
|
|
102
|
+
const derivedLibrary = decodeObject(compileLibraryBuffer("other = |other;\n", {
|
|
103
|
+
source: "derived.k",
|
|
104
|
+
libraries: [library]
|
|
105
|
+
}));
|
|
106
|
+
const derivedAliases = extractAliasesFromObject(derivedLibrary);
|
|
107
|
+
assert.match(derivedAliases, /^\$ nat = @/m);
|
|
108
|
+
assert.match(derivedAliases, /^succ = @/m);
|
|
109
|
+
assert.match(derivedAliases, /^other = @/m);
|
|
110
|
+
assert(derivedAliases.indexOf("$ nat = @") < derivedAliases.indexOf("other = @"));
|
|
111
|
+
assert(derivedAliases.indexOf("other = @") < derivedAliases.indexOf("succ = @"));
|
|
112
|
+
|
|
113
|
+
const aliasSnippet = extractAliasesFromObject({
|
|
114
|
+
format: "k-object",
|
|
115
|
+
version: 2,
|
|
116
|
+
codes: {},
|
|
117
|
+
rels: {},
|
|
118
|
+
meta: {
|
|
119
|
+
"@relB": {
|
|
120
|
+
type: "rel",
|
|
121
|
+
origins: [
|
|
122
|
+
{ source: "new.k", name: "times", compiledAt: "2026-04-12T06:08:10.390Z" },
|
|
123
|
+
{ source: "core.k", name: "times", compiledAt: "2026-03-09T06:17:23.390Z" },
|
|
124
|
+
{ source: "other.k", name: "plus", compiledAt: "2026-05-17T06:10:23.390Z" }
|
|
125
|
+
]
|
|
126
|
+
},
|
|
127
|
+
"@codeB": {
|
|
128
|
+
type: "code",
|
|
129
|
+
origins: [
|
|
130
|
+
{ source: "z.k", name: "edges", compiledAt: "2026-03-09T06:17:23.390Z" }
|
|
131
|
+
]
|
|
132
|
+
},
|
|
133
|
+
"@codeA": {
|
|
134
|
+
type: "code",
|
|
135
|
+
origins: [
|
|
136
|
+
{ source: "a.k", name: "bits", compiledAt: "2026-05-17T06:10:23.390Z" }
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
main: null
|
|
141
|
+
});
|
|
142
|
+
assert.deepEqual(aliasSnippet.split("\n").filter(Boolean), [
|
|
143
|
+
'$ bits = @codeA; # {"source":"a.k","compiledAt":"2026-05-17T06:10:23.390Z"}',
|
|
144
|
+
'$ edges = @codeB; # {"source":"z.k","compiledAt":"2026-03-09T06:17:23.390Z"}',
|
|
145
|
+
'plus = @relB; # {"source":"other.k","compiledAt":"2026-05-17T06:10:23.390Z"}',
|
|
146
|
+
'# times = @relB; # {"source":"core.k","compiledAt":"2026-03-09T06:17:23.390Z"}',
|
|
147
|
+
'times = @relB; # {"source":"new.k","compiledAt":"2026-04-12T06:08:10.390Z"}'
|
|
148
|
+
]);
|
|
149
|
+
|
|
74
150
|
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, []);
|