@fraczak/k 6.0.2 → 6.0.4
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/KIR_SPECIALIZATION_DESIGN.md +458 -0
- package/DOCS/OBJECT_FILE_AND_PATTERN.md +133 -43
- package/DOCS/REPL.md +10 -7
- package/README.md +16 -6
- 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.json +1 -0
- package/core.klib +1 -0
- package/core.klib~ +0 -0
- package/export.mjs +1 -1
- package/ieee.klib +0 -0
- package/index.mjs +1 -1
- package/kkk.k +197 -0
- package/kkk.klib +0 -0
- package/kkk.klib~ +0 -0
- package/object.mjs +452 -57
- package/objects/README.md +28 -1
- package/objects/compile-lib.mjs +27 -2
- package/objects/compile.mjs +27 -2
- package/objects/decompile.mjs +25 -2
- package/objects/extract-aliases.mjs +59 -0
- package/p.json +5475 -0
- package/package.json +2 -1
- package/repl2.mjs +106 -38
- package/run.mjs +172 -2
- package/test-k-object.mjs +129 -2
- package/test-repl2.mjs +10 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fraczak/k",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.4",
|
|
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
|
@@ -19,7 +19,8 @@ import {
|
|
|
19
19
|
encodeObject,
|
|
20
20
|
encodeLibrary,
|
|
21
21
|
hydrateObject,
|
|
22
|
-
loadLibrary
|
|
22
|
+
loadLibrary,
|
|
23
|
+
prettyRelation
|
|
23
24
|
} from "./object.mjs";
|
|
24
25
|
import { propertyListToFilter, valueToK, valueWithEnvelopeToK } from "./codecs/runtime/show-value.mjs";
|
|
25
26
|
|
|
@@ -58,7 +59,6 @@ function createState() {
|
|
|
58
59
|
function stateLibrary(state) {
|
|
59
60
|
return {
|
|
60
61
|
format: "k-object",
|
|
61
|
-
version: 2,
|
|
62
62
|
codes: state.codes,
|
|
63
63
|
rels: state.rels,
|
|
64
64
|
relAlias: state.relAliases,
|
|
@@ -146,21 +146,23 @@ 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((origin) => ({ ...origin })));
|
|
151
153
|
}
|
|
152
154
|
}
|
|
153
155
|
|
|
154
|
-
function rememberOrigin(state, hash, name,
|
|
155
|
-
if (!state.meta[hash]) state.meta[hash] = { origins: [] };
|
|
156
|
+
function rememberOrigin(state, hash, name, type, source = "<repl>") {
|
|
157
|
+
if (!state.meta[hash]) state.meta[hash] = { type, origins: [] };
|
|
158
|
+
if (state.meta[hash].type == null) state.meta[hash].type = type;
|
|
156
159
|
const exists = state.meta[hash].origins.some((origin) =>
|
|
157
|
-
origin.name === name &&
|
|
160
|
+
origin.name === name && state.meta[hash].type === type && origin.source === source
|
|
158
161
|
);
|
|
159
162
|
if (!exists) {
|
|
160
163
|
state.meta[hash].origins.push({
|
|
161
164
|
source,
|
|
162
165
|
name,
|
|
163
|
-
kind,
|
|
164
166
|
compiledAt: new Date().toISOString()
|
|
165
167
|
});
|
|
166
168
|
}
|
|
@@ -170,44 +172,50 @@ function recoverAliasesFromMeta(state, lib) {
|
|
|
170
172
|
for (const [hash, entry] of Object.entries(lib.meta || {})) {
|
|
171
173
|
for (const origin of entry?.origins || []) {
|
|
172
174
|
if (!origin?.name || !NAME_RE.test(origin.name)) continue;
|
|
173
|
-
|
|
175
|
+
const type = entry?.type;
|
|
176
|
+
if (type === "code" || (!type && hash in state.codes && !(hash in state.rels))) {
|
|
174
177
|
state.typeAliases[origin.name] = hash;
|
|
175
|
-
} else if (
|
|
178
|
+
} else if (type === "rel" || hash in state.rels) {
|
|
176
179
|
state.relAliases[origin.name] = hash;
|
|
177
180
|
}
|
|
178
181
|
}
|
|
179
182
|
}
|
|
180
183
|
}
|
|
181
184
|
|
|
182
|
-
function mergeLibrary(state, lib, source = "<load>") {
|
|
185
|
+
function mergeLibrary(state, lib, source = "<load>", options = {}) {
|
|
186
|
+
const loadAliases = options.loadAliases ?? true;
|
|
183
187
|
state.codes = { ...state.codes, ...(lib.codes || {}) };
|
|
184
188
|
state.rels = { ...state.rels, ...(lib.rels || {}) };
|
|
185
|
-
state.relAliases = { ...state.relAliases, ...(lib.relAlias || {}) };
|
|
186
189
|
mergeMeta(state, lib.meta);
|
|
187
190
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
191
|
+
if (loadAliases) {
|
|
192
|
+
state.relAliases = { ...state.relAliases, ...(lib.relAlias || {}) };
|
|
193
|
+
for (const [name, hash] of Object.entries(lib.relAlias || {})) {
|
|
194
|
+
if (name !== "__main__" && NAME_RE.test(name) && hash in state.rels) {
|
|
195
|
+
state.relAliases[name] = hash;
|
|
196
|
+
rememberOrigin(state, hash, name, "rel", source);
|
|
197
|
+
}
|
|
192
198
|
}
|
|
199
|
+
recoverAliasesFromMeta(state, lib);
|
|
193
200
|
}
|
|
194
|
-
recoverAliasesFromMeta(state, lib);
|
|
195
201
|
restoreCodes(state);
|
|
196
202
|
}
|
|
197
203
|
|
|
198
204
|
function savedLibrary(state) {
|
|
199
205
|
const meta = cloneJSON(state.meta);
|
|
206
|
+
const now = new Date().toISOString();
|
|
200
207
|
for (const [name, hash] of Object.entries(state.typeAliases)) {
|
|
201
|
-
if (!meta[hash]) meta[hash] = { origins: [] };
|
|
202
|
-
meta[hash].
|
|
208
|
+
if (!meta[hash]) meta[hash] = { type: "code", origins: [] };
|
|
209
|
+
if (meta[hash].type == null) meta[hash].type = "code";
|
|
210
|
+
meta[hash].origins.push({ source: "<repl>", name, compiledAt: now });
|
|
203
211
|
}
|
|
204
212
|
for (const [name, hash] of Object.entries(state.relAliases)) {
|
|
205
|
-
if (!meta[hash]) meta[hash] = { origins: [] };
|
|
206
|
-
meta[hash].
|
|
213
|
+
if (!meta[hash]) meta[hash] = { type: "rel", origins: [] };
|
|
214
|
+
if (meta[hash].type == null) meta[hash].type = "rel";
|
|
215
|
+
meta[hash].origins.push({ source: "<repl>", name, compiledAt: now });
|
|
207
216
|
}
|
|
208
217
|
return {
|
|
209
218
|
format: "k-object",
|
|
210
|
-
version: 2,
|
|
211
219
|
codes: state.codes,
|
|
212
220
|
rels: state.rels,
|
|
213
221
|
relAlias: state.relAliases,
|
|
@@ -276,6 +284,16 @@ function userDefinedNames(source) {
|
|
|
276
284
|
};
|
|
277
285
|
}
|
|
278
286
|
|
|
287
|
+
function originFromSourceNode(source, name, compiledAt, node) {
|
|
288
|
+
return {
|
|
289
|
+
source,
|
|
290
|
+
name,
|
|
291
|
+
compiledAt,
|
|
292
|
+
...(node?.start ? { start: node.start } : {}),
|
|
293
|
+
...(node?.end ? { end: node.end } : {})
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
279
297
|
function libraryOriginsFromSource(source, fullSource, lib, options) {
|
|
280
298
|
const { typeNames, relNames } = userDefinedNames(source);
|
|
281
299
|
const annotated = annotateWithOptionalIdentity(fullSource, options);
|
|
@@ -285,8 +303,13 @@ function libraryOriginsFromSource(source, fullSource, lib, options) {
|
|
|
285
303
|
for (const name of typeNames) {
|
|
286
304
|
const hash = annotated.representatives?.[name];
|
|
287
305
|
if (!hash) continue;
|
|
288
|
-
if (!meta[hash]) meta[hash] = { origins: [] };
|
|
289
|
-
meta[hash].origins.push(
|
|
306
|
+
if (!meta[hash]) meta[hash] = { type: "code", origins: [] };
|
|
307
|
+
meta[hash].origins.push(originFromSourceNode(
|
|
308
|
+
options.source || null,
|
|
309
|
+
name,
|
|
310
|
+
now,
|
|
311
|
+
annotated.sourceDefs?.codes?.[name]
|
|
312
|
+
));
|
|
290
313
|
}
|
|
291
314
|
|
|
292
315
|
const relAlias = Object.fromEntries(
|
|
@@ -296,8 +319,13 @@ function libraryOriginsFromSource(source, fullSource, lib, options) {
|
|
|
296
319
|
);
|
|
297
320
|
|
|
298
321
|
for (const [name, hash] of Object.entries(relAlias)) {
|
|
299
|
-
if (!meta[hash]) meta[hash] = { origins: [] };
|
|
300
|
-
meta[hash].origins.push(
|
|
322
|
+
if (!meta[hash]) meta[hash] = { type: "rel", origins: [] };
|
|
323
|
+
meta[hash].origins.push(originFromSourceNode(
|
|
324
|
+
options.source || null,
|
|
325
|
+
name,
|
|
326
|
+
now,
|
|
327
|
+
annotated.rels?.[name]?.def
|
|
328
|
+
));
|
|
301
329
|
}
|
|
302
330
|
|
|
303
331
|
return { relAlias, meta };
|
|
@@ -401,6 +429,26 @@ function commandArgIndex(arg) {
|
|
|
401
429
|
return trimmed.split(/\s+/).length - 1;
|
|
402
430
|
}
|
|
403
431
|
|
|
432
|
+
function loadPathCompletionStart(argStart, arg) {
|
|
433
|
+
const leadingWhitespace = arg.length - arg.trimStart().length;
|
|
434
|
+
const trimmed = arg.trimStart();
|
|
435
|
+
if (trimmed === "" || !trimmed.startsWith("--no-alias")) {
|
|
436
|
+
if (commandArgIndex(arg) !== 0) return null;
|
|
437
|
+
return argStart + completionTokenStart(arg);
|
|
438
|
+
}
|
|
439
|
+
if (!"--no-alias".startsWith(trimmed) && !trimmed.startsWith("--no-alias ")) {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
if (trimmed !== "--no-alias" && !trimmed.startsWith("--no-alias ")) {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
if (trimmed === "--no-alias") return null;
|
|
446
|
+
const restStart = leadingWhitespace + "--no-alias".length;
|
|
447
|
+
const rest = arg.slice(restStart);
|
|
448
|
+
const restLeadingWhitespace = rest.length - rest.trimStart().length;
|
|
449
|
+
return argStart + restStart + restLeadingWhitespace + completionTokenStart(rest.trimStart());
|
|
450
|
+
}
|
|
451
|
+
|
|
404
452
|
function completeCommandArgument(line, state) {
|
|
405
453
|
const body = line.slice(1);
|
|
406
454
|
const firstSpace = body.search(/\s/);
|
|
@@ -410,8 +458,13 @@ function completeCommandArgument(line, state) {
|
|
|
410
458
|
const argStart = 1 + firstSpace + 1;
|
|
411
459
|
const arg = line.slice(argStart);
|
|
412
460
|
|
|
413
|
-
if (PATH_COMMANDS.has(command)
|
|
414
|
-
const tokenStart =
|
|
461
|
+
if (PATH_COMMANDS.has(command)) {
|
|
462
|
+
const tokenStart = command === "load"
|
|
463
|
+
? loadPathCompletionStart(argStart, arg)
|
|
464
|
+
: commandArgIndex(arg) === 0
|
|
465
|
+
? argStart + completionTokenStart(arg)
|
|
466
|
+
: null;
|
|
467
|
+
if (tokenStart == null) return [[], line];
|
|
415
468
|
return completePath(line, tokenStart);
|
|
416
469
|
}
|
|
417
470
|
|
|
@@ -460,6 +513,20 @@ function isEofParseError(error) {
|
|
|
460
513
|
return /got 'EOF'|Unexpected end of input/.test(error.message || "");
|
|
461
514
|
}
|
|
462
515
|
|
|
516
|
+
function parseLoadArgs(arg, usagePrefix = ":") {
|
|
517
|
+
const trimmed = arg.trim();
|
|
518
|
+
if (!trimmed) throw new Error(`${usagePrefix}load requires a file path`);
|
|
519
|
+
if (trimmed === "--no-alias") {
|
|
520
|
+
throw new Error(`${usagePrefix}load --no-alias requires a file path`);
|
|
521
|
+
}
|
|
522
|
+
if (trimmed.startsWith("--no-alias ")) {
|
|
523
|
+
const path = trimmed.slice("--no-alias".length).trim();
|
|
524
|
+
if (!path) throw new Error(`${usagePrefix}load --no-alias requires a file path`);
|
|
525
|
+
return { path, loadAliases: false };
|
|
526
|
+
}
|
|
527
|
+
return { path: arg.trim(), loadAliases: true };
|
|
528
|
+
}
|
|
529
|
+
|
|
463
530
|
function lineForContinuation(line) {
|
|
464
531
|
return line.replace(/\\\s*$/, "");
|
|
465
532
|
}
|
|
@@ -703,28 +770,28 @@ async function evaluateCommand(line, state) {
|
|
|
703
770
|
return [`saved ${path} (${main})`];
|
|
704
771
|
}
|
|
705
772
|
case "load": {
|
|
706
|
-
|
|
707
|
-
if (
|
|
708
|
-
const lib = loadLibrary(decodeLibrary(fs.readFileSync(
|
|
709
|
-
mergeLibrary(state, lib,
|
|
773
|
+
const { path: loadPath, loadAliases } = parseLoadArgs(arg, usagePrefix);
|
|
774
|
+
if (loadPath.endsWith(".klib")) {
|
|
775
|
+
const lib = loadLibrary(decodeLibrary(fs.readFileSync(loadPath)));
|
|
776
|
+
mergeLibrary(state, lib, loadPath, { loadAliases });
|
|
710
777
|
} else {
|
|
711
778
|
restoreCodes(state);
|
|
712
|
-
const source = fs.readFileSync(
|
|
779
|
+
const source = fs.readFileSync(loadPath, "utf8");
|
|
713
780
|
const preamble = aliasPreamble(state);
|
|
714
781
|
const fullSource = [preamble, source].filter(Boolean).join("\n");
|
|
715
782
|
const options = {
|
|
716
|
-
source:
|
|
783
|
+
source: loadPath,
|
|
717
784
|
libraries: [stateLibrary(state)]
|
|
718
785
|
};
|
|
719
786
|
try {
|
|
720
787
|
const lib = compileWithOptionalIdentity(fullSource, options);
|
|
721
788
|
const { relAlias, meta } = libraryOriginsFromSource(source, fullSource, lib, options);
|
|
722
|
-
mergeLibrary(state, { ...lib, relAlias, meta },
|
|
789
|
+
mergeLibrary(state, { ...lib, relAlias, meta }, loadPath, { loadAliases });
|
|
723
790
|
} catch (error) {
|
|
724
791
|
throw remapError(error, preambleLineCount(preamble));
|
|
725
792
|
}
|
|
726
793
|
}
|
|
727
|
-
return [`loaded ${
|
|
794
|
+
return [`loaded ${loadPath}`];
|
|
728
795
|
}
|
|
729
796
|
case "t": {
|
|
730
797
|
if (!arg) throw new Error(`${usagePrefix}t requires a relation name`);
|
|
@@ -736,7 +803,7 @@ async function evaluateCommand(line, state) {
|
|
|
736
803
|
if (!arg) throw new Error(`${usagePrefix}d requires a relation name`);
|
|
737
804
|
const { hash, rel } = resolveRel(state, arg);
|
|
738
805
|
if (!rel) throw new Error(`Unknown relation '${arg}'`);
|
|
739
|
-
return [`${arg} = ${
|
|
806
|
+
return [`${arg} = ${prettyRelation(rel)}; -- ${hash}`];
|
|
740
807
|
}
|
|
741
808
|
case "C": {
|
|
742
809
|
if (!arg) throw new Error(`${usagePrefix}C requires a type name`);
|
|
@@ -759,7 +826,8 @@ function helpText() {
|
|
|
759
826
|
":type name show type definition",
|
|
760
827
|
":codes list type aliases",
|
|
761
828
|
":rels list relation aliases",
|
|
762
|
-
":load file
|
|
829
|
+
":load [--no-alias] file",
|
|
830
|
+
" load .k source or .klib",
|
|
763
831
|
":klib file export state as a library",
|
|
764
832
|
":ko file expr export executable .ko using expr as main",
|
|
765
833
|
":val print current value",
|
package/run.mjs
CHANGED
|
@@ -166,7 +166,7 @@ function compiledExp(findCode, exp, typePatternGraph) {
|
|
|
166
166
|
{
|
|
167
167
|
const defn = run.defs.rels[exp.ref];
|
|
168
168
|
if (defn != undefined) {
|
|
169
|
-
const refFn =
|
|
169
|
+
const refFn = compiledRel(findCode, defn, exp.ref);
|
|
170
170
|
fn = (value) => {
|
|
171
171
|
return value === undefined ? undefined : refFn(value);
|
|
172
172
|
};
|
|
@@ -266,6 +266,161 @@ function compiledExp(findCode, exp, typePatternGraph) {
|
|
|
266
266
|
return compiled.fn;
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
+
function compiledRel(findCode, relDef, name = "<anonymous>") {
|
|
270
|
+
const cached = relDef._compiledRunRel;
|
|
271
|
+
if (cached?.findCode === findCode && cached?.defs === run.defs) {
|
|
272
|
+
return cached.fn;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const compiled = {
|
|
276
|
+
findCode,
|
|
277
|
+
defs: run.defs,
|
|
278
|
+
target: null,
|
|
279
|
+
fn(value) {
|
|
280
|
+
return compiled.target(value);
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
relDef._compiledRunRel = compiled;
|
|
284
|
+
|
|
285
|
+
const exp = relDef.def;
|
|
286
|
+
const typePatternGraph = relDef.typePatternGraph || null;
|
|
287
|
+
const inputPattern = staticPattern(typePatternGraph, exp, 0);
|
|
288
|
+
const staticOutputPattern = outputPattern(typePatternGraph, exp);
|
|
289
|
+
const bodyFn = compiledExp(findCode, exp, typePatternGraph);
|
|
290
|
+
|
|
291
|
+
compiled.target = (value) => {
|
|
292
|
+
if (value === undefined) return undefined;
|
|
293
|
+
value = constrainWithPattern(value, inputPattern, exp);
|
|
294
|
+
const result = bodyFn(value);
|
|
295
|
+
if (result === undefined) return undefined;
|
|
296
|
+
try {
|
|
297
|
+
return constrainWithPattern(result, staticOutputPattern, exp);
|
|
298
|
+
} catch (error) {
|
|
299
|
+
error.message = `Type Error in output of '${name}'\n - ${error.message}`;
|
|
300
|
+
throw error;
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
return compiled.fn;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function assertConvergedRel(rel, name) {
|
|
307
|
+
const status = rel?.typeDerivation?.status || "unknown";
|
|
308
|
+
if (status !== "converged") {
|
|
309
|
+
throw new Error(`Cannot run '${name}' without envelopes: type derivation is ${status}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function compiledConvergedExp(findCode, exp, typePatternGraph, options = {}) {
|
|
314
|
+
const requireConverged = options.requireConverged === true;
|
|
315
|
+
const cached = exp._compiledConvergedRun;
|
|
316
|
+
if (
|
|
317
|
+
cached?.findCode === findCode &&
|
|
318
|
+
cached?.typePatternGraph === typePatternGraph &&
|
|
319
|
+
cached?.defs === run_converged.defs &&
|
|
320
|
+
cached?.requireConverged === requireConverged
|
|
321
|
+
) {
|
|
322
|
+
return cached.fn;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const compiled = {
|
|
326
|
+
findCode,
|
|
327
|
+
typePatternGraph,
|
|
328
|
+
defs: run_converged.defs,
|
|
329
|
+
requireConverged,
|
|
330
|
+
target: null,
|
|
331
|
+
fn(value) {
|
|
332
|
+
return compiled.target(value);
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
exp._compiledConvergedRun = compiled;
|
|
336
|
+
|
|
337
|
+
let fn;
|
|
338
|
+
switch (exp.op) {
|
|
339
|
+
case "code":
|
|
340
|
+
case "filter":
|
|
341
|
+
case "identity":
|
|
342
|
+
fn = (value) => value;
|
|
343
|
+
break;
|
|
344
|
+
case "ref":
|
|
345
|
+
{
|
|
346
|
+
const defn = run_converged.defs?.rels?.[exp.ref];
|
|
347
|
+
if (defn != undefined) {
|
|
348
|
+
if (requireConverged) assertConvergedRel(defn, exp.ref);
|
|
349
|
+
const refFn = compiledConvergedExp(findCode, defn.def, defn.typePatternGraph, options);
|
|
350
|
+
fn = (value) => value === undefined ? undefined : refFn(value);
|
|
351
|
+
} else {
|
|
352
|
+
const builtin_func = builtin[exp.ref];
|
|
353
|
+
if (builtin_func != null) {
|
|
354
|
+
fn = (value) => value === undefined ? undefined : builtin_func(value);
|
|
355
|
+
} else {
|
|
356
|
+
fn = () => {
|
|
357
|
+
throw(`Unknown ref: '${exp.ref}'`);
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
break;
|
|
363
|
+
case "dot":
|
|
364
|
+
fn = (value) => {
|
|
365
|
+
if (value === undefined || !(value instanceof Product)) return undefined;
|
|
366
|
+
return Object.hasOwn(value.product, exp.dot) ? value.product[exp.dot] : undefined;
|
|
367
|
+
};
|
|
368
|
+
break;
|
|
369
|
+
case "div":
|
|
370
|
+
fn = (value) => {
|
|
371
|
+
if (value === undefined || !(value instanceof Variant) || value.tag !== exp.div) return undefined;
|
|
372
|
+
return value.value;
|
|
373
|
+
};
|
|
374
|
+
break;
|
|
375
|
+
case "comp": {
|
|
376
|
+
const parts = exp.comp.map((part) => compiledConvergedExp(findCode, part, typePatternGraph, options));
|
|
377
|
+
fn = (value) => {
|
|
378
|
+
for (let i = 0, len = parts.length; i < len; i++) {
|
|
379
|
+
value = parts[i](value);
|
|
380
|
+
if (value === undefined) return undefined;
|
|
381
|
+
}
|
|
382
|
+
return value;
|
|
383
|
+
};
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
case "union": {
|
|
387
|
+
const parts = exp.union.map((part) => compiledConvergedExp(findCode, part, typePatternGraph, options));
|
|
388
|
+
fn = (value) => {
|
|
389
|
+
if (value === undefined) return undefined;
|
|
390
|
+
for (let i = 0, len = parts.length; i < len; i++) {
|
|
391
|
+
const result = parts[i](value);
|
|
392
|
+
if (result !== undefined) return result;
|
|
393
|
+
}
|
|
394
|
+
return undefined;
|
|
395
|
+
};
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
case "product": {
|
|
399
|
+
const labels = exp.product.map(({ label }) => label);
|
|
400
|
+
const fieldFns = exp.product.map(({ exp: e }) => compiledConvergedExp(findCode, e, typePatternGraph, options));
|
|
401
|
+
fn = (value) => {
|
|
402
|
+
if (value === undefined) return undefined;
|
|
403
|
+
const result = {};
|
|
404
|
+
for (let i = 0, len = labels.length; i < len; i++) {
|
|
405
|
+
const r = fieldFns[i](value);
|
|
406
|
+
if (r === undefined) return undefined;
|
|
407
|
+
result[labels[i]] = r;
|
|
408
|
+
}
|
|
409
|
+
return new Product(result);
|
|
410
|
+
};
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
case "vid":
|
|
414
|
+
fn = (value) => value === undefined ? undefined : new Variant(exp.vid, value);
|
|
415
|
+
break;
|
|
416
|
+
default:
|
|
417
|
+
assert(false,`Unknown operation: '${exp.op}'`);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
compiled.target = fn;
|
|
421
|
+
return compiled.fn;
|
|
422
|
+
}
|
|
423
|
+
|
|
269
424
|
function verify(findCode, code, value) {
|
|
270
425
|
"use strict";
|
|
271
426
|
if (code == null) return false;
|
|
@@ -296,7 +451,22 @@ function run(findCode, exp, value, typePatternGraph) {
|
|
|
296
451
|
return compiledExp(findCode, exp, typePatternGraph || null)(value);
|
|
297
452
|
}
|
|
298
453
|
|
|
454
|
+
function run_rel(findCode, relDef, value, name) {
|
|
455
|
+
return compiledRel(findCode, relDef, name)(value);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function run_converged(findCode, exp, value, typePatternGraph, options = {}) {
|
|
459
|
+
const graph = typePatternGraph || null;
|
|
460
|
+
const result = compiledConvergedExp(findCode, exp, graph, options)(value);
|
|
461
|
+
if (result === undefined) return undefined;
|
|
462
|
+
return withPattern(result, outputPattern(graph, exp));
|
|
463
|
+
}
|
|
464
|
+
|
|
299
465
|
run.builtin = builtin;
|
|
466
|
+
run_converged.builtin = builtin;
|
|
467
|
+
run_converged.defs = null;
|
|
468
|
+
|
|
469
|
+
const run_coverged = run_converged;
|
|
300
470
|
|
|
301
471
|
export default run;
|
|
302
|
-
export { run
|
|
472
|
+
export { run, run_rel, run_converged, run_coverged };
|
package/test-k-object.mjs
CHANGED
|
@@ -6,7 +6,15 @@ 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
|
+
runConvergedObject,
|
|
15
|
+
decompileObjectBuffer,
|
|
16
|
+
extractAliasesFromObject
|
|
17
|
+
} from "./object.mjs";
|
|
10
18
|
|
|
11
19
|
const value = parseValue("{a:{b:x,c:{}}}");
|
|
12
20
|
const wire = encodeToWire(value, null);
|
|
@@ -47,10 +55,30 @@ const objectBuffer = compileObjectBuffer(`
|
|
|
47
55
|
$ int = < bits +, bits - >;
|
|
48
56
|
{}|_|0|1|+ $int
|
|
49
57
|
`);
|
|
50
|
-
|
|
58
|
+
assert.equal(objectBuffer.subarray(0, 5).toString("utf8"), "KOBJ\n");
|
|
59
|
+
const object = decodeObject(objectBuffer);
|
|
60
|
+
assert.equal("version" in object, false);
|
|
61
|
+
assert(Object.values(object.codes).every((code) => code.typeDerivation == null));
|
|
62
|
+
assert.equal(JSON.stringify(object.codes).includes('"start"'), false);
|
|
63
|
+
assert.equal(JSON.stringify(object.codes).includes('"end"'), false);
|
|
64
|
+
assert.equal(JSON.stringify(object.meta).includes('"spans"'), false);
|
|
65
|
+
assert(Object.values(object.rels).every((rel) =>
|
|
66
|
+
JSON.stringify(Object.keys(rel.typeDerivation || {}).sort()) === JSON.stringify(["status"]) &&
|
|
67
|
+
rel.typeDerivation.status === "converged"
|
|
68
|
+
));
|
|
69
|
+
assert.notEqual(object.rels.__main__.def.comp?.[0]?.op, "filter");
|
|
70
|
+
assert.equal(JSON.stringify(object.rels.__main__.def).includes('"start"'), false);
|
|
71
|
+
assert(object.meta[object.relAlias.__main__]?.origins?.some((origin) =>
|
|
72
|
+
origin.name === "__main__" && origin.start && origin.end
|
|
73
|
+
));
|
|
74
|
+
|
|
75
|
+
const objectFn = objectToFunction(object);
|
|
51
76
|
const objectResult = objectFn(new Product({}));
|
|
52
77
|
assert.deepEqual(objectResult.pattern, INT_PATTERN);
|
|
53
78
|
assert.deepEqual(objectResult.toJSON(), { "+": { "1": { "0": "_" } } });
|
|
79
|
+
const convergedObjectResult = runConvergedObject(object, new Product({}));
|
|
80
|
+
assert.deepEqual(convergedObjectResult.pattern, INT_PATTERN);
|
|
81
|
+
assert.deepEqual(convergedObjectResult.toJSON(), objectResult.toJSON());
|
|
54
82
|
|
|
55
83
|
const decompiledSource = decompileObjectBuffer(objectBuffer);
|
|
56
84
|
const decompiledResult = k.compile(decompiledSource)(new Product({}));
|
|
@@ -71,4 +99,103 @@ const sccSource = decompileObjectBuffer(compileObjectBuffer("c = {}; b = c |x; a
|
|
|
71
99
|
assert.match(sccSource, /----- rels -----\nPQgV = .+\n\naQAD = .+\n\nN9UH = /s);
|
|
72
100
|
assert.deepEqual(k.compile(sccSource)(new Product({})).toJSON(), { y: "x" });
|
|
73
101
|
|
|
102
|
+
const libraryBuffer = compileLibraryBuffer("$ nat = <{} zero, nat succ>;\nsucc = |succ;\n", { source: "defs-only.k" });
|
|
103
|
+
assert.equal(libraryBuffer.subarray(0, 1).toString("utf8"), "{");
|
|
104
|
+
const library = decodeObject(libraryBuffer);
|
|
105
|
+
assert.equal("version" in library, false);
|
|
106
|
+
assert.equal(library.main, null);
|
|
107
|
+
assert(Object.values(library.codes).every((code) => code.typeDerivation == null));
|
|
108
|
+
assert.equal(JSON.stringify(library.codes).includes('"start"'), false);
|
|
109
|
+
assert.equal(JSON.stringify(library.codes).includes('"end"'), false);
|
|
110
|
+
assert.equal(JSON.stringify(library.meta).includes('"spans"'), false);
|
|
111
|
+
assert(Object.values(library.rels).every((rel) =>
|
|
112
|
+
JSON.stringify(Object.keys(rel.typeDerivation || {}).sort()) === JSON.stringify(["status"]) &&
|
|
113
|
+
rel.typeDerivation.status === "converged"
|
|
114
|
+
));
|
|
115
|
+
assert(Object.values(library.rels).every((rel) => rel.def.op !== "comp" || rel.def.comp[0]?.op !== "filter"));
|
|
116
|
+
assert(Object.values(library.meta).some(({ type, origins }) =>
|
|
117
|
+
type === "code" &&
|
|
118
|
+
origins.some((origin) =>
|
|
119
|
+
origin.source === "defs-only.k" &&
|
|
120
|
+
origin.name === "nat" &&
|
|
121
|
+
typeof origin.compiledAt === "string" &&
|
|
122
|
+
origin.start &&
|
|
123
|
+
origin.end
|
|
124
|
+
)
|
|
125
|
+
));
|
|
126
|
+
assert(Object.values(library.meta).some(({ type, origins }) =>
|
|
127
|
+
type === "rel" &&
|
|
128
|
+
origins.some((origin) =>
|
|
129
|
+
origin.source === "defs-only.k" &&
|
|
130
|
+
origin.name === "succ" &&
|
|
131
|
+
typeof origin.compiledAt === "string" &&
|
|
132
|
+
origin.start &&
|
|
133
|
+
origin.end
|
|
134
|
+
)
|
|
135
|
+
));
|
|
136
|
+
|
|
137
|
+
const derivedLibrary = decodeObject(compileLibraryBuffer("other = |other;\n", {
|
|
138
|
+
source: "derived.k",
|
|
139
|
+
libraries: [library]
|
|
140
|
+
}));
|
|
141
|
+
const derivedAliases = extractAliasesFromObject(derivedLibrary);
|
|
142
|
+
assert.match(derivedAliases, /^\$ nat = @/m);
|
|
143
|
+
assert.match(derivedAliases, /^succ = @/m);
|
|
144
|
+
assert.match(derivedAliases, /^other = @/m);
|
|
145
|
+
assert(derivedAliases.indexOf("$ nat = @") < derivedAliases.indexOf("other = @"));
|
|
146
|
+
assert(derivedAliases.indexOf("other = @") < derivedAliases.indexOf("succ = @"));
|
|
147
|
+
|
|
148
|
+
{
|
|
149
|
+
const warn = console.warn;
|
|
150
|
+
console.warn = () => {};
|
|
151
|
+
try {
|
|
152
|
+
const nonConverged = decodeObject(compileObjectBuffer("f = .x f; f", {
|
|
153
|
+
convergence: { strategy: "fixed_point", maxIterations: 1 }
|
|
154
|
+
}));
|
|
155
|
+
assert.equal(nonConverged.rels.__main__.typeDerivation.status, "not-converged");
|
|
156
|
+
assert.throws(
|
|
157
|
+
() => runConvergedObject(nonConverged, new Product({ x: new Product({}) })),
|
|
158
|
+
/type derivation is not-converged/
|
|
159
|
+
);
|
|
160
|
+
} finally {
|
|
161
|
+
console.warn = warn;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const aliasSnippet = extractAliasesFromObject({
|
|
166
|
+
format: "k-object",
|
|
167
|
+
codes: {},
|
|
168
|
+
rels: {},
|
|
169
|
+
meta: {
|
|
170
|
+
"@relB": {
|
|
171
|
+
type: "rel",
|
|
172
|
+
origins: [
|
|
173
|
+
{ source: "new.k", name: "times", compiledAt: "2026-04-12T06:08:10.390Z" },
|
|
174
|
+
{ source: "core.k", name: "times", compiledAt: "2026-03-09T06:17:23.390Z" },
|
|
175
|
+
{ source: "other.k", name: "plus", compiledAt: "2026-05-17T06:10:23.390Z" }
|
|
176
|
+
]
|
|
177
|
+
},
|
|
178
|
+
"@codeB": {
|
|
179
|
+
type: "code",
|
|
180
|
+
origins: [
|
|
181
|
+
{ source: "z.k", name: "edges", compiledAt: "2026-03-09T06:17:23.390Z" }
|
|
182
|
+
]
|
|
183
|
+
},
|
|
184
|
+
"@codeA": {
|
|
185
|
+
type: "code",
|
|
186
|
+
origins: [
|
|
187
|
+
{ source: "a.k", name: "bits", compiledAt: "2026-05-17T06:10:23.390Z" }
|
|
188
|
+
]
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
main: null
|
|
192
|
+
});
|
|
193
|
+
assert.deepEqual(aliasSnippet.split("\n").filter(Boolean), [
|
|
194
|
+
'$ bits = @codeA; # {"source":"a.k","compiledAt":"2026-05-17T06:10:23.390Z"}',
|
|
195
|
+
'$ edges = @codeB; # {"source":"z.k","compiledAt":"2026-03-09T06:17:23.390Z"}',
|
|
196
|
+
'plus = @relB; # {"source":"other.k","compiledAt":"2026-05-17T06:10:23.390Z"}',
|
|
197
|
+
'# times = @relB; # {"source":"core.k","compiledAt":"2026-03-09T06:17:23.390Z"}',
|
|
198
|
+
'times = @relB; # {"source":"new.k","compiledAt":"2026-04-12T06:08:10.390Z"}'
|
|
199
|
+
]);
|
|
200
|
+
|
|
74
201
|
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, []);
|