@fraczak/k 6.0.3 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fraczak/k",
3
- "version": "6.0.3",
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": {
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,
@@ -149,11 +149,7 @@ function mergeMeta(state, meta = {}) {
149
149
  const entryType = entry?.type;
150
150
  if (!state.meta[hash]) state.meta[hash] = { type: entryType, origins: [] };
151
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
- })));
152
+ state.meta[hash].origins.push(...origins.map((origin) => ({ ...origin })));
157
153
  }
158
154
  }
159
155
 
@@ -220,7 +216,6 @@ function savedLibrary(state) {
220
216
  }
221
217
  return {
222
218
  format: "k-object",
223
- version: 2,
224
219
  codes: state.codes,
225
220
  rels: state.rels,
226
221
  relAlias: state.relAliases,
@@ -289,6 +284,16 @@ function userDefinedNames(source) {
289
284
  };
290
285
  }
291
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
+
292
297
  function libraryOriginsFromSource(source, fullSource, lib, options) {
293
298
  const { typeNames, relNames } = userDefinedNames(source);
294
299
  const annotated = annotateWithOptionalIdentity(fullSource, options);
@@ -299,7 +304,12 @@ function libraryOriginsFromSource(source, fullSource, lib, options) {
299
304
  const hash = annotated.representatives?.[name];
300
305
  if (!hash) continue;
301
306
  if (!meta[hash]) meta[hash] = { type: "code", origins: [] };
302
- meta[hash].origins.push({ source: options.source || null, name, compiledAt: now });
307
+ meta[hash].origins.push(originFromSourceNode(
308
+ options.source || null,
309
+ name,
310
+ now,
311
+ annotated.sourceDefs?.codes?.[name]
312
+ ));
303
313
  }
304
314
 
305
315
  const relAlias = Object.fromEntries(
@@ -310,7 +320,12 @@ function libraryOriginsFromSource(source, fullSource, lib, options) {
310
320
 
311
321
  for (const [name, hash] of Object.entries(relAlias)) {
312
322
  if (!meta[hash]) meta[hash] = { type: "rel", origins: [] };
313
- meta[hash].origins.push({ source: options.source || null, name, compiledAt: now });
323
+ meta[hash].origins.push(originFromSourceNode(
324
+ options.source || null,
325
+ name,
326
+ now,
327
+ annotated.rels?.[name]?.def
328
+ ));
314
329
  }
315
330
 
316
331
  return { relAlias, meta };
@@ -788,7 +803,7 @@ async function evaluateCommand(line, state) {
788
803
  if (!arg) throw new Error(`${usagePrefix}d requires a relation name`);
789
804
  const { hash, rel } = resolveRel(state, arg);
790
805
  if (!rel) throw new Error(`Unknown relation '${arg}'`);
791
- return [`${arg} = ${prettyRel(rel.def)}; -- ${hash}`];
806
+ return [`${arg} = ${prettyRelation(rel)}; -- ${hash}`];
792
807
  }
793
808
  case "C": {
794
809
  if (!arg) throw new Error(`${usagePrefix}C requires a type name`);
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 = compiledExp(findCode, defn.def, defn.typePatternGraph);
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
@@ -11,6 +11,7 @@ import {
11
11
  compileLibraryBuffer,
12
12
  decodeObject,
13
13
  objectToFunction,
14
+ runConvergedObject,
14
15
  decompileObjectBuffer,
15
16
  extractAliasesFromObject
16
17
  } from "./object.mjs";
@@ -54,10 +55,30 @@ const objectBuffer = compileObjectBuffer(`
54
55
  $ int = < bits +, bits - >;
55
56
  {}|_|0|1|+ $int
56
57
  `);
57
- const objectFn = objectToFunction(decodeObject(objectBuffer));
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);
58
76
  const objectResult = objectFn(new Product({}));
59
77
  assert.deepEqual(objectResult.pattern, INT_PATTERN);
60
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());
61
82
 
62
83
  const decompiledSource = decompileObjectBuffer(objectBuffer);
63
84
  const decompiledResult = k.compile(decompiledSource)(new Product({}));
@@ -78,15 +99,28 @@ const sccSource = decompileObjectBuffer(compileObjectBuffer("c = {}; b = c |x; a
78
99
  assert.match(sccSource, /----- rels -----\nPQgV = .+\n\naQAD = .+\n\nN9UH = /s);
79
100
  assert.deepEqual(k.compile(sccSource)(new Product({})).toJSON(), { y: "x" });
80
101
 
81
- const library = decodeObject(compileLibraryBuffer("$ nat = <{} zero, nat succ>;\nsucc = |succ;\n", { source: "defs-only.k" }));
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);
82
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"));
83
116
  assert(Object.values(library.meta).some(({ type, origins }) =>
84
117
  type === "code" &&
85
118
  origins.some((origin) =>
86
119
  origin.source === "defs-only.k" &&
87
120
  origin.name === "nat" &&
88
- JSON.stringify(Object.keys(origin).sort()) === JSON.stringify(["compiledAt", "name", "source"]) &&
89
- typeof origin.compiledAt === "string"
121
+ typeof origin.compiledAt === "string" &&
122
+ origin.start &&
123
+ origin.end
90
124
  )
91
125
  ));
92
126
  assert(Object.values(library.meta).some(({ type, origins }) =>
@@ -94,8 +128,9 @@ assert(Object.values(library.meta).some(({ type, origins }) =>
94
128
  origins.some((origin) =>
95
129
  origin.source === "defs-only.k" &&
96
130
  origin.name === "succ" &&
97
- JSON.stringify(Object.keys(origin).sort()) === JSON.stringify(["compiledAt", "name", "source"]) &&
98
- typeof origin.compiledAt === "string"
131
+ typeof origin.compiledAt === "string" &&
132
+ origin.start &&
133
+ origin.end
99
134
  )
100
135
  ));
101
136
 
@@ -110,9 +145,25 @@ assert.match(derivedAliases, /^other = @/m);
110
145
  assert(derivedAliases.indexOf("$ nat = @") < derivedAliases.indexOf("other = @"));
111
146
  assert(derivedAliases.indexOf("other = @") < derivedAliases.indexOf("succ = @"));
112
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
+
113
165
  const aliasSnippet = extractAliasesFromObject({
114
166
  format: "k-object",
115
- version: 2,
116
167
  codes: {},
117
168
  rels: {},
118
169
  meta: {