@fraczak/k 6.7.0 → 6.9.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.
- package/CHANGELOG.md +10 -0
- package/DOCS/CODECS.md +80 -181
- package/DOCS/CODEC_ARCHITECTURE.md +194 -0
- package/DOCS/REPL.md +49 -60
- package/Examples/arithmetics.k +1 -0
- package/Examples/poly.k +46 -0
- package/TODO.md +112 -0
- package/browser/repl-browser.mjs +112 -733
- package/browser/shims/process.mjs +7 -0
- package/browser/tree-view.mjs +5 -2
- package/browser/vfs-data.mjs +14 -12
- package/codecs/README.md +7 -0
- package/codecs/ieee.mjs +50 -75
- package/codecs/int.mjs +83 -62
- package/codecs/json-codec.mjs +5 -77
- package/codecs/json.mjs +12 -56
- package/codecs/k-parse.mjs +3 -19
- package/codecs/runtime/codec-sdk.mjs +94 -0
- package/codecs/runtime/pattern-k.mjs +45 -0
- package/codecs/string-codec.mjs +48 -74
- package/codecs/unit.mjs +13 -61
- package/codecs/utf16.mjs +13 -56
- package/codecs/utf8.mjs +11 -55
- package/package.json +1 -1
- package/patterns/from-k.mjs +2 -22
- package/repl-codecs.mjs +78 -184
- package/repl.mjs +168 -226
- package/scripts/build-repl-html.mjs +273 -424
- package/scripts/run-tests.mjs +1 -0
- package/tests/test-codecs.mjs +186 -0
- package/tests/test-poly.mjs +44 -0
- package/tests/test-repl-html.mjs +190 -53
- package/tests/test-repl.mjs +150 -114
- package/tests/test-structural-values.mjs +17 -0
- package/tests/test-tree-view.mjs +3 -0
- package/codecs/example-pipeline.mjs +0 -163
- package/codecs/runtime/ieee-pattern.mjs +0 -30
- package/codecs/runtime/test-codec.mjs +0 -89
- package/codecs/runtime/typeFromValue.mjs +0 -34
package/repl-codecs.mjs
CHANGED
|
@@ -6,26 +6,7 @@ import { encodeToWire, decodeWire } from "./codecs/runtime/prefix-codec.mjs";
|
|
|
6
6
|
import { deriveClosedPattern } from "./codecs/runtime/codec.mjs";
|
|
7
7
|
import { patternToPropertyList } from "./codecs/runtime/pattern-json.mjs";
|
|
8
8
|
import { finalize } from "./codes.mjs";
|
|
9
|
-
import * as intCodec from "./codecs/int.mjs";
|
|
10
|
-
import * as utf8Codec from "./codecs/utf8.mjs";
|
|
11
|
-
import * as jsonCodec from "./codecs/json.mjs";
|
|
12
|
-
import * as ieeeCodec from "./codecs/ieee.mjs";
|
|
13
|
-
import * as unitCodec from "./codecs/unit.mjs";
|
|
14
9
|
|
|
15
|
-
const BUILTIN_CODECS = {
|
|
16
|
-
int: intCodec,
|
|
17
|
-
utf8: utf8Codec,
|
|
18
|
-
json: jsonCodec,
|
|
19
|
-
ieee: ieeeCodec,
|
|
20
|
-
unit: unitCodec
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const UNIVERSAL_CODE = "*";
|
|
24
|
-
const NAME_RE = /^[a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*$/;
|
|
25
|
-
|
|
26
|
-
function isCanonicalCodeName(name) {
|
|
27
|
-
return typeof name === "string" && name.startsWith("@");
|
|
28
|
-
}
|
|
29
10
|
|
|
30
11
|
function closedPatternToCodeHash(pattern) {
|
|
31
12
|
if (!Array.isArray(pattern) || pattern.length === 0) {
|
|
@@ -72,40 +53,39 @@ function codeHashToPattern(codeHash, findCode) {
|
|
|
72
53
|
return patternToPropertyList(deriveClosedPattern(codeHash, code, resolveType));
|
|
73
54
|
}
|
|
74
55
|
|
|
56
|
+
function valueForPattern(value, pattern = null) {
|
|
57
|
+
if (pattern) {
|
|
58
|
+
return decodeWire(encodeToWire(value, pattern)).value;
|
|
59
|
+
}
|
|
60
|
+
return decodeWire(encodeToWire(value)).value;
|
|
61
|
+
}
|
|
62
|
+
|
|
75
63
|
function valueForCode(value, codeHash, findCode) {
|
|
76
64
|
const pattern = codeHashToPattern(codeHash, findCode);
|
|
77
|
-
return
|
|
65
|
+
return valueForPattern(value, pattern);
|
|
78
66
|
}
|
|
79
67
|
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const codeHash = closedPatternToCodeHash(pattern);
|
|
88
|
-
if (!codeHash) throw new Error(`Codec '${name}' pattern ${index} must be a closed property-list pattern`);
|
|
89
|
-
return codeHash;
|
|
90
|
-
});
|
|
91
|
-
if (!name || typeof name !== "string") {
|
|
92
|
-
throw new Error("Codec module must export a string name");
|
|
93
|
-
}
|
|
94
|
-
if (!universal && (!Array.isArray(codes) || codes.length === 0)) {
|
|
95
|
-
throw new Error(`Codec '${name}' must export a non-empty codes array`);
|
|
96
|
-
}
|
|
97
|
-
if (codec.parse != null && typeof codec.parse !== "function") {
|
|
98
|
-
throw new Error(`Codec '${name}' parse export must be a function`);
|
|
99
|
-
}
|
|
100
|
-
if (codec.print != null && typeof codec.print !== "function") {
|
|
101
|
-
throw new Error(`Codec '${name}' print export must be a function`);
|
|
68
|
+
function ensureEnveloped(value, codec = null) {
|
|
69
|
+
if (!value || typeof value !== "object") return value;
|
|
70
|
+
if (value.pattern) return value;
|
|
71
|
+
if (codec?.pattern) {
|
|
72
|
+
try {
|
|
73
|
+
return decodeWire(encodeToWire(value, codec.pattern)).value;
|
|
74
|
+
} catch {}
|
|
102
75
|
}
|
|
76
|
+
try {
|
|
77
|
+
return decodeWire(encodeToWire(value)).value;
|
|
78
|
+
} catch {}
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function normalizeCodecModule(mod, name) {
|
|
83
|
+
const codec = mod.replCodec || mod.codec || mod.default || mod;
|
|
103
84
|
return {
|
|
104
85
|
name,
|
|
105
|
-
codes: universal ? [UNIVERSAL_CODE] : codes,
|
|
106
|
-
universal,
|
|
107
86
|
parse: codec.parse,
|
|
108
|
-
print: codec.print
|
|
87
|
+
print: codec.print,
|
|
88
|
+
pattern: codec.pattern
|
|
109
89
|
};
|
|
110
90
|
}
|
|
111
91
|
|
|
@@ -114,164 +94,77 @@ function codecStore(state) {
|
|
|
114
94
|
return state.codecs;
|
|
115
95
|
}
|
|
116
96
|
|
|
117
|
-
function
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
97
|
+
function registerCodec(state, rawCodec, filePath) {
|
|
98
|
+
const name = path.basename(filePath);
|
|
99
|
+
const codec = normalizeCodecModule(rawCodec, name);
|
|
100
|
+
const store = codecStore(state);
|
|
101
|
+
store[codec.name] = {
|
|
102
|
+
...codec,
|
|
103
|
+
source: filePath
|
|
104
|
+
};
|
|
105
|
+
return [{ name: codec.name, source: filePath }];
|
|
125
106
|
}
|
|
126
107
|
|
|
127
|
-
function
|
|
128
|
-
const registered = [];
|
|
108
|
+
function unregisterCodec(state, codecName) {
|
|
129
109
|
const store = codecStore(state);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (!universal && !isCanonicalCodeName(codeHash)) {
|
|
134
|
-
throw new Error(`Codec '${codec.name}' has non-canonical code '${codeHash}'`);
|
|
135
|
-
}
|
|
136
|
-
if (!store[codeHash]) store[codeHash] = [];
|
|
137
|
-
const entry = {
|
|
138
|
-
name: codec.name,
|
|
139
|
-
source,
|
|
140
|
-
universal,
|
|
141
|
-
parse: codec.parse,
|
|
142
|
-
print: codec.print
|
|
143
|
-
};
|
|
144
|
-
const existingIndex = store[codeHash].findIndex((existing) =>
|
|
145
|
-
existing.name === entry.name && existing.source === entry.source
|
|
146
|
-
);
|
|
147
|
-
if (existingIndex === -1) {
|
|
148
|
-
store[codeHash].push(entry);
|
|
149
|
-
} else {
|
|
150
|
-
store[codeHash][existingIndex] = entry;
|
|
151
|
-
}
|
|
152
|
-
if (!universal && isCanonicalCodeName(codeHash) && state?.typeAliases && codec.name && NAME_RE.test(codec.name)) {
|
|
153
|
-
state.typeAliases[codec.name] = codeHash;
|
|
154
|
-
}
|
|
155
|
-
registered.push({ codeHash, ...entry });
|
|
156
|
-
}
|
|
157
|
-
return registered;
|
|
110
|
+
if (!store[codecName]) return 0;
|
|
111
|
+
delete store[codecName];
|
|
112
|
+
return 1;
|
|
158
113
|
}
|
|
159
114
|
|
|
160
|
-
function
|
|
115
|
+
function listCodecs(state) {
|
|
161
116
|
const store = codecStore(state);
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (remaining.length !== codecs.length) {
|
|
166
|
-
removedCount += codecs.length - remaining.length;
|
|
167
|
-
if (remaining.length === 0) {
|
|
168
|
-
delete store[codeHash];
|
|
169
|
-
} else {
|
|
170
|
-
store[codeHash] = remaining;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
return removedCount;
|
|
117
|
+
const entries = Object.values(store).sort((a, b) => a.name.localeCompare(b.name));
|
|
118
|
+
if (entries.length === 0) return "(none)";
|
|
119
|
+
return entries.map((c) => `${c.name}${c.source ? ` (${c.source})` : ""}`).join("\n");
|
|
175
120
|
}
|
|
176
121
|
|
|
177
|
-
function
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
if (BUILTIN_CODECS[base]) return { name: base, mod: BUILTIN_CODECS[base] };
|
|
181
|
-
return null;
|
|
122
|
+
function codecNames(state) {
|
|
123
|
+
const store = codecStore(state);
|
|
124
|
+
return Object.keys(store).sort();
|
|
182
125
|
}
|
|
183
126
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const codec = normalizeCodecModule(builtin.mod, builtin.name);
|
|
188
|
-
return registerCodec(state, codec, "built-in");
|
|
127
|
+
function resolveCodec(state, codecName, capability = null) {
|
|
128
|
+
if (!codecName) {
|
|
129
|
+
throw new Error("Codec name is required");
|
|
189
130
|
}
|
|
131
|
+
const store = codecStore(state);
|
|
132
|
+
const codec = store[codecName];
|
|
133
|
+
if (!codec) {
|
|
134
|
+
throw new Error(`Codec '${codecName}' is not loaded`);
|
|
135
|
+
}
|
|
136
|
+
if (capability && typeof codec[capability] !== "function") {
|
|
137
|
+
throw new Error(`Codec '${codecName}' does not support ${capability}`);
|
|
138
|
+
}
|
|
139
|
+
return codec;
|
|
140
|
+
}
|
|
190
141
|
|
|
191
|
-
|
|
192
|
-
let
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
} catch {}
|
|
198
|
-
|
|
199
|
-
const resolved = path.resolve(filePath);
|
|
200
|
-
if (!source && fs.existsSync(resolved)) {
|
|
142
|
+
async function loadCodecModule(state, filePath) {
|
|
143
|
+
let mod;
|
|
144
|
+
if (typeof process !== "undefined" && process?.versions?.node) {
|
|
145
|
+
const fullPath = path.resolve(filePath);
|
|
146
|
+
const url = pathToFileURL(fullPath);
|
|
201
147
|
try {
|
|
202
|
-
|
|
148
|
+
const stats = fs.statSync(fullPath);
|
|
149
|
+
if (stats?.mtimeMs) url.searchParams.set("mtime", String(stats.mtimeMs));
|
|
203
150
|
} catch {}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
151
|
+
mod = await import(url.href);
|
|
152
|
+
} else {
|
|
153
|
+
const rawSource = fs.readFileSync(filePath, "utf8");
|
|
154
|
+
const source = rawSource.replace(/^#![^\n]*\n/, "");
|
|
155
|
+
const blob = new Blob([source], { type: "text/javascript" });
|
|
156
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
208
157
|
try {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (stats?.mtimeMs) url.searchParams.set("mtime", String(stats.mtimeMs));
|
|
213
|
-
} catch {}
|
|
214
|
-
const mod = await import(url.href);
|
|
215
|
-
const codec = normalizeCodecModule(mod, path.basename(resolved, path.extname(resolved)));
|
|
216
|
-
return registerCodec(state, codec, resolved);
|
|
217
|
-
} catch (nodeErr) {
|
|
218
|
-
if (!source) throw nodeErr;
|
|
158
|
+
mod = await import(blobUrl);
|
|
159
|
+
} finally {
|
|
160
|
+
URL.revokeObjectURL(blobUrl);
|
|
219
161
|
}
|
|
220
162
|
}
|
|
221
163
|
|
|
222
|
-
|
|
223
|
-
throw new Error(`Cannot find codec file '${filePath}'`);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Universal data URL loader (works in browser & Node, and in VFS)
|
|
227
|
-
const sanitized = source.replace(
|
|
228
|
-
/import\s+[^;]*from\s+['"][^'"]*['"];?/g,
|
|
229
|
-
"const { Value, isProduct, isVariant, NODE_KIND } = globalThis;"
|
|
230
|
-
);
|
|
231
|
-
const dataUrl = "data:text/javascript;charset=utf-8," + encodeURIComponent(sanitized);
|
|
232
|
-
const mod = await import(dataUrl);
|
|
233
|
-
const codec = normalizeCodecModule(mod, path.basename(filePath, path.extname(filePath)));
|
|
234
|
-
return registerCodec(state, codec, filePath);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function listCodecs(state) {
|
|
238
|
-
const entries = Object.entries(codecStore(state))
|
|
239
|
-
.flatMap(([codeHash, codecs]) => codecs.map((codec) => ({ codeHash, ...codec })))
|
|
240
|
-
.sort((a, b) => a.name.localeCompare(b.name) || a.codeHash.localeCompare(b.codeHash));
|
|
241
|
-
if (entries.length === 0) return "(none)";
|
|
242
|
-
return entries.map((entry) => `${entry.name} ${entry.codeHash === UNIVERSAL_CODE ? "all" : entry.codeHash}${entry.source ? ` (${entry.source})` : ""}`).join("\n");
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function codecNames(state, codeHash = null) {
|
|
246
|
-
const store = codecStore(state);
|
|
247
|
-
const codecs = codeHash
|
|
248
|
-
? [...(store[codeHash] || []), ...(store[UNIVERSAL_CODE] || [])]
|
|
249
|
-
: Object.values(store).flat();
|
|
250
|
-
return [...new Set(codecs.map((codec) => codec.name))].sort();
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function resolveCodec(state, codeHash, codecName = null, capability = null) {
|
|
254
|
-
const store = codecStore(state);
|
|
255
|
-
const matchesCodec = (codec) =>
|
|
256
|
-
(codecName == null || codec.name === codecName) &&
|
|
257
|
-
(capability == null || typeof codec[capability] === "function");
|
|
258
|
-
const exactMatches = (store[codeHash] || []).filter(matchesCodec);
|
|
259
|
-
const universalMatches = (store[UNIVERSAL_CODE] || []).filter(matchesCodec);
|
|
260
|
-
const matches = codecName == null && exactMatches.length > 0
|
|
261
|
-
? exactMatches
|
|
262
|
-
: [...exactMatches, ...universalMatches];
|
|
263
|
-
if (matches.length === 0) {
|
|
264
|
-
const suffix = codecName ? ` named '${codecName}'` : "";
|
|
265
|
-
throw new Error(`No ${capability || "usable"} codec${suffix} for ${codeHash}`);
|
|
266
|
-
}
|
|
267
|
-
if (matches.length > 1 && codecName == null) {
|
|
268
|
-
throw new Error(`Multiple codecs for ${codeHash}; specify one of: ${matches.map((codec) => codec.name).join(", ")}`);
|
|
269
|
-
}
|
|
270
|
-
return matches[0];
|
|
164
|
+
return registerCodec(state, mod, filePath);
|
|
271
165
|
}
|
|
272
166
|
|
|
273
167
|
export {
|
|
274
|
-
BUILTIN_CODECS,
|
|
275
168
|
codecNames,
|
|
276
169
|
codeHashToPattern,
|
|
277
170
|
listCodecs,
|
|
@@ -281,6 +174,7 @@ export {
|
|
|
281
174
|
resolveCodec,
|
|
282
175
|
unregisterCodec,
|
|
283
176
|
closedPatternToCodeHash,
|
|
284
|
-
|
|
285
|
-
|
|
177
|
+
valueForCode,
|
|
178
|
+
valueForPattern,
|
|
179
|
+
ensureEnveloped
|
|
286
180
|
};
|