@fraczak/k 5.1.1 → 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,30 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { parse as parseScript } from "../../parser.mjs";
|
|
3
|
+
import codes from "../../codes.mjs";
|
|
4
|
+
import { deriveClosedPattern } from "./codec.mjs";
|
|
5
|
+
import { patternToPropertyList } from "./pattern-json.mjs";
|
|
6
|
+
|
|
7
|
+
const IEEE_SCRIPT = fs.readFileSync(new URL("../../Examples/ieee.k", import.meta.url), "utf8");
|
|
8
|
+
|
|
9
|
+
function loadFloat64Pattern() {
|
|
10
|
+
const { defs } = parseScript(IEEE_SCRIPT);
|
|
11
|
+
const { codes: finalizedCodes, representatives } = codes.finalize(defs.codes);
|
|
12
|
+
const typeName = representatives.float64 || "float64";
|
|
13
|
+
const typeInfo = finalizedCodes[typeName];
|
|
14
|
+
if (!typeInfo) {
|
|
15
|
+
throw new Error("Could not resolve float64 type from Examples/ieee.k");
|
|
16
|
+
}
|
|
17
|
+
const resolveType = (name) => {
|
|
18
|
+
const type = finalizedCodes[name];
|
|
19
|
+
if (!type) {
|
|
20
|
+
throw new Error(`Unknown IEEE helper type '${name}'`);
|
|
21
|
+
}
|
|
22
|
+
return type;
|
|
23
|
+
};
|
|
24
|
+
return patternToPropertyList(deriveClosedPattern(typeName, typeInfo, resolveType));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const FLOAT64_PATTERN = loadFloat64Pattern();
|
|
28
|
+
|
|
29
|
+
export { FLOAT64_PATTERN, loadFloat64Pattern };
|
|
30
|
+
export default { FLOAT64_PATTERN, loadFloat64Pattern };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const NODE_KIND_TO_NAME = Object.freeze({
|
|
2
|
+
0: "any",
|
|
3
|
+
1: "open-product",
|
|
4
|
+
2: "open-union",
|
|
5
|
+
3: "closed-product",
|
|
6
|
+
4: "closed-union"
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const NAME_TO_NODE_KIND = Object.freeze(
|
|
10
|
+
Object.fromEntries(Object.entries(NODE_KIND_TO_NAME).map(([kind, name]) => [name, Number(kind)]))
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
function patternToPropertyList(pattern) {
|
|
14
|
+
if (!pattern || !Array.isArray(pattern.nodes)) {
|
|
15
|
+
throw new Error("Pattern must be an object with a nodes array");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return pattern.nodes.map((node, index) => {
|
|
19
|
+
const kind = NODE_KIND_TO_NAME[node.kind];
|
|
20
|
+
if (!kind) {
|
|
21
|
+
throw new Error(`Unknown pattern node kind ${node.kind} at node ${index}`);
|
|
22
|
+
}
|
|
23
|
+
const edges = (node.edges || []).map((edge) => {
|
|
24
|
+
if (typeof edge.label !== "string") {
|
|
25
|
+
throw new Error(`Pattern node ${index} has an edge without a string label`);
|
|
26
|
+
}
|
|
27
|
+
if (!Number.isInteger(edge.target) || edge.target < 0 || edge.target >= pattern.nodes.length) {
|
|
28
|
+
throw new Error(`Pattern node ${index} has invalid edge target ${edge.target}`);
|
|
29
|
+
}
|
|
30
|
+
return [edge.label, edge.target];
|
|
31
|
+
});
|
|
32
|
+
return [kind, edges];
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function propertyListToPattern(propertyList) {
|
|
37
|
+
if (!Array.isArray(propertyList)) {
|
|
38
|
+
throw new Error("Pattern property list must be an array");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const dictionary = [...new Set(propertyList.flatMap((entry) => {
|
|
42
|
+
if (!Array.isArray(entry) || entry.length !== 2 || !Array.isArray(entry[1])) return [];
|
|
43
|
+
return entry[1].map((edge) => edge[0]);
|
|
44
|
+
}))].sort((a, b) => Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8")));
|
|
45
|
+
const symbolIds = new Map(dictionary.map((label, index) => [label, index]));
|
|
46
|
+
|
|
47
|
+
const nodes = propertyList.map((entry, index) => {
|
|
48
|
+
if (!Array.isArray(entry) || entry.length !== 2) {
|
|
49
|
+
throw new Error(`Pattern node ${index} must be [kind, edges]`);
|
|
50
|
+
}
|
|
51
|
+
const [kindName, rawEdges] = entry;
|
|
52
|
+
const kind = NAME_TO_NODE_KIND[kindName];
|
|
53
|
+
if (kind == null) {
|
|
54
|
+
throw new Error(`Unknown pattern node kind '${kindName}' at node ${index}`);
|
|
55
|
+
}
|
|
56
|
+
if (!Array.isArray(rawEdges)) {
|
|
57
|
+
throw new Error(`Pattern node ${index} edges must be an array`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const edges = rawEdges.map((edge, edgeIndex) => {
|
|
61
|
+
if (!Array.isArray(edge) || edge.length !== 2) {
|
|
62
|
+
throw new Error(`Pattern node ${index} edge ${edgeIndex} must be [label, target]`);
|
|
63
|
+
}
|
|
64
|
+
const [label, target] = edge;
|
|
65
|
+
if (typeof label !== "string") {
|
|
66
|
+
throw new Error(`Pattern node ${index} edge ${edgeIndex} label must be a string`);
|
|
67
|
+
}
|
|
68
|
+
if (!Number.isInteger(target) || target < 0 || target >= propertyList.length) {
|
|
69
|
+
throw new Error(`Pattern node ${index} edge ${edgeIndex} has invalid target ${target}`);
|
|
70
|
+
}
|
|
71
|
+
return { label, target };
|
|
72
|
+
}).sort((a, b) => Buffer.compare(Buffer.from(a.label, "utf8"), Buffer.from(b.label, "utf8")));
|
|
73
|
+
|
|
74
|
+
for (let i = 1; i < edges.length; i++) {
|
|
75
|
+
if (edges[i - 1].label === edges[i].label) {
|
|
76
|
+
throw new Error(`Pattern node ${index} has duplicate edge label '${edges[i].label}'`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (kind === NAME_TO_NODE_KIND.any && edges.length > 0) {
|
|
80
|
+
throw new Error("Pattern node 'any' cannot have outgoing edges");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
kind,
|
|
85
|
+
edges: edges.map((edge) => ({ ...edge, symbolId: symbolIds.get(edge.label) }))
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
dictionary,
|
|
91
|
+
nodes
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export { patternToPropertyList, propertyListToPattern, NODE_KIND_TO_NAME, NAME_TO_NODE_KIND };
|
|
96
|
+
export default { patternToPropertyList, propertyListToPattern, NODE_KIND_TO_NAME, NAME_TO_NODE_KIND };
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import { Product, Variant, withPattern } from "../../Value.mjs";
|
|
2
|
+
import { refinePatternForValue, coerceValueForPattern, NODE_KIND } from "./codec.mjs";
|
|
3
|
+
import { patternToPropertyList, propertyListToPattern } from "./pattern-json.mjs";
|
|
4
|
+
import { textToStringValue, stringValueToText } from "./unicode-string.mjs";
|
|
5
|
+
|
|
6
|
+
const CORE_PATTERN_PROPERTY_LIST = [
|
|
7
|
+
["closed-union", [["cons", 1], ["nil", 3]]],
|
|
8
|
+
["closed-product", [["car", 2], ["cdr", 0]]],
|
|
9
|
+
["closed-union", [["any", 3], ["closed-product", 4], ["closed-union", 4], ["open-product", 4], ["open-union", 4]]],
|
|
10
|
+
["closed-product", []],
|
|
11
|
+
["closed-union", [["cons", 5], ["nil", 3]]],
|
|
12
|
+
["closed-product", [["car", 6], ["cdr", 4]]],
|
|
13
|
+
["closed-product", [["label", 7], ["target", 25]]],
|
|
14
|
+
["closed-union", [["cons", 8], ["nil", 3]]],
|
|
15
|
+
["closed-product", [["car", 9], ["cdr", 7]]],
|
|
16
|
+
["closed-union", [["ascii", 10], ["bmp_common", 12], ["bmp_private_use", 17], ["plane0", 20], ["supplementary_plane1", 21], ["supplementary_planes2_16", 22]]],
|
|
17
|
+
["closed-product", [["0", 11], ["1", 11], ["2", 11], ["3", 11], ["4", 11], ["5", 11], ["6", 11]]],
|
|
18
|
+
["closed-union", [["0", 3], ["1", 3]]],
|
|
19
|
+
["closed-product", [["hi", 13], ["lo", 16]]],
|
|
20
|
+
["closed-union", [["h08_0F", 14], ["h10_7F", 10], ["h80_CF", 10], ["hD0_D7", 14], ["hF9", 3], ["hFA_FB", 15], ["hFC_FD", 15], ["hFE_FF", 15]]],
|
|
21
|
+
["closed-product", [["0", 11], ["1", 11], ["2", 11]]],
|
|
22
|
+
["closed-product", [["0", 11]]],
|
|
23
|
+
["closed-product", [["0", 11], ["1", 11], ["2", 11], ["3", 11], ["4", 11], ["5", 11], ["6", 11], ["7", 11]]],
|
|
24
|
+
["closed-product", [["hi", 18], ["lo", 16]]],
|
|
25
|
+
["closed-union", [["hE0_EF", 19], ["hF0_F7", 14], ["hF8", 3]]],
|
|
26
|
+
["closed-product", [["0", 11], ["1", 11], ["2", 11], ["3", 11]]],
|
|
27
|
+
["closed-product", [["0", 11], ["1", 11], ["10", 11], ["2", 11], ["3", 11], ["4", 11], ["5", 11], ["6", 11], ["7", 11], ["8", 11], ["9", 11]]],
|
|
28
|
+
["closed-product", [["lo", 16], ["mid", 16]]],
|
|
29
|
+
["closed-product", [["lo", 16], ["mid", 16], ["plane", 23]]],
|
|
30
|
+
["closed-union", [["p02_03", 15], ["p04_07", 24], ["p08_0F", 14], ["p10", 3]]],
|
|
31
|
+
["closed-product", [["0", 11], ["1", 11]]],
|
|
32
|
+
["closed-union", [["0", 25], ["1", 25], ["_", 3]]]
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const CORE_PATTERN_PATTERN = propertyListToPattern(CORE_PATTERN_PROPERTY_LIST);
|
|
36
|
+
const UNIT = new Product({});
|
|
37
|
+
|
|
38
|
+
class BitWriter {
|
|
39
|
+
constructor() {
|
|
40
|
+
this.bytes = [];
|
|
41
|
+
this.current = 0;
|
|
42
|
+
this.bitCount = 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
writeBit(bit) {
|
|
46
|
+
this.current = (this.current << 1) | (bit ? 1 : 0);
|
|
47
|
+
this.bitCount += 1;
|
|
48
|
+
if (this.bitCount === 8) {
|
|
49
|
+
this.bytes.push(this.current);
|
|
50
|
+
this.current = 0;
|
|
51
|
+
this.bitCount = 0;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
writeBits(value, width) {
|
|
56
|
+
if (!Number.isInteger(width) || width < 0) {
|
|
57
|
+
throw new Error(`Invalid bit width ${width}`);
|
|
58
|
+
}
|
|
59
|
+
if (width === 0) return;
|
|
60
|
+
if (!Number.isInteger(value) || value < 0 || value >= 2 ** width) {
|
|
61
|
+
throw new Error(`Value ${value} does not fit in ${width} bits`);
|
|
62
|
+
}
|
|
63
|
+
for (let i = width - 1; i >= 0; i--) {
|
|
64
|
+
this.writeBit((value >> i) & 1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
toBuffer() {
|
|
69
|
+
if (this.bitCount > 0) {
|
|
70
|
+
this.bytes.push(this.current << (8 - this.bitCount));
|
|
71
|
+
}
|
|
72
|
+
return Buffer.from(this.bytes);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
class BitReader {
|
|
77
|
+
constructor(buffer) {
|
|
78
|
+
this.buffer = buffer;
|
|
79
|
+
this.byteOffset = 0;
|
|
80
|
+
this.bitOffset = 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
readBit() {
|
|
84
|
+
if (this.byteOffset >= this.buffer.length) {
|
|
85
|
+
throw new Error("Unexpected end of bitstream");
|
|
86
|
+
}
|
|
87
|
+
const byte = this.buffer[this.byteOffset];
|
|
88
|
+
const bit = (byte >> (7 - this.bitOffset)) & 1;
|
|
89
|
+
this.bitOffset += 1;
|
|
90
|
+
if (this.bitOffset === 8) {
|
|
91
|
+
this.byteOffset += 1;
|
|
92
|
+
this.bitOffset = 0;
|
|
93
|
+
}
|
|
94
|
+
return bit;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
readBits(width) {
|
|
98
|
+
let value = 0;
|
|
99
|
+
for (let i = 0; i < width; i++) {
|
|
100
|
+
value = (value << 1) | this.readBit();
|
|
101
|
+
}
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
assertZeroPadding() {
|
|
106
|
+
while (this.byteOffset < this.buffer.length) {
|
|
107
|
+
if (this.readBit() !== 0) {
|
|
108
|
+
throw new Error("Non-zero trailing padding bits");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function choiceWidth(cardinality) {
|
|
115
|
+
if (!Number.isInteger(cardinality) || cardinality < 0) {
|
|
116
|
+
throw new Error(`Invalid cardinality ${cardinality}`);
|
|
117
|
+
}
|
|
118
|
+
if (cardinality <= 1) return 0;
|
|
119
|
+
return Math.ceil(Math.log2(cardinality));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function encodeNode(writer, value, pattern, patternNodeId) {
|
|
123
|
+
const patternNode = pattern.nodes[patternNodeId];
|
|
124
|
+
if (!patternNode) {
|
|
125
|
+
throw new Error(`Unknown pattern node ${patternNodeId}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
switch (patternNode.kind) {
|
|
129
|
+
case NODE_KIND.ANY:
|
|
130
|
+
throw new Error("Pattern node 'any' is not directly encodable");
|
|
131
|
+
|
|
132
|
+
case NODE_KIND.OPEN_PRODUCT:
|
|
133
|
+
case NODE_KIND.CLOSED_PRODUCT: {
|
|
134
|
+
if (!(value instanceof Product)) {
|
|
135
|
+
throw new Error(`Expected Product for pattern node ${patternNodeId}`);
|
|
136
|
+
}
|
|
137
|
+
const actual = Object.keys(value.product).sort((a, b) => Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8")));
|
|
138
|
+
const expected = patternNode.edges.map((edge) => edge.label);
|
|
139
|
+
if (actual.length !== expected.length || actual.some((label, i) => label !== expected[i])) {
|
|
140
|
+
throw new Error(`Product fields do not match pattern node ${patternNodeId}`);
|
|
141
|
+
}
|
|
142
|
+
for (const edge of patternNode.edges) {
|
|
143
|
+
encodeNode(writer, value.product[edge.label], pattern, edge.target);
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
case NODE_KIND.OPEN_UNION:
|
|
149
|
+
case NODE_KIND.CLOSED_UNION: {
|
|
150
|
+
if (!(value instanceof Variant)) {
|
|
151
|
+
throw new Error(`Expected Variant for pattern node ${patternNodeId}`);
|
|
152
|
+
}
|
|
153
|
+
const tagOrdinal = patternNode.edges.findIndex((edge) => edge.label === value.tag);
|
|
154
|
+
if (tagOrdinal === -1) {
|
|
155
|
+
throw new Error(`Variant tag '${value.tag}' is not present in pattern node ${patternNodeId}`);
|
|
156
|
+
}
|
|
157
|
+
const width = choiceWidth(patternNode.edges.length);
|
|
158
|
+
writer.writeBits(tagOrdinal, width);
|
|
159
|
+
encodeNode(writer, value.value, pattern, patternNode.edges[tagOrdinal].target);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
default:
|
|
164
|
+
throw new Error(`Unsupported pattern node kind ${patternNode.kind}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function decodeNode(reader, pattern, patternNodeId) {
|
|
169
|
+
const patternNode = pattern.nodes[patternNodeId];
|
|
170
|
+
if (!patternNode) {
|
|
171
|
+
throw new Error(`Unknown pattern node ${patternNodeId}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
switch (patternNode.kind) {
|
|
175
|
+
case NODE_KIND.ANY:
|
|
176
|
+
throw new Error("Pattern node 'any' is not directly decodable");
|
|
177
|
+
|
|
178
|
+
case NODE_KIND.OPEN_PRODUCT:
|
|
179
|
+
case NODE_KIND.CLOSED_PRODUCT: {
|
|
180
|
+
const product = {};
|
|
181
|
+
for (const edge of patternNode.edges) {
|
|
182
|
+
product[edge.label] = decodeNode(reader, pattern, edge.target);
|
|
183
|
+
}
|
|
184
|
+
return new Product(product);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
case NODE_KIND.OPEN_UNION:
|
|
188
|
+
case NODE_KIND.CLOSED_UNION: {
|
|
189
|
+
if (patternNode.edges.length === 0) {
|
|
190
|
+
throw new Error(`Union pattern node ${patternNodeId} has no tags`);
|
|
191
|
+
}
|
|
192
|
+
const width = choiceWidth(patternNode.edges.length);
|
|
193
|
+
const tagOrdinal = reader.readBits(width);
|
|
194
|
+
if (tagOrdinal >= patternNode.edges.length) {
|
|
195
|
+
throw new Error(`Choice ${tagOrdinal} is out of range for pattern node ${patternNodeId}`);
|
|
196
|
+
}
|
|
197
|
+
const edge = patternNode.edges[tagOrdinal];
|
|
198
|
+
return new Variant(edge.label, decodeNode(reader, pattern, edge.target));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
default:
|
|
202
|
+
throw new Error(`Unsupported pattern node kind ${patternNode.kind}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function preparePatternAndValue(value, propertyList) {
|
|
207
|
+
const valuePattern = propertyList || value.pattern;
|
|
208
|
+
const basePattern = valuePattern ? propertyListToPattern(valuePattern) : deriveClosedPatternFromValue(value);
|
|
209
|
+
const coercedValue = valuePattern ? coerceValueForPattern(basePattern, value) : value;
|
|
210
|
+
const refinedPattern = refinePatternForValue(basePattern, coercedValue);
|
|
211
|
+
return { coercedValue, refinedPattern, propertyList: patternToPropertyList(refinedPattern) };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function deriveClosedPatternFromValue(value) {
|
|
215
|
+
const nodes = [];
|
|
216
|
+
|
|
217
|
+
function visit(node) {
|
|
218
|
+
const nodeId = nodes.length;
|
|
219
|
+
if (node instanceof Product) {
|
|
220
|
+
const placeholder = { kind: NODE_KIND.CLOSED_PRODUCT, edges: [] };
|
|
221
|
+
nodes.push(placeholder);
|
|
222
|
+
const labels = Object.keys(node.product).sort((a, b) => Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8")));
|
|
223
|
+
placeholder.edges = labels.map((label) => ({
|
|
224
|
+
label,
|
|
225
|
+
symbolId: 0,
|
|
226
|
+
target: visit(node.product[label])
|
|
227
|
+
}));
|
|
228
|
+
return nodeId;
|
|
229
|
+
}
|
|
230
|
+
if (node instanceof Variant) {
|
|
231
|
+
const placeholder = { kind: NODE_KIND.OPEN_UNION, edges: [] };
|
|
232
|
+
nodes.push(placeholder);
|
|
233
|
+
placeholder.edges = [{
|
|
234
|
+
label: node.tag,
|
|
235
|
+
symbolId: 0,
|
|
236
|
+
target: visit(node.value)
|
|
237
|
+
}];
|
|
238
|
+
return nodeId;
|
|
239
|
+
}
|
|
240
|
+
throw new Error(`Unsupported runtime value node: ${node?.constructor?.name || typeof node}`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
visit(value);
|
|
244
|
+
const dictionary = [...new Set(nodes.flatMap((node) => node.edges.map((edge) => edge.label)))]
|
|
245
|
+
.sort((a, b) => Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8")));
|
|
246
|
+
const symbolIds = new Map(dictionary.map((label, index) => [label, index]));
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
dictionary,
|
|
250
|
+
nodes: nodes.map((node) => ({
|
|
251
|
+
kind: node.kind,
|
|
252
|
+
edges: node.edges
|
|
253
|
+
.map((edge) => ({ ...edge, symbolId: symbolIds.get(edge.label) }))
|
|
254
|
+
.sort((a, b) => a.symbolId - b.symbolId)
|
|
255
|
+
}))
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function requireProduct(value, where) {
|
|
260
|
+
if (!(value instanceof Product)) {
|
|
261
|
+
throw new Error(`${where}: expected Product`);
|
|
262
|
+
}
|
|
263
|
+
return value.product;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function requireVariant(value, where) {
|
|
267
|
+
if (!(value instanceof Variant)) {
|
|
268
|
+
throw new Error(`${where}: expected Variant`);
|
|
269
|
+
}
|
|
270
|
+
return value;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function listToK(items) {
|
|
274
|
+
let result = new Variant("nil", UNIT);
|
|
275
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
276
|
+
result = new Variant("cons", new Product({
|
|
277
|
+
car: items[i],
|
|
278
|
+
cdr: result
|
|
279
|
+
}));
|
|
280
|
+
}
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function listFromK(value, where) {
|
|
285
|
+
const result = [];
|
|
286
|
+
let cursor = value;
|
|
287
|
+
for (;;) {
|
|
288
|
+
const variant = requireVariant(cursor, where);
|
|
289
|
+
if (variant.tag === "nil") return result;
|
|
290
|
+
if (variant.tag !== "cons") {
|
|
291
|
+
throw new Error(`${where}: expected nil or cons, got ${variant.tag}`);
|
|
292
|
+
}
|
|
293
|
+
const product = requireProduct(variant.value, `${where}.cons`);
|
|
294
|
+
result.push(product.car);
|
|
295
|
+
cursor = product.cdr;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function bitsToInteger(value, where) {
|
|
300
|
+
let cursor = value;
|
|
301
|
+
let place = 1;
|
|
302
|
+
let result = 0;
|
|
303
|
+
|
|
304
|
+
for (;;) {
|
|
305
|
+
const variant = requireVariant(cursor, where);
|
|
306
|
+
if (variant.tag === "_") return result;
|
|
307
|
+
if (variant.tag !== "0" && variant.tag !== "1") {
|
|
308
|
+
throw new Error(`${where}: expected bits tag _, 0, or 1, got ${variant.tag}`);
|
|
309
|
+
}
|
|
310
|
+
if (variant.tag === "1") result += place;
|
|
311
|
+
if (result > Number.MAX_SAFE_INTEGER) {
|
|
312
|
+
throw new Error(`${where}: bits value exceeds safe integer range`);
|
|
313
|
+
}
|
|
314
|
+
place *= 2;
|
|
315
|
+
cursor = variant.value;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function integerToBits(value) {
|
|
320
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
321
|
+
throw new Error(`bits target must be a non-negative safe integer, got ${value}`);
|
|
322
|
+
}
|
|
323
|
+
if (value === 0) return new Variant("_", UNIT);
|
|
324
|
+
|
|
325
|
+
const bits = [];
|
|
326
|
+
let n = value;
|
|
327
|
+
while (n > 0) {
|
|
328
|
+
bits.push(n & 1);
|
|
329
|
+
n = Math.floor(n / 2);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
let result = new Variant("_", UNIT);
|
|
333
|
+
for (let i = bits.length - 1; i >= 0; i--) {
|
|
334
|
+
result = new Variant(bits[i] ? "1" : "0", result);
|
|
335
|
+
}
|
|
336
|
+
return result;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function edgeToK([label, target]) {
|
|
340
|
+
return new Product({
|
|
341
|
+
label: textToStringValue(label),
|
|
342
|
+
target: integerToBits(target)
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function edgeFromK(value, where) {
|
|
347
|
+
const product = requireProduct(value, where);
|
|
348
|
+
return [
|
|
349
|
+
stringValueToText(product.label),
|
|
350
|
+
bitsToInteger(product.target, `${where}.target`)
|
|
351
|
+
];
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function patternToKValue(propertyList) {
|
|
355
|
+
if (!Array.isArray(propertyList)) {
|
|
356
|
+
throw new Error("Pattern must be a property-list array");
|
|
357
|
+
}
|
|
358
|
+
return listToK(propertyList.map((node, nodeIndex) => {
|
|
359
|
+
if (!Array.isArray(node) || node.length !== 2 || !Array.isArray(node[1])) {
|
|
360
|
+
throw new Error(`Pattern node ${nodeIndex} must be [kind, edges]`);
|
|
361
|
+
}
|
|
362
|
+
const [kind, edges] = node;
|
|
363
|
+
if (kind === "any") {
|
|
364
|
+
return new Variant("any", UNIT);
|
|
365
|
+
}
|
|
366
|
+
return new Variant(kind, listToK(edges.map(edgeToK)));
|
|
367
|
+
}));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function patternFromKValue(value) {
|
|
371
|
+
const propertyList = listFromK(value, "pattern").map((node, nodeIndex) => {
|
|
372
|
+
const variant = requireVariant(node, `pattern[${nodeIndex}]`);
|
|
373
|
+
if (variant.tag === "any") {
|
|
374
|
+
return ["any", []];
|
|
375
|
+
}
|
|
376
|
+
if (!["open-product", "open-union", "closed-product", "closed-union"].includes(variant.tag)) {
|
|
377
|
+
throw new Error(`pattern[${nodeIndex}]: unknown pattern-node tag ${variant.tag}`);
|
|
378
|
+
}
|
|
379
|
+
return [
|
|
380
|
+
variant.tag,
|
|
381
|
+
listFromK(variant.value, `pattern[${nodeIndex}].edges`).map((edge, edgeIndex) =>
|
|
382
|
+
edgeFromK(edge, `pattern[${nodeIndex}].edges[${edgeIndex}]`)
|
|
383
|
+
)
|
|
384
|
+
];
|
|
385
|
+
});
|
|
386
|
+
propertyListToPattern(propertyList);
|
|
387
|
+
return propertyList;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function encodeToWire(value, propertyList) {
|
|
391
|
+
const { coercedValue, refinedPattern, propertyList: encodedPattern } = preparePatternAndValue(value, propertyList);
|
|
392
|
+
const writer = new BitWriter();
|
|
393
|
+
encodeNode(writer, patternToKValue(encodedPattern), CORE_PATTERN_PATTERN, 0);
|
|
394
|
+
encodeNode(writer, coercedValue, refinedPattern, 0);
|
|
395
|
+
return writer.toBuffer();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function decodeWire(buffer) {
|
|
399
|
+
if (!Buffer.isBuffer(buffer)) {
|
|
400
|
+
throw new Error("Wire input must be a Buffer");
|
|
401
|
+
}
|
|
402
|
+
const reader = new BitReader(buffer);
|
|
403
|
+
const patternPropertyList = patternFromKValue(decodeNode(reader, CORE_PATTERN_PATTERN, 0));
|
|
404
|
+
const pattern = propertyListToPattern(patternPropertyList);
|
|
405
|
+
const value = decodeNode(reader, pattern, 0);
|
|
406
|
+
reader.assertZeroPadding();
|
|
407
|
+
return { pattern: patternPropertyList, value: withPattern(value, patternPropertyList) };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export {
|
|
411
|
+
encodeToWire,
|
|
412
|
+
decodeWire,
|
|
413
|
+
propertyListToPattern,
|
|
414
|
+
patternToPropertyList,
|
|
415
|
+
choiceWidth,
|
|
416
|
+
CORE_PATTERN_PROPERTY_LIST
|
|
417
|
+
};
|
|
418
|
+
export default {
|
|
419
|
+
encodeToWire,
|
|
420
|
+
decodeWire,
|
|
421
|
+
propertyListToPattern,
|
|
422
|
+
patternToPropertyList,
|
|
423
|
+
choiceWidth,
|
|
424
|
+
CORE_PATTERN_PROPERTY_LIST
|
|
425
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Product, Variant } from "../../Value.mjs";
|
|
2
|
+
|
|
3
|
+
const nameRE = /^[a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*$/;
|
|
4
|
+
|
|
5
|
+
function pLabel(label) {
|
|
6
|
+
return nameRE.test(label) ? ` ${label}` : ` ${JSON.stringify(label)}`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function propertyListToFilter(propertyList) {
|
|
10
|
+
if (!propertyList || propertyList.length === 0) return "(...)";
|
|
11
|
+
|
|
12
|
+
const refCount = new Array(propertyList.length).fill(0);
|
|
13
|
+
for (const [, edges] of propertyList) {
|
|
14
|
+
for (const [, target] of edges) refCount[target]++;
|
|
15
|
+
}
|
|
16
|
+
refCount[0]++;
|
|
17
|
+
|
|
18
|
+
const varNames = new Map();
|
|
19
|
+
let varCounter = 0;
|
|
20
|
+
for (let i = 0; i < propertyList.length; i++) {
|
|
21
|
+
if (refCount[i] > 1) varNames.set(i, `X${varCounter++}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const defined = new Set();
|
|
25
|
+
|
|
26
|
+
function fmt(nodeId) {
|
|
27
|
+
if (varNames.has(nodeId) && defined.has(nodeId)) return varNames.get(nodeId);
|
|
28
|
+
|
|
29
|
+
const [kind, edges] = propertyList[nodeId];
|
|
30
|
+
const hasVar = varNames.has(nodeId);
|
|
31
|
+
if (hasVar) defined.add(nodeId);
|
|
32
|
+
|
|
33
|
+
const suffix = hasVar ? `=${varNames.get(nodeId)}` : "";
|
|
34
|
+
|
|
35
|
+
if (kind === "any") return hasVar ? varNames.get(nodeId) : "(...)";
|
|
36
|
+
|
|
37
|
+
const isOpen = kind.startsWith("open-");
|
|
38
|
+
const isProduct = kind.endsWith("product");
|
|
39
|
+
const open = isProduct ? "{" : "<";
|
|
40
|
+
const close = isProduct ? "}" : ">";
|
|
41
|
+
|
|
42
|
+
if (edges.length === 0 && !isOpen) return `${open}${close}${suffix}`;
|
|
43
|
+
|
|
44
|
+
const fields = edges.map(([label, target]) => `${fmt(target)}${pLabel(label)}`);
|
|
45
|
+
if (isOpen) fields.push("...");
|
|
46
|
+
return `${open}${fields.join(", ")}${close}${suffix}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return fmt(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function valueToK(v) {
|
|
53
|
+
if (v instanceof Variant) return `${valueToK(v.value)}|${pLabel(v.tag).trimStart()}`;
|
|
54
|
+
if (v instanceof Product) {
|
|
55
|
+
const keys = Object.keys(v.product);
|
|
56
|
+
if (keys.length === 0) return "{}";
|
|
57
|
+
const fields = keys.map((k) => `${valueToK(v.product[k])}${pLabel(k)}`);
|
|
58
|
+
return `{${fields.join(", ")}}`;
|
|
59
|
+
}
|
|
60
|
+
return String(v);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function valueWithEnvelopeToK(value) {
|
|
64
|
+
if (value === undefined) return "... undefined";
|
|
65
|
+
return `${valueToK(value)} ?${propertyListToFilter(value.pattern)}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { propertyListToFilter, valueToK, valueWithEnvelopeToK };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Product, Variant } from "../../Value.mjs";
|
|
2
|
+
|
|
3
|
+
function typeDefsFromValue(value) {
|
|
4
|
+
let nextId = 0;
|
|
5
|
+
const defs = {};
|
|
6
|
+
|
|
7
|
+
function visit(node) {
|
|
8
|
+
const name = `:auto${nextId++}`;
|
|
9
|
+
if (node instanceof Product) {
|
|
10
|
+
const labels = Object.keys(node.product).sort();
|
|
11
|
+
const product = {};
|
|
12
|
+
for (const label of labels) {
|
|
13
|
+
product[label] = visit(node.product[label]);
|
|
14
|
+
}
|
|
15
|
+
defs[name] = { code: "product", product };
|
|
16
|
+
return name;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (node instanceof Variant) {
|
|
20
|
+
const childName = visit(node.value);
|
|
21
|
+
defs[name] = { code: "union", union: { [node.tag]: childName } };
|
|
22
|
+
return name;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
throw new Error(`Unsupported runtime value node: ${node?.constructor?.name || typeof node}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const root = visit(value);
|
|
29
|
+
defs.__root__ = { code: "ref", ref: `@${root}` };
|
|
30
|
+
return { defs, root };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { typeDefsFromValue };
|
|
34
|
+
export default { typeDefsFromValue };
|