@fraczak/k 5.1.2 → 6.0.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.
Files changed (99) hide show
  1. package/Code-derivation-tests/blowup.mjs +3 -3
  2. package/Code-derivation-tests/bnat.mjs +2 -2
  3. package/Code-derivation-tests/codec-witness-pattern.mjs +50 -0
  4. package/Code-derivation-tests/convergence-strategy.mjs +24 -0
  5. package/Code-derivation-tests/dot.mjs +12 -9
  6. package/Code-derivation-tests/filter-syntax.mjs +21 -0
  7. package/Code-derivation-tests/loop.mjs +8 -2
  8. package/Code-derivation-tests/nat.mjs +1 -1
  9. package/Code-derivation-tests/not-not.mjs +3 -3
  10. package/Code-derivation-tests/pattern-envelope.mjs +68 -0
  11. package/Code-derivation-tests/rlz.mjs +2 -2
  12. package/{BIG_PICTURE.md → DOCS/BIG_PICTURE.md} +103 -50
  13. package/DOCS/REPL.md +191 -0
  14. package/DOCS/book/01-introduction.md +51 -0
  15. package/DOCS/book/02-syntax-and-values.md +111 -0
  16. package/DOCS/book/03-types-as-finite-automata.md +156 -0
  17. package/DOCS/book/04-partial-functions-and-composition.md +146 -0
  18. package/DOCS/book/05-typing-filters-and-normalization.md +111 -0
  19. package/DOCS/book.md +54 -0
  20. package/README.md +204 -508
  21. package/TypeGraph.mjs +43 -0
  22. package/TypePatternGraph.mjs +251 -0
  23. package/Value.mjs +192 -0
  24. package/augmentation.mjs +198 -0
  25. package/codecs/BINARY_FORMAT.md +53 -0
  26. package/codecs/POLYMORPHIC_BINARY_FORMAT.md +303 -0
  27. package/codecs/README.md +215 -0
  28. package/codecs/example-pipeline.mjs +149 -0
  29. package/codecs/ieee.mjs +140 -0
  30. package/codecs/int.mjs +104 -0
  31. package/codecs/json-codec.mjs +215 -0
  32. package/codecs/json.mjs +40 -0
  33. package/codecs/k-parse.mjs +90 -0
  34. package/codecs/k-pattern.mjs +75 -0
  35. package/codecs/k-print.mjs +46 -0
  36. package/codecs/runtime/codec.mjs +768 -0
  37. package/codecs/runtime/ieee-pattern.mjs +30 -0
  38. package/codecs/runtime/pattern-json.mjs +96 -0
  39. package/codecs/runtime/prefix-codec.mjs +425 -0
  40. package/codecs/runtime/show-value.mjs +68 -0
  41. package/codecs/runtime/typeFromValue.mjs +34 -0
  42. package/codecs/runtime/unicode-string.mjs +220 -0
  43. package/codecs/show.mjs +41 -0
  44. package/codecs/string-codec.mjs +80 -0
  45. package/codecs/unit.mjs +51 -0
  46. package/codecs/utf16.mjs +66 -0
  47. package/codecs/utf8.mjs +37 -0
  48. package/codes.mjs +146 -44
  49. package/compiler.mjs +111 -0
  50. package/convergence.mjs +197 -0
  51. package/core.k +197 -0
  52. package/export.mjs +22 -32
  53. package/hash.mjs +29 -31
  54. package/index.mjs +41 -15
  55. package/k-vscode/.vscode/launch.json +14 -0
  56. package/k-vscode/README.md +65 -0
  57. package/k-vscode/language-configuration.json +42 -0
  58. package/k-vscode/package.json +57 -0
  59. package/k-vscode/snippets/k.json +92 -0
  60. package/k-vscode/syntaxes/k.tmLanguage.json +243 -0
  61. package/k.mjs +56 -54
  62. package/object.mjs +517 -0
  63. package/objects/README.md +38 -0
  64. package/objects/compile-lib.mjs +42 -0
  65. package/objects/compile.mjs +42 -0
  66. package/objects/decompile.mjs +32 -0
  67. package/package.json +18 -8
  68. package/parser.jison +27 -46
  69. package/parser.mjs +68 -100
  70. package/pretty.mjs +21 -40
  71. package/repl2.mjs +652 -0
  72. package/run.mjs +264 -231
  73. package/symbol-table.mjs +3 -17
  74. package/test-fingerprint.mjs +7 -7
  75. package/test-hash-fuzz.mjs +107 -0
  76. package/test-hash-normalization.mjs +134 -0
  77. package/test-k-object.mjs +74 -0
  78. package/test-repl2.mjs +95 -0
  79. package/test.mjs +45 -70
  80. package/tests.sh +10 -1
  81. package/typing.mjs +129 -676
  82. package/unification.mjs +157 -0
  83. package/valueIO.mjs +54 -0
  84. package/valueParser.jison +143 -0
  85. package/valueParser.mjs +713 -0
  86. package/Code-derivation-tests/built-in.mjs +0 -19
  87. package/Code-derivation-tests/vector.mjs +0 -19
  88. package/Filters/bug.k +0 -3
  89. package/Filters/composing.k +0 -15
  90. package/Filters/filter.k +0 -9
  91. package/TODO.md +0 -53
  92. package/TYPING.md +0 -365
  93. package/k-repl.cast +0 -1913
  94. package/k.cast +0 -618
  95. package/my-objects.json +0 -6
  96. package/patterns.mjs +0 -543
  97. package/repl.mjs +0 -213
  98. package/test-filters.mjs +0 -45
  99. package/typing-test.mjs +0 -40
package/codes.mjs CHANGED
@@ -1,6 +1,5 @@
1
1
  import hash from "./hash.mjs";
2
2
 
3
-
4
3
  const theRepository = { codes: { } };
5
4
 
6
5
  const unitCode = function() {
@@ -8,36 +7,18 @@ const unitCode = function() {
8
7
  return hash(unitCodeDef);
9
8
  }();
10
9
 
11
- function isBuiltIn(code) {
12
- return code.match(/^@(int|string|bool)$/);
13
- }
10
+ theRepository.codes[unitCode] = { code: "product", product: {} };
14
11
 
15
12
  function encodeCodeToString(code, codes = theRepository.codes) {
16
- if (isBuiltIn(code)) return code;
17
- var i = 0;
13
+ let i = 0;
18
14
  const Q = [[code,i]];
19
15
  const D = {[code]: i++};
20
- var result=[];
16
+ let result=[];
21
17
  while (Q.length > 0) {
22
18
  const [x,c] = Q.shift();
23
19
  const c_code = codes[x];
24
- if (c_code.code == "vector") {
25
- const u_arg = c_code.vector;
26
- if (isBuiltIn(u_arg)) {
27
- result.push(`$C${result.length}=[${u_arg}];`);
28
- continue;
29
- }
30
- if (D[u_arg] === undefined) {
31
- Q.push([u_arg,i]);
32
- D[u_arg] = i++;
33
- }
34
- result.push(`$C${result.length}=[C${D[u_arg]}];`);
35
- continue;
36
- }
37
20
  const u_args = Object.keys(c_code[c_code.code]).sort().map((k) => {
38
21
  const u_arg = c_code[c_code.code][k];
39
- if (isBuiltIn(u_arg))
40
- return `${u_arg}${JSON.stringify(k)}`;
41
22
  if (D[u_arg] === undefined) {
42
23
  Q.push([u_arg,i]);
43
24
  D[u_arg] = i++;
@@ -79,22 +60,20 @@ function are_different(classes, representatives, name1, name2, codes) {
79
60
  for (const field in fields1) {
80
61
  if (fields2[field] !== fields1[field]) return true;
81
62
  }
82
- break;
83
- case "vector":
84
- const [arg1, arg2] = [code1, code2].map(
85
- ({ vector }) => representatives[vector] || vector
86
- );
87
- if (arg1 !== arg2) return true;
88
63
  }
89
64
  return false;
90
65
  }
91
66
 
92
67
  function minimize(codes) {
93
68
  const names = Object.keys(codes);
69
+ if (names.length === 0) {
70
+ return { classes: {}, representatives: {} };
71
+ }
94
72
  const classes = {};
95
- classes[unitCode] = names;
73
+ const seed = names.includes(unitCode) ? unitCode : names[0];
74
+ classes[seed] = names;
96
75
  const representatives = names.reduce((representatives, name) => {
97
- representatives[name] = unitCode;
76
+ representatives[name] = seed;
98
77
  return representatives;
99
78
  }, {});
100
79
  let changed = true;
@@ -127,6 +106,34 @@ function minimize(codes) {
127
106
  return { classes, representatives };
128
107
  }
129
108
 
109
+ function resolveRefs(codes) {
110
+ const resolved = {};
111
+
112
+ for (const name in codes) {
113
+ const code = codes[name];
114
+ if (code.code === "ref" && code.ref.startsWith("@")) {
115
+ // This is an alias to a canonical type (by name or by hash)
116
+ const refName = code.ref.substring(1); // Remove the @ prefix
117
+
118
+ // Check if it's a reference to a user-defined type in the current codes
119
+ if (codes[refName] !== undefined) {
120
+ resolved[name] = refName;
121
+ }
122
+ // Check if it's a reference to an existing canonical hash in the repository
123
+ else if (theRepository.codes[code.ref] !== undefined) {
124
+ resolved[name] = code.ref;
125
+ }
126
+ else {
127
+ throw new Error(`Type alias references undefined type: ${code.ref}`);
128
+ }
129
+ } else {
130
+ resolved[name] = name;
131
+ }
132
+ }
133
+
134
+ return resolved;
135
+ }
136
+
130
137
  function normalize(label_ref_map, representatives) {
131
138
  return Object.keys(label_ref_map).reduce((result, label) => {
132
139
  const name = label_ref_map[label];
@@ -148,12 +155,9 @@ function normalizeAll(codes, representatives) {
148
155
  representatives
149
156
  );
150
157
  break;
151
- case "vector":
152
- normalized[name] = {
153
- ...code,
154
- vector: representatives[code.vector] || code.vector,
155
- };
156
- break;
158
+ case "ref":
159
+ // Ref codes should have been resolved in finalize, this shouldn't happen
160
+ throw new Error(`Unresolved type alias: ${code.ref}`);
157
161
  default:
158
162
  throw new Error(`Unexpected code ${code.code}`);
159
163
  }
@@ -164,8 +168,41 @@ function normalizeAll(codes, representatives) {
164
168
  }
165
169
 
166
170
  function finalize(codes) {
167
- const representatives = minimize(codes).representatives;
168
- const normalizedCodes = normalizeAll(codes, representatives);
171
+ // First, resolve all @name references to their actual code definitions
172
+ const refResolution = resolveRefs(codes);
173
+ const resolvedCodes = {};
174
+
175
+ for (const name in codes) {
176
+ const code = codes[name];
177
+ if (code.code === "ref" && code.ref.startsWith("@")) {
178
+ // This is an alias, use the resolved target's code
179
+ const target = refResolution[name];
180
+
181
+ // Check if target is a canonical hash (starts with @)
182
+ if (target.startsWith("@")) {
183
+ // Reference to existing canonical hash in repository
184
+ resolvedCodes[name] = theRepository.codes[target];
185
+ } else {
186
+ // Reference to a user-defined type in current codes
187
+ resolvedCodes[name] = codes[target];
188
+ }
189
+ } else {
190
+ resolvedCodes[name] = code;
191
+ }
192
+ }
193
+
194
+ const representatives = minimize(resolvedCodes).representatives;
195
+
196
+ // Also update representatives to account for aliases
197
+ // If name was aliased to target, and target maps to rep, then name should map to rep
198
+ for (const name in refResolution) {
199
+ if (refResolution[name] !== name) {
200
+ const target = refResolution[name];
201
+ representatives[name] = representatives[target] || target;
202
+ }
203
+ }
204
+
205
+ const normalizedCodes = normalizeAll(resolvedCodes, representatives);
169
206
  const globalNames = Object.keys(normalizedCodes).reduce((globalNames, name) => {
170
207
  const globalDef = encodeCodeToString(name, normalizedCodes);
171
208
  normalizedCodes[name].def = globalDef;
@@ -193,22 +230,87 @@ function finalize(codes) {
193
230
  };
194
231
  }
195
232
 
233
+ function finalizeIncremental(newCodes) {
234
+ const refResolution = resolveRefs(newCodes);
235
+ const resolvedCodes = {};
236
+
237
+ for (const name in newCodes) {
238
+ const code = newCodes[name];
239
+ if (code.code === "ref" && code.ref.startsWith("@")) {
240
+ const target = refResolution[name];
241
+ if (target.startsWith("@")) {
242
+ resolvedCodes[name] = theRepository.codes[target];
243
+ } else {
244
+ resolvedCodes[name] = newCodes[target];
245
+ }
246
+ } else {
247
+ resolvedCodes[name] = code;
248
+ }
249
+ }
250
+
251
+ const representatives = minimize(resolvedCodes).representatives;
252
+ for (const name in refResolution) {
253
+ if (refResolution[name] !== name) {
254
+ const target = refResolution[name];
255
+ representatives[name] = representatives[target] || target;
256
+ }
257
+ }
258
+
259
+ const normalizedCodes = normalizeAll(resolvedCodes, representatives);
260
+ const allReadableCodes = { ...theRepository.codes, ...normalizedCodes };
261
+ const globalNames = {};
262
+
263
+ for (const name in normalizedCodes) {
264
+ const globalDef = encodeCodeToString(name, allReadableCodes);
265
+ normalizedCodes[name].def = globalDef;
266
+ globalNames[name] = hash(globalDef);
267
+ }
268
+
269
+ const globalCodes = Object.keys(normalizedCodes).reduce((result, name) => {
270
+ result[globalNames[name]] = normalizedCodes[name];
271
+ return result;
272
+ }, {});
273
+
274
+ const extendedRepresentatives = Object.keys(representatives).reduce((result, name) => {
275
+ result[name] = globalNames[representatives[name]] || representatives[name] || name;
276
+ return result;
277
+ }, {});
278
+
279
+ Object.values(globalNames).forEach((name) => {
280
+ extendedRepresentatives[name] = name;
281
+ });
282
+
283
+ const normalizedGlobalCodes = normalizeAll(globalCodes, extendedRepresentatives);
284
+ return {
285
+ codes: normalizedGlobalCodes,
286
+ representatives: extendedRepresentatives
287
+ };
288
+ }
289
+
196
290
  function register(newCodes) {
197
- const { codes, representatives } = finalize( {...theRepository.codes,...newCodes});
291
+ const { codes, representatives } = finalizeIncremental(newCodes);
198
292
  const reps = Object.values(representatives).reduce((reps,rep) =>
199
293
  ({...reps, [rep]:rep})
200
294
  , {});
201
295
  for (const rep in reps) {
202
- theRepository.codes[rep] = codes[rep];
296
+ if (codes[rep] !== undefined) {
297
+ theRepository.codes[rep] = codes[rep];
298
+ }
203
299
  };
204
300
  return representatives;
205
301
  }
206
302
 
207
303
  function find(codeName) {
208
- if (isBuiltIn(codeName))
209
- return {code: codeName, def: "builtin"};
210
304
  return JSON.parse(JSON.stringify(theRepository.codes[codeName] || {code: "undefined"}));
211
305
  };
212
306
 
213
- export default { minimize, normalize, normalizeAll, encodeCodeToString, finalize, unitCode, register, find };
214
- export { minimize, normalize, normalizeAll, encodeCodeToString, finalize, unitCode, register, find };
307
+ function load(dump) {
308
+ theRepository.codes = dump;
309
+ }
310
+
311
+ function dump() {
312
+ return JSON.parse(JSON.stringify(theRepository.codes));
313
+ }
314
+
315
+ export default { minimize, normalize, normalizeAll, encodeCodeToString, finalize, unitCode, register, find, load, dump };
316
+ export { minimize, normalize, normalizeAll, encodeCodeToString, finalize, unitCode, register, find, load, dump };
package/compiler.mjs ADDED
@@ -0,0 +1,111 @@
1
+ import hash from "./hash.mjs";
2
+ import { TypePatternGraph } from "./TypePatternGraph.mjs";
3
+ import { Graph, sccs, topoOrder } from "./Graph.mjs";
4
+ import codes from "./codes.mjs";
5
+ import { augment } from "./augmentation.mjs";
6
+ import { assignCanonicalNames } from "./export.mjs";
7
+ import { convergeScc, relDefToString } from "./convergence.mjs";
8
+
9
+ function compileTypes(representatives, rels, options = {}) {
10
+ // INPUT:
11
+ // representatives: {"{}": "KL", ...} - mapping from types to their canonical names
12
+ // rels: {"rlz": {def: {op: comp,...}, ...}, ...} - relation definitions
13
+ // options: {
14
+ // convergence: {
15
+ // strategy: "auto" | "single_pass" | "fixed_point", // type derivation strategy
16
+ // maxIterations: number // max iterations for fixed_point (default: 2 + scc.length)
17
+ // }
18
+ // }
19
+
20
+ const relAlias = {};
21
+ const compileStats = {
22
+ sccs: []
23
+ };
24
+
25
+ // 1 INITIALIZATION
26
+ // 1.1 initialize patternNodes and rels
27
+ // ==================================================================================
28
+ // ----------------------------------------------------------------------------------
29
+ // 1. Initialize the typePatternGraph and varRefs for each relation
30
+
31
+ for (const relName in rels) {
32
+ const rootDef = rels[relName];
33
+ // Skip already-compiled rels (e.g., from loaded libraries)
34
+ if (rootDef._library) continue;
35
+ rootDef.typePatternGraph = new TypePatternGraph(codes.register, codes.find);
36
+ rootDef.varRefs = []; // the list of references to non-builtin rels as pointers to AST nodes
37
+ rootDef.patternVars = {};
38
+ rootDef.rels = rels;
39
+ rootDef.representatives = representatives;
40
+ augment(rootDef.def, rootDef, rels, representatives);
41
+ delete rootDef.patternVars;
42
+ delete rootDef.rels;
43
+ delete rootDef.representatives;
44
+ }
45
+
46
+
47
+ // 2. Compute DAG of strongly connected components in varRefs
48
+ // A rel_1 -> rel_2 if rel1,varRefs contains a reference to {op: "ref", ref: rel_2}
49
+
50
+ const graph = new Graph(
51
+ // 1st argument: edges
52
+ [].concat(...Object.keys(rels).map( relName =>
53
+ (rels[relName].varRefs || []).map( ({varName}) =>
54
+ ({src: relName, dst: varName})))),
55
+ // 2nd argument: vertices
56
+ rels
57
+ );
58
+
59
+ const sccs_ = sccs(graph);
60
+
61
+ const DAGnodes = sccs_.reduce ((nodes, scc) =>
62
+ ({...nodes, [scc[0]]: {scc}}), {});
63
+ const fromRelNameToDAGnode = Object.keys(DAGnodes).reduce((map, node) =>
64
+ DAGnodes[node].scc.reduce((map, relName) => ({...map, [relName]: node}), map), {});
65
+ const DAGedges = [].concat(
66
+ ...Object.keys(rels).map( x =>
67
+ (rels[x].varRefs || []).map(({ varName }) =>
68
+ ({src: fromRelNameToDAGnode[x], dst: fromRelNameToDAGnode[varName]})
69
+ ) // don't forget to remove loops
70
+ .filter(({src, dst}) => src !== dst )
71
+ )
72
+ );
73
+ const DAG = new Graph(DAGedges, DAGnodes);
74
+
75
+ // 3. Topological sort of the DAG
76
+
77
+ const sccInOrder = topoOrder(DAG).reverse();
78
+
79
+ // 4. For each strongly connected component C in a bottom-up order
80
+ // we will also generate the canonical representation of the relations in C and replace
81
+ // the name to the relation by the hash of the canonical representation
82
+
83
+ for (const scc of sccInOrder) {
84
+ const currentScc = DAGnodes[scc].scc;
85
+
86
+ // Skip SCCs that consist entirely of pre-compiled (library) rels
87
+ const newRels = currentScc.filter(name => !rels[name]._library);
88
+ if (newRels.length === 0) continue;
89
+
90
+ const convergenceStats = convergeScc(newRels, rels, options.convergence);
91
+ compileStats.sccs.push({
92
+ members: [...newRels],
93
+ ...convergenceStats
94
+ });
95
+
96
+ newRels.forEach( relName => {
97
+ const relDef = rels[relName];
98
+ const canonical = relDefToString(relDef);
99
+ const h = hash(canonical);
100
+ relAlias[relName] = h;
101
+ });
102
+
103
+ assignCanonicalNames(newRels, rels, relAlias);
104
+ }
105
+
106
+ compileStats.sccCount = compileStats.sccs.length;
107
+ return { relAlias, compileStats };
108
+ }
109
+
110
+ export default { compileTypes, assignCanonicalNames };
111
+ export { compileTypes, assignCanonicalNames };
@@ -0,0 +1,197 @@
1
+ import { prettyFilter, patterns2filters } from "./pretty.mjs";
2
+ import { getCompressed } from "./typing.mjs";
3
+
4
+ export function signature(relDef) {
5
+ const typePatternGraph = relDef.typePatternGraph;
6
+ const filters = patterns2filters(typePatternGraph, ...relDef.def.patterns);
7
+ const result = filters.map( prettyFilter ).join(" --> ");
8
+ return result;
9
+ }
10
+
11
+ export function relDefToString(relDef) {
12
+ const start = new Date().getTime();
13
+ const dumpPatterns = (rel) => {
14
+ switch (rel.op) {
15
+ case "product":
16
+ return [].concat(rel.patterns, ...rel.product.map(({exp}) => dumpPatterns(exp)));
17
+ case "union":
18
+ return [].concat(rel.patterns, ...rel.union.map(exp => dumpPatterns(exp)));
19
+ case "comp":
20
+ return [].concat(rel.patterns, ...rel.comp.map(exp => dumpPatterns(exp)));
21
+ case "ref":
22
+ case "identity":
23
+ case "dot":
24
+ case "div":
25
+ case "vid":
26
+ case "code":
27
+ case "filter":
28
+ return [...rel.patterns];
29
+ }
30
+ throw new Error("NOT EXPECTED OP:", JSON.stringify(rel, null, 2));
31
+ };
32
+
33
+ const result = JSON.stringify(dumpPatterns(relDef.def).map((x) => {
34
+ const { pattern, fields, type} = relDef.typePatternGraph.get_pattern(x);
35
+ return {pattern, fields, type};
36
+ }));
37
+
38
+ let took = new Date().getTime() - start;
39
+ if (took > 200) {
40
+ console.log(` -- RelDefToString: size=${result.length}, took ${took} msecs`);
41
+ }
42
+ return result;
43
+ }
44
+
45
+ export function compactRel(relDef, name = "") {
46
+ const start = new Date().getTime();
47
+ const {def, typePatternGraph, varRefs } = relDef;
48
+
49
+ const {typePatternGraph : newTypePatternGraph, remapping: renumbering} = getCompressed(typePatternGraph);
50
+
51
+ const remapRel = (rel) => {
52
+ rel.patterns = [renumbering[rel.patterns[0]], renumbering[rel.patterns[1]]];
53
+ switch (rel.op) {
54
+ case "product":
55
+ rel.product.forEach(({ exp }) => remapRel(exp));
56
+ break;
57
+ case "union":
58
+ rel.union.forEach(exp => remapRel(exp));
59
+ break;
60
+ case "comp":
61
+ rel.comp.forEach(exp => remapRel(exp));
62
+ break;
63
+ case "ref":
64
+ case "identity":
65
+ case "dot":
66
+ case "div":
67
+ case "vid":
68
+ case "code":
69
+ case "filter":
70
+ break;
71
+ default:
72
+ console.error("NOT EXPECTED OP:", JSON.stringify(rel, null, 2));
73
+ break;
74
+ };
75
+ };
76
+ remapRel(def);
77
+ const newVarRefs = varRefs.map(x => {
78
+ x.inputPatternId = renumbering[x.inputPatternId];
79
+ x.outputPatternId = renumbering[x.outputPatternId];
80
+ return x;
81
+ });
82
+
83
+ const time = new Date().getTime() - start;
84
+ if (time > 500 ) {
85
+ console.log(`[${new Date().getTime() - start} msecs] - COMPACTED ${name}.typePatternGraph [${typePatternGraph.size()}->${newTypePatternGraph.size()}]`);
86
+ }
87
+ return {def, typePatternGraph: newTypePatternGraph, varRefs: newVarRefs};
88
+ }
89
+
90
+ function processReference(relName, relDef, rels, varRel, index) {
91
+ const start = new Date().getTime();
92
+ let { varName } = varRel;
93
+ try {
94
+ let varRootDef = rels[varName];
95
+ let varInputPatternId = varRootDef.typePatternGraph.find(varRootDef.def.patterns[0]);
96
+ let varOutputPatternId = varRootDef.typePatternGraph.find(varRootDef.def.patterns[1]);
97
+ let cloned = varRootDef.typePatternGraph.clone([varInputPatternId, varOutputPatternId], relDef.typePatternGraph);
98
+ relDef.typePatternGraph.unify(`ref:input ${relName}(${varName})`, varRel.inputPatternId, cloned[varInputPatternId]);
99
+ relDef.typePatternGraph.unify("ref:output", varRel.outputPatternId, cloned[varOutputPatternId]);
100
+ } catch (e) {
101
+ e.message = `Type Error in definition of '${relName}' at call to '${varName}': lines ${varRel.start?.line}:${varRel.start?.column}...${varRel.end?.line}:${varRel.end?.column}):\n - ${e.message}`;
102
+ throw e;
103
+ }
104
+ const tookMs = new Date().getTime() - start;
105
+ if (tookMs > 200) {
106
+ console.log(` ---- Processing ${relName}(.., x${index}=${relDef.varRefs[index].varName}, ..) took: ${tookMs} ms`);
107
+ }
108
+ }
109
+
110
+ function propagateReferences(relName, relDef, rels) {
111
+ for (let i = 0; i < relDef.varRefs.length; i++) {
112
+ processReference(relName, relDef, rels, relDef.varRefs[i], i);
113
+ }
114
+ }
115
+
116
+ function hasSelfReference(relName, relDef) {
117
+ return relDef.varRefs.some(({ varName }) => varName === relName);
118
+ }
119
+
120
+ function resolveStrategy(scc, rels, options = {}) {
121
+ const strategy = options.strategy || "auto";
122
+ if (strategy !== "auto") {
123
+ return strategy;
124
+ }
125
+ if (scc.length === 1 && !hasSelfReference(scc[0], rels[scc[0]])) {
126
+ return "single_pass";
127
+ }
128
+ return "fixed_point";
129
+ }
130
+
131
+ function convergeSccSinglePass(scc, rels) {
132
+ const relName = scc[0];
133
+ const relDef = rels[relName];
134
+ propagateReferences(relName, relDef, rels);
135
+ rels[relName] = compactRel(relDef, relName);
136
+ return {
137
+ strategy: "single_pass",
138
+ iterations: 1,
139
+ converged: true
140
+ };
141
+ }
142
+
143
+ function convergeSccFixedPoint(scc, rels, options = {}) {
144
+ const maxNumberOfIterations = options.maxIterations || (2 + scc.length);
145
+ let converged = false;
146
+ let iterations = 0;
147
+
148
+ for (let iteration = 1; iteration <= maxNumberOfIterations; iteration++) {
149
+ iterations = iteration;
150
+ let before = JSON.stringify(scc.map( relName => {
151
+ const relDef = rels[relName];
152
+ rels[relName] = compactRel(relDef,relName);
153
+ return signature(rels[relName]);
154
+ }));
155
+
156
+ let after = JSON.stringify(scc.map( relName => {
157
+ const relDef = compactRel(rels[relName],relName);
158
+ propagateReferences(relName, relDef, rels);
159
+ rels[relName] = compactRel(relDef,relName);
160
+ return signature(rels[relName]);
161
+ }));
162
+
163
+ if (before === after) {
164
+ converged = true;
165
+ break;
166
+ }
167
+ }
168
+
169
+ if (!converged) {
170
+ const details = scc.map( relName => {
171
+ const relDef = rels[relName];
172
+ return `\x1b[94m${relName}\x1b[0m: ${signature(relDef)}`;
173
+ }).join('\n ');
174
+ console.warn(`WARNING ⚠️ : Type derivation did NOT converge after ${maxNumberOfIterations} iterations:\n ${details}\n Consider adding filter expressions when defining recursive polymorphic functions.`);
175
+ }
176
+
177
+ return {
178
+ strategy: "fixed_point",
179
+ iterations,
180
+ converged
181
+ };
182
+ }
183
+
184
+ export function convergeScc(scc, rels, options = {}) {
185
+ const strategy = resolveStrategy(scc, rels, options);
186
+ switch (strategy) {
187
+ case "single_pass":
188
+ if (scc.length !== 1) {
189
+ throw new Error(`single_pass convergence requires a singleton SCC, got: ${JSON.stringify(scc)}`);
190
+ }
191
+ return convergeSccSinglePass(scc, rels);
192
+ case "fixed_point":
193
+ return convergeSccFixedPoint(scc, rels, options);
194
+ default:
195
+ throw new Error(`Unknown convergence strategy '${strategy}'`);
196
+ }
197
+ }