@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
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* K pattern graph runtime helpers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Product, Variant } from "../../Value.mjs";
|
|
6
|
+
import { TypePatternGraph } from "../../TypePatternGraph.mjs";
|
|
7
|
+
import { getCompressed } from "../../typing.mjs";
|
|
8
|
+
import { finalize as finalizeCodes } from "../../codes.mjs";
|
|
9
|
+
import { patternToPropertyList, propertyListToPattern } from "./pattern-json.mjs";
|
|
10
|
+
|
|
11
|
+
const NODE_KIND = Object.freeze({
|
|
12
|
+
ANY: 0,
|
|
13
|
+
OPEN_PRODUCT: 1,
|
|
14
|
+
OPEN_UNION: 2,
|
|
15
|
+
CLOSED_PRODUCT: 3,
|
|
16
|
+
CLOSED_UNION: 4
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
function getNodeKindFromType(typeInfo) {
|
|
20
|
+
switch (typeInfo.code) {
|
|
21
|
+
case "product":
|
|
22
|
+
return NODE_KIND.CLOSED_PRODUCT;
|
|
23
|
+
case "union":
|
|
24
|
+
return NODE_KIND.CLOSED_UNION;
|
|
25
|
+
case "ref":
|
|
26
|
+
throw new Error("Unexpected unresolved ref in closed pattern derivation");
|
|
27
|
+
default:
|
|
28
|
+
throw new Error(`Unsupported type code: ${typeInfo.code}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resolveConcreteType(typeInfo, resolveType) {
|
|
33
|
+
if (typeof typeInfo === "string") {
|
|
34
|
+
const resolved = resolveType(typeInfo);
|
|
35
|
+
if (!resolved) throw new Error(`Unknown type: ${typeInfo}`);
|
|
36
|
+
return resolved;
|
|
37
|
+
}
|
|
38
|
+
if (!typeInfo || typeof typeInfo !== "object") {
|
|
39
|
+
throw new Error(`Invalid type info: ${typeInfo}`);
|
|
40
|
+
}
|
|
41
|
+
if (typeInfo.code === "ref") {
|
|
42
|
+
return resolveConcreteType(typeInfo.ref, resolveType);
|
|
43
|
+
}
|
|
44
|
+
return typeInfo;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function deriveClosedPattern(rootTypeName, rootTypeInfo, resolveType) {
|
|
48
|
+
const keyToId = new Map();
|
|
49
|
+
const nodes = [];
|
|
50
|
+
|
|
51
|
+
function visit(typeName, typeInfo) {
|
|
52
|
+
const key = typeName || JSON.stringify(typeInfo);
|
|
53
|
+
if (keyToId.has(key)) return keyToId.get(key);
|
|
54
|
+
|
|
55
|
+
const concrete = resolveConcreteType(typeInfo ?? typeName, resolveType);
|
|
56
|
+
const nodeId = nodes.length;
|
|
57
|
+
keyToId.set(key, nodeId);
|
|
58
|
+
const node = { kind: getNodeKindFromType(concrete), edges: [] };
|
|
59
|
+
nodes.push(node);
|
|
60
|
+
|
|
61
|
+
const edgeMap = concrete[concrete.code];
|
|
62
|
+
for (const label of Object.keys(edgeMap).sort()) {
|
|
63
|
+
const refName = edgeMap[label];
|
|
64
|
+
const targetInfo = resolveConcreteType(refName, resolveType);
|
|
65
|
+
const targetId = visit(refName, targetInfo);
|
|
66
|
+
node.edges.push({ label, target: targetId });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return nodeId;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
visit(rootTypeName, rootTypeInfo);
|
|
73
|
+
|
|
74
|
+
const dictionary = [...new Set(nodes.flatMap((node) => node.edges.map((edge) => edge.label)))].sort();
|
|
75
|
+
const symbolIds = new Map(dictionary.map((label, index) => [label, index]));
|
|
76
|
+
|
|
77
|
+
return collapseClosedNodes({
|
|
78
|
+
dictionary,
|
|
79
|
+
nodes: nodes.map((node) => ({
|
|
80
|
+
kind: node.kind,
|
|
81
|
+
edges: node.edges.map((edge) => ({
|
|
82
|
+
symbolId: symbolIds.get(edge.label),
|
|
83
|
+
label: edge.label,
|
|
84
|
+
target: edge.target
|
|
85
|
+
}))
|
|
86
|
+
}))
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function exportPatternGraph(typePatternGraph, rootPatternId) {
|
|
91
|
+
const tpg = typePatternGraph;
|
|
92
|
+
const cache = new Map();
|
|
93
|
+
|
|
94
|
+
function exportFromType(typeName) {
|
|
95
|
+
const key = `type:${typeName}`;
|
|
96
|
+
if (cache.has(key)) return cache.get(key);
|
|
97
|
+
|
|
98
|
+
const typeInfo = tpg.findCode(typeName);
|
|
99
|
+
if (!typeInfo || !typeInfo.code) {
|
|
100
|
+
throw new Error(`Unknown type while exporting pattern graph: ${typeName}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const node = {
|
|
104
|
+
kind: typeInfo.code === "product" ? NODE_KIND.CLOSED_PRODUCT : NODE_KIND.CLOSED_UNION,
|
|
105
|
+
edges: []
|
|
106
|
+
};
|
|
107
|
+
cache.set(key, node);
|
|
108
|
+
|
|
109
|
+
const edgeMap = typeInfo[typeInfo.code] || {};
|
|
110
|
+
for (const label of Object.keys(edgeMap).sort()) {
|
|
111
|
+
node.edges.push({ label, target: exportAny(edgeMap[label]) });
|
|
112
|
+
}
|
|
113
|
+
return node;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function exportFromPattern(patternId) {
|
|
117
|
+
const repId = tpg.find(patternId);
|
|
118
|
+
const key = `pattern:${repId}`;
|
|
119
|
+
if (cache.has(key)) return cache.get(key);
|
|
120
|
+
|
|
121
|
+
const pattern = tpg.get_pattern(repId);
|
|
122
|
+
if (!pattern) {
|
|
123
|
+
throw new Error(`Unknown pattern node ${patternId}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (pattern.pattern === "type") {
|
|
127
|
+
return exportFromType(pattern.type);
|
|
128
|
+
}
|
|
129
|
+
if (pattern.pattern === "()") {
|
|
130
|
+
throw new Error("Pattern node '()' is not supported by the binary format");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const kind = (() => {
|
|
134
|
+
switch (pattern.pattern) {
|
|
135
|
+
case "(...)": return NODE_KIND.ANY;
|
|
136
|
+
case "{...}": return NODE_KIND.OPEN_PRODUCT;
|
|
137
|
+
case "<...>": return NODE_KIND.OPEN_UNION;
|
|
138
|
+
case "{}": return NODE_KIND.CLOSED_PRODUCT;
|
|
139
|
+
case "<>": return NODE_KIND.CLOSED_UNION;
|
|
140
|
+
default:
|
|
141
|
+
throw new Error(`Unsupported pattern node kind '${pattern.pattern}'`);
|
|
142
|
+
}
|
|
143
|
+
})();
|
|
144
|
+
|
|
145
|
+
const node = { kind, edges: [] };
|
|
146
|
+
cache.set(key, node);
|
|
147
|
+
|
|
148
|
+
const edgeMap = tpg.edges[repId] || {};
|
|
149
|
+
for (const label of Object.keys(edgeMap).sort()) {
|
|
150
|
+
const dests = Array.from(new Set((edgeMap[label] || []).map((dst) => tpg.find(dst))));
|
|
151
|
+
if (dests.length !== 1) {
|
|
152
|
+
throw new Error(`Pattern edge '${label}' from node ${repId} must have exactly one destination`);
|
|
153
|
+
}
|
|
154
|
+
node.edges.push({ label, target: exportFromPattern(dests[0]) });
|
|
155
|
+
}
|
|
156
|
+
return node;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function exportAny(ref) {
|
|
160
|
+
if (typeof ref === "string") {
|
|
161
|
+
return exportFromType(ref);
|
|
162
|
+
}
|
|
163
|
+
return exportFromPattern(ref);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const rootNode = exportFromPattern(rootPatternId);
|
|
167
|
+
|
|
168
|
+
const discovered = [];
|
|
169
|
+
const assigned = new Map();
|
|
170
|
+
function assign(node) {
|
|
171
|
+
if (assigned.has(node)) return;
|
|
172
|
+
const id = discovered.length;
|
|
173
|
+
assigned.set(node, id);
|
|
174
|
+
discovered.push(node);
|
|
175
|
+
const edges = [...node.edges].sort((a, b) => Buffer.compare(Buffer.from(a.label, "utf8"), Buffer.from(b.label, "utf8")));
|
|
176
|
+
for (const edge of edges) {
|
|
177
|
+
assign(edge.target);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
assign(rootNode);
|
|
181
|
+
|
|
182
|
+
const dictionary = [...new Set(discovered.flatMap((node) => node.edges.map((edge) => edge.label)))].sort((a, b) =>
|
|
183
|
+
Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"))
|
|
184
|
+
);
|
|
185
|
+
const symbolIds = new Map(dictionary.map((label, index) => [label, index]));
|
|
186
|
+
|
|
187
|
+
return collapseClosedNodes({
|
|
188
|
+
dictionary,
|
|
189
|
+
nodes: discovered.map((node) => ({
|
|
190
|
+
kind: node.kind,
|
|
191
|
+
edges: [...node.edges]
|
|
192
|
+
.sort((a, b) => symbolIds.get(a.label) - symbolIds.get(b.label))
|
|
193
|
+
.map((edge) => ({
|
|
194
|
+
label: edge.label,
|
|
195
|
+
symbolId: symbolIds.get(edge.label),
|
|
196
|
+
target: assigned.get(edge.target)
|
|
197
|
+
}))
|
|
198
|
+
}))
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function normalizePattern(pattern) {
|
|
203
|
+
const dictionary = [...new Set(pattern.dictionary)].sort();
|
|
204
|
+
const symbolIds = new Map(dictionary.map((label, index) => [label, index]));
|
|
205
|
+
const nodes = pattern.nodes.map((node, index) => {
|
|
206
|
+
const edges = (node.edges || []).map((edge) => {
|
|
207
|
+
const label = edge.label ?? dictionary[edge.symbolId];
|
|
208
|
+
if (label == null) {
|
|
209
|
+
throw new Error(`Pattern node ${index} references unknown symbol`);
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
label,
|
|
213
|
+
symbolId: symbolIds.get(label),
|
|
214
|
+
target: edge.target
|
|
215
|
+
};
|
|
216
|
+
}).sort((a, b) => a.symbolId - b.symbolId);
|
|
217
|
+
|
|
218
|
+
for (let i = 1; i < edges.length; i++) {
|
|
219
|
+
if (edges[i - 1].symbolId === edges[i].symbolId) {
|
|
220
|
+
throw new Error(`Duplicate edge label in pattern node ${index}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (node.kind === NODE_KIND.ANY && edges.length > 0) {
|
|
225
|
+
throw new Error("Pattern node (...) cannot have outgoing edges");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return { kind: node.kind, edges };
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
return { dictionary, nodes };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function collapseClosedNodes(pattern) {
|
|
235
|
+
const base = normalizePattern(pattern);
|
|
236
|
+
const { nodes } = base;
|
|
237
|
+
const representative = nodes.map((_, index) => index);
|
|
238
|
+
const state = new Array(nodes.length).fill("unseen");
|
|
239
|
+
const cyclic = new Array(nodes.length).fill(false);
|
|
240
|
+
const stack = [];
|
|
241
|
+
const stackIndex = new Map();
|
|
242
|
+
const canonicalClosed = new Map();
|
|
243
|
+
|
|
244
|
+
// Collapse finite closed subtrees from the leaves up. Open nodes keep identity,
|
|
245
|
+
// and recursive closed nodes are preserved because they are not value-tree leaves.
|
|
246
|
+
function closedKey(node) {
|
|
247
|
+
return JSON.stringify([
|
|
248
|
+
node.kind,
|
|
249
|
+
node.edges.map((edge) => [edge.label, representative[edge.target]])
|
|
250
|
+
]);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function visit(nodeId) {
|
|
254
|
+
if (state[nodeId] === "done") return representative[nodeId];
|
|
255
|
+
if (state[nodeId] === "visiting") {
|
|
256
|
+
const cycleStart = stackIndex.get(nodeId);
|
|
257
|
+
if (cycleStart != null) {
|
|
258
|
+
for (let i = cycleStart; i < stack.length; i++) {
|
|
259
|
+
cyclic[stack[i]] = true;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return nodeId;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
state[nodeId] = "visiting";
|
|
266
|
+
stackIndex.set(nodeId, stack.length);
|
|
267
|
+
stack.push(nodeId);
|
|
268
|
+
const node = nodes[nodeId];
|
|
269
|
+
for (const edge of node.edges) {
|
|
270
|
+
visit(edge.target);
|
|
271
|
+
}
|
|
272
|
+
stack.pop();
|
|
273
|
+
stackIndex.delete(nodeId);
|
|
274
|
+
|
|
275
|
+
if ((node.kind === NODE_KIND.CLOSED_PRODUCT || node.kind === NODE_KIND.CLOSED_UNION) && !cyclic[nodeId]) {
|
|
276
|
+
const key = closedKey(node);
|
|
277
|
+
if (canonicalClosed.has(key)) {
|
|
278
|
+
representative[nodeId] = canonicalClosed.get(key);
|
|
279
|
+
} else {
|
|
280
|
+
canonicalClosed.set(key, nodeId);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
state[nodeId] = "done";
|
|
285
|
+
return representative[nodeId];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
visit(0);
|
|
289
|
+
|
|
290
|
+
const discovered = [];
|
|
291
|
+
const assigned = new Map();
|
|
292
|
+
|
|
293
|
+
function assign(oldNodeId) {
|
|
294
|
+
const repId = representative[oldNodeId];
|
|
295
|
+
if (assigned.has(repId)) return assigned.get(repId);
|
|
296
|
+
|
|
297
|
+
const newNodeId = discovered.length;
|
|
298
|
+
assigned.set(repId, newNodeId);
|
|
299
|
+
const node = nodes[repId];
|
|
300
|
+
const newNode = { kind: node.kind, edges: [] };
|
|
301
|
+
discovered.push(newNode);
|
|
302
|
+
newNode.edges = node.edges.map((edge) => ({
|
|
303
|
+
label: edge.label,
|
|
304
|
+
target: assign(edge.target)
|
|
305
|
+
}));
|
|
306
|
+
return newNodeId;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
assign(0);
|
|
310
|
+
|
|
311
|
+
const dictionary = [...new Set(discovered.flatMap((node) => node.edges.map((edge) => edge.label)))].sort((a, b) =>
|
|
312
|
+
Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"))
|
|
313
|
+
);
|
|
314
|
+
const symbolIds = new Map(dictionary.map((label, index) => [label, index]));
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
dictionary,
|
|
318
|
+
nodes: discovered.map((node) => ({
|
|
319
|
+
kind: node.kind,
|
|
320
|
+
edges: node.edges
|
|
321
|
+
.map((edge) => ({
|
|
322
|
+
label: edge.label,
|
|
323
|
+
symbolId: symbolIds.get(edge.label),
|
|
324
|
+
target: edge.target
|
|
325
|
+
}))
|
|
326
|
+
.sort((a, b) => a.symbolId - b.symbolId)
|
|
327
|
+
}))
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function createLocalCodeRepository() {
|
|
332
|
+
const repository = {};
|
|
333
|
+
|
|
334
|
+
function registerCodeDef(newCodes) {
|
|
335
|
+
const { codes, representatives } = finalizeCodes(newCodes);
|
|
336
|
+
Object.assign(repository, codes);
|
|
337
|
+
return representatives;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function findCode(codeName) {
|
|
341
|
+
return JSON.parse(JSON.stringify(repository[codeName] || { code: "undefined" }));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return { registerCodeDef, findCode };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function addPropertyListToPatternGraph(graph, propertyList) {
|
|
348
|
+
const kindToPattern = {
|
|
349
|
+
any: "(...)",
|
|
350
|
+
"open-product": "{...}",
|
|
351
|
+
"open-union": "<...>",
|
|
352
|
+
"closed-product": "{}",
|
|
353
|
+
"closed-union": "<>"
|
|
354
|
+
};
|
|
355
|
+
const nodes = propertyList.map(([kind]) =>
|
|
356
|
+
graph.addNewNode({ pattern: kindToPattern[kind] })
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
propertyList.forEach(([, edges], nodeIndex) => {
|
|
360
|
+
for (const [label, target] of edges) {
|
|
361
|
+
graph.edges[nodes[nodeIndex]][label] = [nodes[target]];
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
return nodes[0];
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function patternGraphFromPropertyList(propertyList) {
|
|
369
|
+
const { registerCodeDef, findCode } = createLocalCodeRepository();
|
|
370
|
+
const graph = new TypePatternGraph(registerCodeDef, findCode);
|
|
371
|
+
addPropertyListToPatternGraph(graph, propertyList);
|
|
372
|
+
return graph;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function compressPatternGraph(pattern) {
|
|
376
|
+
const propertyList = patternToPropertyList(normalizePattern(pattern));
|
|
377
|
+
return getCompressed(patternGraphFromPropertyList(propertyList));
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function canonicalizePattern(pattern) {
|
|
381
|
+
const collapsed = collapseClosedNodes(pattern);
|
|
382
|
+
const compressed = compressPatternGraph(collapsed);
|
|
383
|
+
return propertyListToPattern(patternToPropertyList(exportPatternGraph(compressed.typePatternGraph, compressed.remapping[0])));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function isSingletonPropertyList(propertyList) {
|
|
387
|
+
return propertyList.every(([kind]) => kind === "closed-product" || kind === "closed-union");
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function singletonMatchesPattern(singleton, pattern) {
|
|
391
|
+
const mapped = new Map();
|
|
392
|
+
const visiting = new Set();
|
|
393
|
+
|
|
394
|
+
function visit(patternNodeId, singletonNodeId) {
|
|
395
|
+
const mappedSingletonId = mapped.get(patternNodeId);
|
|
396
|
+
if (mappedSingletonId != null) return mappedSingletonId === singletonNodeId;
|
|
397
|
+
mapped.set(patternNodeId, singletonNodeId);
|
|
398
|
+
|
|
399
|
+
const key = `${patternNodeId}|${singletonNodeId}`;
|
|
400
|
+
if (visiting.has(key)) return true;
|
|
401
|
+
visiting.add(key);
|
|
402
|
+
|
|
403
|
+
const [patternKind, patternEdges] = pattern[patternNodeId];
|
|
404
|
+
const [singletonKind, singletonEdges] = singleton[singletonNodeId];
|
|
405
|
+
const singletonIsProduct = singletonKind === "closed-product";
|
|
406
|
+
const singletonIsUnion = singletonKind === "closed-union";
|
|
407
|
+
|
|
408
|
+
if (patternKind === "any") {
|
|
409
|
+
visiting.delete(key);
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const patternIsProduct = patternKind === "open-product" || patternKind === "closed-product";
|
|
414
|
+
const patternIsUnion = patternKind === "open-union" || patternKind === "closed-union";
|
|
415
|
+
if ((patternIsProduct && !singletonIsProduct) || (patternIsUnion && !singletonIsUnion)) {
|
|
416
|
+
visiting.delete(key);
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const singletonTargets = new Map(singletonEdges.map(([label, target]) => [label, target]));
|
|
421
|
+
if (patternKind === "closed-product" || patternKind === "closed-union") {
|
|
422
|
+
if (patternEdges.length !== singletonEdges.length) {
|
|
423
|
+
visiting.delete(key);
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
for (const [label, patternTarget] of patternEdges) {
|
|
429
|
+
const singletonTarget = singletonTargets.get(label);
|
|
430
|
+
if (singletonTarget == null || !visit(patternTarget, singletonTarget)) {
|
|
431
|
+
visiting.delete(key);
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
visiting.delete(key);
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return visit(0, 0);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function intersectPropertyListPatterns(left, right) {
|
|
444
|
+
if (!left || left.length === 0) return right;
|
|
445
|
+
if (!right || right.length === 0) return left;
|
|
446
|
+
if (left[0]?.[0] === "any") return right;
|
|
447
|
+
if (right[0]?.[0] === "any") return left;
|
|
448
|
+
if (left === right) return left;
|
|
449
|
+
|
|
450
|
+
if (isSingletonPropertyList(left) && singletonMatchesPattern(left, right)) return left;
|
|
451
|
+
if (isSingletonPropertyList(right) && singletonMatchesPattern(right, left)) return right;
|
|
452
|
+
if (JSON.stringify(left) === JSON.stringify(right)) return left;
|
|
453
|
+
|
|
454
|
+
const { registerCodeDef, findCode } = createLocalCodeRepository();
|
|
455
|
+
const graph = new TypePatternGraph(registerCodeDef, findCode);
|
|
456
|
+
const leftRoot = addPropertyListToPatternGraph(graph, patternToPropertyList(propertyListToPattern(left)));
|
|
457
|
+
const rightRoot = addPropertyListToPatternGraph(graph, patternToPropertyList(propertyListToPattern(right)));
|
|
458
|
+
|
|
459
|
+
graph.unify("pattern-intersection", leftRoot, rightRoot);
|
|
460
|
+
const root = graph.find(leftRoot);
|
|
461
|
+
const compressed = getCompressed(graph);
|
|
462
|
+
return patternToPropertyList(exportPatternGraph(compressed.typePatternGraph, compressed.remapping[root]));
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function refinePatternForValue(pattern, value) {
|
|
466
|
+
const base = normalizePattern(pattern);
|
|
467
|
+
const occurrences = new Map();
|
|
468
|
+
const extraEdges = new Map();
|
|
469
|
+
|
|
470
|
+
function pushOccurrence(patternNodeId, currentValue) {
|
|
471
|
+
if (!occurrences.has(patternNodeId)) occurrences.set(patternNodeId, []);
|
|
472
|
+
occurrences.get(patternNodeId).push(currentValue);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function pushExtra(patternNodeId, label, currentValue) {
|
|
476
|
+
if (!extraEdges.has(patternNodeId)) extraEdges.set(patternNodeId, new Map());
|
|
477
|
+
const labelMap = extraEdges.get(patternNodeId);
|
|
478
|
+
if (!labelMap.has(label)) labelMap.set(label, []);
|
|
479
|
+
labelMap.get(label).push(currentValue);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function collect(patternNodeId, currentValue) {
|
|
483
|
+
const patternNode = base.nodes[patternNodeId];
|
|
484
|
+
if (!patternNode) {
|
|
485
|
+
throw new Error(`Unknown pattern node ${patternNodeId}`);
|
|
486
|
+
}
|
|
487
|
+
pushOccurrence(patternNodeId, currentValue);
|
|
488
|
+
|
|
489
|
+
switch (patternNode.kind) {
|
|
490
|
+
case NODE_KIND.ANY:
|
|
491
|
+
return;
|
|
492
|
+
|
|
493
|
+
case NODE_KIND.CLOSED_PRODUCT:
|
|
494
|
+
case NODE_KIND.OPEN_PRODUCT: {
|
|
495
|
+
if (!(currentValue instanceof Product)) {
|
|
496
|
+
throw new Error(`Expected Product for pattern node ${patternNodeId}`);
|
|
497
|
+
}
|
|
498
|
+
const actualLabels = Object.keys(currentValue.product).sort();
|
|
499
|
+
const explicit = patternNode.edges.map((edge) => edge.label);
|
|
500
|
+
if (patternNode.kind === NODE_KIND.CLOSED_PRODUCT) {
|
|
501
|
+
if (actualLabels.length !== explicit.length || actualLabels.some((label, idx) => label !== explicit[idx])) {
|
|
502
|
+
throw new Error(`Closed product pattern does not match value fields [${actualLabels.join(", ")}]`);
|
|
503
|
+
}
|
|
504
|
+
} else {
|
|
505
|
+
for (const label of explicit) {
|
|
506
|
+
if (!(label in currentValue.product)) {
|
|
507
|
+
throw new Error(`Open product pattern is missing required field '${label}'`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const explicitTargets = new Map(patternNode.edges.map((edge) => [edge.label, edge.target]));
|
|
513
|
+
for (const label of actualLabels) {
|
|
514
|
+
if (explicitTargets.has(label)) {
|
|
515
|
+
collect(explicitTargets.get(label), currentValue.product[label]);
|
|
516
|
+
} else if (patternNode.kind === NODE_KIND.OPEN_PRODUCT) {
|
|
517
|
+
pushExtra(patternNodeId, label, currentValue.product[label]);
|
|
518
|
+
} else {
|
|
519
|
+
throw new Error(`Unexpected extra field '${label}' for closed product pattern`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
case NODE_KIND.CLOSED_UNION:
|
|
526
|
+
case NODE_KIND.OPEN_UNION: {
|
|
527
|
+
if (!(currentValue instanceof Variant)) {
|
|
528
|
+
throw new Error(`Expected Variant for pattern node ${patternNodeId}`);
|
|
529
|
+
}
|
|
530
|
+
const explicitTargets = new Map(patternNode.edges.map((edge) => [edge.label, edge.target]));
|
|
531
|
+
if (explicitTargets.has(currentValue.tag)) {
|
|
532
|
+
collect(explicitTargets.get(currentValue.tag), currentValue.value);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (patternNode.kind === NODE_KIND.OPEN_UNION) {
|
|
536
|
+
pushExtra(patternNodeId, currentValue.tag, currentValue.value);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
throw new Error(`Unexpected variant tag '${currentValue.tag}' for closed union pattern`);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
default:
|
|
543
|
+
throw new Error(`Unknown pattern node kind: ${patternNode.kind}`);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function sameSortedKeys(values, kindName, getter) {
|
|
548
|
+
if (values.length === 0) return [];
|
|
549
|
+
const expected = getter(values[0]).sort();
|
|
550
|
+
for (let i = 1; i < values.length; i++) {
|
|
551
|
+
const actual = getter(values[i]).sort();
|
|
552
|
+
if (actual.length !== expected.length || actual.some((label, idx) => label !== expected[idx])) {
|
|
553
|
+
throw new Error(`Cannot refine shared ${kindName}: observed incompatible shapes`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return expected;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function synthesizeClosed(values) {
|
|
560
|
+
if (values.length === 0) {
|
|
561
|
+
return { kind: NODE_KIND.ANY, edges: [] };
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const allProducts = values.every((node) => node instanceof Product);
|
|
565
|
+
const allVariants = values.every((node) => node instanceof Variant);
|
|
566
|
+
if (!allProducts && !allVariants) {
|
|
567
|
+
throw new Error("Cannot refine pattern from mixed product/union values");
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (allProducts) {
|
|
571
|
+
const labels = sameSortedKeys(values, "product pattern", (node) => Object.keys(node.product));
|
|
572
|
+
const node = { kind: NODE_KIND.CLOSED_PRODUCT, edges: [] };
|
|
573
|
+
for (const label of labels) {
|
|
574
|
+
node.edges.push({
|
|
575
|
+
label,
|
|
576
|
+
target: synthesizeClosed(values.map((v) => v.product[label]))
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
return node;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const tagSet = [...new Set(values.map((node) => node.tag))].sort((a, b) =>
|
|
583
|
+
Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"))
|
|
584
|
+
);
|
|
585
|
+
const node = { kind: NODE_KIND.OPEN_UNION, edges: [] };
|
|
586
|
+
for (const tag of tagSet) {
|
|
587
|
+
node.edges.push({
|
|
588
|
+
label: tag,
|
|
589
|
+
target: synthesizeClosed(values.filter((v) => v.tag === tag).map((v) => v.value))
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
return node;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
collect(0, value);
|
|
596
|
+
|
|
597
|
+
const memo = new Map();
|
|
598
|
+
|
|
599
|
+
function refineExisting(patternNodeId) {
|
|
600
|
+
if (memo.has(patternNodeId)) return memo.get(patternNodeId);
|
|
601
|
+
const baseNode = base.nodes[patternNodeId];
|
|
602
|
+
const result = { kind: baseNode.kind, edges: [] };
|
|
603
|
+
memo.set(patternNodeId, result);
|
|
604
|
+
|
|
605
|
+
if (baseNode.kind === NODE_KIND.ANY) {
|
|
606
|
+
const values = occurrences.get(patternNodeId) || [];
|
|
607
|
+
if (values.length === 0) {
|
|
608
|
+
return result;
|
|
609
|
+
}
|
|
610
|
+
const synthesized = synthesizeClosed(values);
|
|
611
|
+
result.kind = synthesized.kind;
|
|
612
|
+
result.edges = synthesized.edges;
|
|
613
|
+
return result;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (baseNode.kind === NODE_KIND.CLOSED_PRODUCT || baseNode.kind === NODE_KIND.OPEN_PRODUCT) {
|
|
617
|
+
const values = occurrences.get(patternNodeId) || [];
|
|
618
|
+
const explicit = new Map(baseNode.edges.map((edge) => [edge.label, edge.target]));
|
|
619
|
+
let labels = [...explicit.keys()].sort();
|
|
620
|
+
if (values.length > 0) {
|
|
621
|
+
const actual = sameSortedKeys(values, "product pattern", (node) => {
|
|
622
|
+
if (!(node instanceof Product)) throw new Error("Expected Product while refining product pattern");
|
|
623
|
+
return Object.keys(node.product);
|
|
624
|
+
});
|
|
625
|
+
if (baseNode.kind === NODE_KIND.CLOSED_PRODUCT) {
|
|
626
|
+
if (actual.length !== labels.length || actual.some((label, idx) => label !== labels[idx])) {
|
|
627
|
+
throw new Error("Closed product pattern does not match observed values");
|
|
628
|
+
}
|
|
629
|
+
} else {
|
|
630
|
+
for (const label of labels) {
|
|
631
|
+
if (!actual.includes(label)) {
|
|
632
|
+
throw new Error(`Open product pattern is missing required field '${label}'`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
labels = actual;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
result.edges = labels.map((label) => ({
|
|
639
|
+
label,
|
|
640
|
+
target: explicit.has(label)
|
|
641
|
+
? refineExisting(explicit.get(label))
|
|
642
|
+
: synthesizeClosed((extraEdges.get(patternNodeId)?.get(label)) || [])
|
|
643
|
+
}));
|
|
644
|
+
return result;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (baseNode.kind === NODE_KIND.CLOSED_UNION || baseNode.kind === NODE_KIND.OPEN_UNION) {
|
|
648
|
+
const values = occurrences.get(patternNodeId) || [];
|
|
649
|
+
const explicit = new Map(baseNode.edges.map((edge) => [edge.label, edge.target]));
|
|
650
|
+
let tags = [...explicit.keys()].sort((a, b) => Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8")));
|
|
651
|
+
if (values.length > 0) {
|
|
652
|
+
const observed = [...new Set(values.map((node) => {
|
|
653
|
+
if (!(node instanceof Variant)) throw new Error("Expected Variant while refining union pattern");
|
|
654
|
+
return node.tag;
|
|
655
|
+
}))].sort((a, b) => Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8")));
|
|
656
|
+
if (baseNode.kind === NODE_KIND.CLOSED_UNION) {
|
|
657
|
+
for (const tag of observed) {
|
|
658
|
+
if (!explicit.has(tag)) {
|
|
659
|
+
throw new Error(`Closed union pattern does not match observed tag '${tag}'`);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
} else {
|
|
663
|
+
tags = [...new Set([...tags, ...observed])].sort((a, b) => Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8")));
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
result.edges = tags.map((label) => ({
|
|
667
|
+
label,
|
|
668
|
+
target: explicit.has(label)
|
|
669
|
+
? refineExisting(explicit.get(label))
|
|
670
|
+
: synthesizeClosed((extraEdges.get(patternNodeId)?.get(label)) || [])
|
|
671
|
+
}));
|
|
672
|
+
return result;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
throw new Error(`Unknown pattern node kind: ${baseNode.kind}`);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const root = refineExisting(0);
|
|
679
|
+
|
|
680
|
+
const discovered = [];
|
|
681
|
+
const assigned = new Map();
|
|
682
|
+
function assign(node) {
|
|
683
|
+
if (assigned.has(node)) return;
|
|
684
|
+
const id = discovered.length;
|
|
685
|
+
assigned.set(node, id);
|
|
686
|
+
discovered.push(node);
|
|
687
|
+
const edges = [...(node.edges || [])].sort((a, b) => Buffer.compare(Buffer.from(a.label, "utf8"), Buffer.from(b.label, "utf8")));
|
|
688
|
+
for (const edge of edges) {
|
|
689
|
+
assign(edge.target);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
assign(root);
|
|
693
|
+
|
|
694
|
+
const dictionary = [...new Set(discovered.flatMap((node) => (node.edges || []).map((edge) => edge.label)))].sort((a, b) =>
|
|
695
|
+
Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"))
|
|
696
|
+
);
|
|
697
|
+
const symbolIds = new Map(dictionary.map((label, index) => [label, index]));
|
|
698
|
+
|
|
699
|
+
return canonicalizePattern({
|
|
700
|
+
dictionary,
|
|
701
|
+
nodes: discovered.map((node) => ({
|
|
702
|
+
kind: node.kind,
|
|
703
|
+
edges: [...(node.edges || [])]
|
|
704
|
+
.sort((a, b) => symbolIds.get(a.label) - symbolIds.get(b.label))
|
|
705
|
+
.map((edge) => ({
|
|
706
|
+
label: edge.label,
|
|
707
|
+
symbolId: symbolIds.get(edge.label),
|
|
708
|
+
target: assigned.get(edge.target)
|
|
709
|
+
}))
|
|
710
|
+
}))
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function coerceValueForPattern(pattern, value) {
|
|
715
|
+
const base = normalizePattern(pattern);
|
|
716
|
+
|
|
717
|
+
function coerce(patternNodeId, currentValue) {
|
|
718
|
+
const patternNode = base.nodes[patternNodeId];
|
|
719
|
+
if (!patternNode) {
|
|
720
|
+
throw new Error(`Unknown pattern node ${patternNodeId}`);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
switch (patternNode.kind) {
|
|
724
|
+
case NODE_KIND.ANY:
|
|
725
|
+
return currentValue;
|
|
726
|
+
|
|
727
|
+
case NODE_KIND.CLOSED_PRODUCT:
|
|
728
|
+
case NODE_KIND.OPEN_PRODUCT: {
|
|
729
|
+
let productValue = currentValue;
|
|
730
|
+
if (currentValue instanceof Variant) {
|
|
731
|
+
productValue = new Product({ [currentValue.tag]: currentValue.value });
|
|
732
|
+
}
|
|
733
|
+
if (!(productValue instanceof Product)) {
|
|
734
|
+
throw new Error(`Expected Product for pattern node ${patternNodeId}`);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const explicitTargets = new Map(patternNode.edges.map((edge) => [edge.label, edge.target]));
|
|
738
|
+
const product = {};
|
|
739
|
+
for (const [label, childValue] of Object.entries(productValue.product)) {
|
|
740
|
+
product[label] = explicitTargets.has(label)
|
|
741
|
+
? coerce(explicitTargets.get(label), childValue)
|
|
742
|
+
: childValue;
|
|
743
|
+
}
|
|
744
|
+
return new Product(product);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
case NODE_KIND.CLOSED_UNION:
|
|
748
|
+
case NODE_KIND.OPEN_UNION: {
|
|
749
|
+
if (!(currentValue instanceof Variant)) {
|
|
750
|
+
throw new Error(`Expected Variant for pattern node ${patternNodeId}`);
|
|
751
|
+
}
|
|
752
|
+
const target = patternNode.edges.find((edge) => edge.label === currentValue.tag)?.target;
|
|
753
|
+
return new Variant(
|
|
754
|
+
currentValue.tag,
|
|
755
|
+
target == null ? currentValue.value : coerce(target, currentValue.value)
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
default:
|
|
760
|
+
throw new Error(`Unknown pattern node kind: ${patternNode.kind}`);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
return coerce(0, value);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
export { deriveClosedPattern, exportPatternGraph, refinePatternForValue, coerceValueForPattern, canonicalizePattern, intersectPropertyListPatterns, NODE_KIND };
|
|
768
|
+
export default { deriveClosedPattern, exportPatternGraph, refinePatternForValue, coerceValueForPattern, canonicalizePattern, intersectPropertyListPatterns, NODE_KIND };
|