@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.
- package/Code-derivation-tests/blowup.mjs +3 -3
- package/Code-derivation-tests/bnat.mjs +2 -2
- package/Code-derivation-tests/codec-witness-pattern.mjs +50 -0
- package/Code-derivation-tests/convergence-strategy.mjs +24 -0
- package/Code-derivation-tests/dot.mjs +12 -9
- package/Code-derivation-tests/filter-syntax.mjs +21 -0
- package/Code-derivation-tests/loop.mjs +8 -2
- package/Code-derivation-tests/nat.mjs +1 -1
- package/Code-derivation-tests/not-not.mjs +3 -3
- package/Code-derivation-tests/pattern-envelope.mjs +68 -0
- package/Code-derivation-tests/rlz.mjs +2 -2
- package/{BIG_PICTURE.md → DOCS/BIG_PICTURE.md} +103 -50
- package/DOCS/REPL.md +191 -0
- package/DOCS/book/01-introduction.md +51 -0
- package/DOCS/book/02-syntax-and-values.md +111 -0
- package/DOCS/book/03-types-as-finite-automata.md +156 -0
- package/DOCS/book/04-partial-functions-and-composition.md +146 -0
- package/DOCS/book/05-typing-filters-and-normalization.md +111 -0
- package/DOCS/book.md +54 -0
- package/README.md +204 -508
- package/TypeGraph.mjs +43 -0
- package/TypePatternGraph.mjs +251 -0
- package/Value.mjs +192 -0
- package/augmentation.mjs +198 -0
- package/codecs/BINARY_FORMAT.md +53 -0
- package/codecs/POLYMORPHIC_BINARY_FORMAT.md +303 -0
- package/codecs/README.md +215 -0
- package/codecs/example-pipeline.mjs +149 -0
- package/codecs/ieee.mjs +140 -0
- package/codecs/int.mjs +104 -0
- package/codecs/json-codec.mjs +215 -0
- package/codecs/json.mjs +40 -0
- package/codecs/k-parse.mjs +90 -0
- package/codecs/k-pattern.mjs +75 -0
- package/codecs/k-print.mjs +46 -0
- package/codecs/runtime/codec.mjs +768 -0
- package/codecs/runtime/ieee-pattern.mjs +30 -0
- package/codecs/runtime/pattern-json.mjs +96 -0
- package/codecs/runtime/prefix-codec.mjs +425 -0
- package/codecs/runtime/show-value.mjs +68 -0
- package/codecs/runtime/typeFromValue.mjs +34 -0
- package/codecs/runtime/unicode-string.mjs +220 -0
- package/codecs/show.mjs +41 -0
- package/codecs/string-codec.mjs +80 -0
- package/codecs/unit.mjs +51 -0
- package/codecs/utf16.mjs +66 -0
- package/codecs/utf8.mjs +37 -0
- package/codes.mjs +146 -44
- package/compiler.mjs +111 -0
- package/convergence.mjs +197 -0
- package/core.k +197 -0
- package/export.mjs +22 -32
- package/hash.mjs +29 -31
- package/index.mjs +41 -15
- package/k-vscode/.vscode/launch.json +14 -0
- package/k-vscode/README.md +65 -0
- package/k-vscode/language-configuration.json +42 -0
- package/k-vscode/package.json +57 -0
- package/k-vscode/snippets/k.json +92 -0
- package/k-vscode/syntaxes/k.tmLanguage.json +243 -0
- package/k.mjs +56 -54
- package/object.mjs +517 -0
- package/objects/README.md +38 -0
- package/objects/compile-lib.mjs +42 -0
- package/objects/compile.mjs +42 -0
- package/objects/decompile.mjs +32 -0
- package/package.json +18 -8
- package/parser.jison +27 -46
- package/parser.mjs +68 -100
- package/pretty.mjs +21 -40
- package/repl2.mjs +652 -0
- package/run.mjs +264 -231
- package/symbol-table.mjs +3 -17
- package/test-fingerprint.mjs +7 -7
- package/test-hash-fuzz.mjs +107 -0
- package/test-hash-normalization.mjs +134 -0
- package/test-k-object.mjs +74 -0
- package/test-repl2.mjs +95 -0
- package/test.mjs +45 -70
- package/tests.sh +10 -1
- package/typing.mjs +129 -676
- package/unification.mjs +157 -0
- package/valueIO.mjs +54 -0
- package/valueParser.jison +143 -0
- package/valueParser.mjs +713 -0
- package/Code-derivation-tests/built-in.mjs +0 -19
- package/Code-derivation-tests/vector.mjs +0 -19
- package/Filters/bug.k +0 -3
- package/Filters/composing.k +0 -15
- package/Filters/filter.k +0 -9
- package/TODO.md +0 -53
- package/TYPING.md +0 -365
- package/k-repl.cast +0 -1913
- package/k.cast +0 -618
- package/my-objects.json +0 -6
- package/patterns.mjs +0 -543
- package/repl.mjs +0 -213
- package/test-filters.mjs +0 -45
- 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
|
-
|
|
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
|
-
|
|
17
|
-
var i = 0;
|
|
13
|
+
let i = 0;
|
|
18
14
|
const Q = [[code,i]];
|
|
19
15
|
const D = {[code]: i++};
|
|
20
|
-
|
|
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
|
-
|
|
73
|
+
const seed = names.includes(unitCode) ? unitCode : names[0];
|
|
74
|
+
classes[seed] = names;
|
|
96
75
|
const representatives = names.reduce((representatives, name) => {
|
|
97
|
-
representatives[name] =
|
|
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 "
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
168
|
-
const
|
|
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 } =
|
|
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
|
-
|
|
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
|
-
|
|
214
|
-
|
|
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 };
|
package/convergence.mjs
ADDED
|
@@ -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
|
+
}
|