@fraczak/k 6.2.0 → 6.2.1
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/DOCS/KIR_SPECIALIZATION_DESIGN.md +11 -2
- package/DOCS/KVM_EXECUTION_MODEL.md +413 -0
- package/Examples/arithmetics.k +452 -0
- package/Examples/bnat.k +291 -0
- package/Examples/byte.k +67 -0
- package/Examples/code.k +44 -0
- package/Examples/esc.k +9 -0
- package/Examples/ieee.k +15766 -0
- package/Examples/list.k +7 -0
- package/Examples/nat.k +12 -0
- package/Examples/vhdl_to_k.k +50 -0
- package/README.md +8 -0
- package/backend-api.mjs +12 -0
- package/codecs/runtime/codec.mjs +1 -0
- package/kvm.mjs +523 -0
- package/package.json +3 -2
- package/run.mjs +1 -1
- package/scripts/run-tests.mjs +1 -0
- package/tests/README.md +35 -0
- package/tests/code-derivation/codec-witness-pattern.mjs +1 -1
- package/tests/integration.sh +8 -5
- package/tests/test-kvm.mjs +163 -0
package/Examples/list.k
ADDED
package/Examples/nat.k
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
$ nat = < {} 0, nat +1 >;
|
|
2
|
+
0 = {} | 0 ;
|
|
3
|
+
0? = $ nat { / 0 if, () then} .then;
|
|
4
|
+
succ = | +1 $ nat ;
|
|
5
|
+
pred = $nat / +1 ;
|
|
6
|
+
add = ?{ $nat x, ... } = X <{.x pred x, .y succ y} ?X add, .y> ;
|
|
7
|
+
mult = <
|
|
8
|
+
{{.x pred x, .y y} mult x, .y y} add,
|
|
9
|
+
0
|
|
10
|
+
> ;
|
|
11
|
+
|
|
12
|
+
succ {()x,()y} add
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
-- VHDL to K Translation Patterns
|
|
2
|
+
|
|
3
|
+
-- VHDL: signal a : std_logic;
|
|
4
|
+
-- K: $ bit = <{} 0, {} 1>;
|
|
5
|
+
|
|
6
|
+
-- VHDL: signal bus : std_logic_vector(7 downto 0);
|
|
7
|
+
-- K: $ byte = {bit 0, bit 1, bit 2, bit 3, bit 4, bit 5, bit 6, bit 7};
|
|
8
|
+
|
|
9
|
+
-- VHDL: entity adder is port(a,b: in std_logic; sum: out std_logic); end;
|
|
10
|
+
-- K: adder = ${bit a, bit b} {.a a_out, .b b_out, xor_gate sum_out};
|
|
11
|
+
|
|
12
|
+
-- VHDL: process(clk) begin if rising_edge(clk) then q <= d; end if; end;
|
|
13
|
+
-- K: dff = $bit .; -- Identity (register holds value)
|
|
14
|
+
|
|
15
|
+
-- VHDL: y <= a and b;
|
|
16
|
+
-- K: and_gate = ${bit a, bit b} <{.a.1 1, .b.1 1, 1}, 0>;
|
|
17
|
+
|
|
18
|
+
-- VHDL: y <= a or b;
|
|
19
|
+
-- K: or_gate = ${bit a, bit b} <{.a.0 0, .b.0 0, 0}, 1>;
|
|
20
|
+
|
|
21
|
+
-- VHDL: y <= not a;
|
|
22
|
+
-- K: not_gate = $bit <{.0 0, 1}, {.1 1, 0}>;
|
|
23
|
+
|
|
24
|
+
-- VHDL: with sel select y <= a when "00", b when "01", c when others;
|
|
25
|
+
-- K: mux = ${bit sel0, bit sel1, bit a, bit b, bit c} <
|
|
26
|
+
-- {.sel0.0 0, .sel1.0 0, .a},
|
|
27
|
+
-- {.sel0.1 1, .sel1.0 0, .b},
|
|
28
|
+
-- .c>;
|
|
29
|
+
|
|
30
|
+
-- VHDL: architecture of 4-bit adder
|
|
31
|
+
four_bit_adder = ${
|
|
32
|
+
{bit 0, bit 1, bit 2, bit 3} a,
|
|
33
|
+
{bit 0, bit 1, bit 2, bit 3} b
|
|
34
|
+
} {
|
|
35
|
+
{.a.0 a0, .b.0 b0} full_adder fa0,
|
|
36
|
+
{.a.1 a1, .b.1 b1, .fa0.carry c_in} full_adder fa1,
|
|
37
|
+
{.a.2 a2, .b.2 b2, .fa1.carry c_in} full_adder fa2,
|
|
38
|
+
{.a.3 a3, .b.3 b3, .fa2.carry c_in} full_adder fa3,
|
|
39
|
+
{.fa0.sum 0, .fa1.sum 1, .fa2.sum 2, .fa3.sum 3} sum,
|
|
40
|
+
.fa3.carry overflow
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
-- VHDL: Full adder component
|
|
44
|
+
full_adder = ${bit a, bit b, bit c_in} {
|
|
45
|
+
{.a a_val, .b b_val} xor_gate ab_xor,
|
|
46
|
+
{.ab_xor xor_val, .c_in c_val} xor_gate sum,
|
|
47
|
+
{.a a_val, .b b_val} and_gate ab_and,
|
|
48
|
+
{.ab_xor xor_val, .c_in c_val} and_gate xor_cin_and,
|
|
49
|
+
{.ab_and and1, .xor_cin_and and2} or_gate carry
|
|
50
|
+
};
|
package/README.md
CHANGED
|
@@ -195,6 +195,14 @@ Installed binary names are `k-` plus the source basename without `.mjs`, except
|
|
|
195
195
|
for `k.mjs` itself. Source names that already include `k-`, such as
|
|
196
196
|
`codecs/k-parse.mjs`, keep that name.
|
|
197
197
|
|
|
198
|
+
## WebAssembly Backend
|
|
199
|
+
|
|
200
|
+
The experimental WebAssembly backend lives in the separate
|
|
201
|
+
[`fraczak/k-wasm`](https://github.com/fraczak/k-wasm) repository. Keeping the
|
|
202
|
+
backend separate avoids adding its WABT dependency to the core runtime. External
|
|
203
|
+
backends integrate with the compiler and binary codecs through
|
|
204
|
+
[`backend-api.mjs`](backend-api.mjs).
|
|
205
|
+
|
|
198
206
|
## Node.js API
|
|
199
207
|
|
|
200
208
|
```js
|
package/backend-api.mjs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { annotate } from "./index.mjs";
|
|
2
|
+
export { lowerToKVM, executeKVM } from "./kvm.mjs";
|
|
3
|
+
export { decodeWire, encodeToWire } from "./codecs/runtime/prefix-codec.mjs";
|
|
4
|
+
export { exportPatternGraph, NODE_KIND } from "./codecs/runtime/codec.mjs";
|
|
5
|
+
export { patternToPropertyList, propertyListToPattern } from "./codecs/runtime/pattern-json.mjs";
|
|
6
|
+
export { Product, Variant, fromObject } from "./Value.mjs";
|
|
7
|
+
export { decodeObject, loadLibrary } from "./object.mjs";
|
|
8
|
+
export { createState, evaluateInput } from "./repl.mjs";
|
|
9
|
+
export { parse as parseFloat64, print as printFloat64 } from "./codecs/ieee.mjs";
|
|
10
|
+
export { valueForCode } from "./repl-codecs.mjs";
|
|
11
|
+
export { default as codes } from "./codes.mjs";
|
|
12
|
+
export { default as run, run_converged } from "./run.mjs";
|
package/codecs/runtime/codec.mjs
CHANGED
|
@@ -614,6 +614,7 @@ function refinePatternForValue(pattern, value) {
|
|
|
614
614
|
}
|
|
615
615
|
|
|
616
616
|
if (baseNode.kind === NODE_KIND.CLOSED_PRODUCT || baseNode.kind === NODE_KIND.OPEN_PRODUCT) {
|
|
617
|
+
result.kind = NODE_KIND.CLOSED_PRODUCT;
|
|
617
618
|
const values = occurrences.get(patternNodeId) || [];
|
|
618
619
|
const explicit = new Map(baseNode.edges.map((edge) => [edge.label, edge.target]));
|
|
619
620
|
let labels = [...explicit.keys()].sort();
|
package/kvm.mjs
ADDED
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { argv, stdin, exit, stdout } from "node:process";
|
|
4
|
+
import { Product, Variant, composePattern, withPattern } from "./Value.mjs";
|
|
5
|
+
import { exportPatternGraph } from "./codecs/runtime/codec.mjs";
|
|
6
|
+
import { patternToPropertyList } from "./codecs/runtime/pattern-json.mjs";
|
|
7
|
+
import {
|
|
8
|
+
constrainWithPattern,
|
|
9
|
+
intersectPatterns,
|
|
10
|
+
projectionPattern,
|
|
11
|
+
verify,
|
|
12
|
+
run
|
|
13
|
+
} from "./run.mjs";
|
|
14
|
+
import { annotate } from "./index.mjs";
|
|
15
|
+
import { decodeWire, encodeToWire } from "./codecs/runtime/prefix-codec.mjs";
|
|
16
|
+
import { decodeObject, loadLibrary } from "./object.mjs";
|
|
17
|
+
import codes from "./codes.mjs";
|
|
18
|
+
import { isMainEntrypoint } from "./codecs/runtime/cli-entry.mjs";
|
|
19
|
+
|
|
20
|
+
class KVMBuilder {
|
|
21
|
+
constructor(typePatternGraph) {
|
|
22
|
+
this.typePatternGraph = typePatternGraph;
|
|
23
|
+
this.regCount = 0;
|
|
24
|
+
this.instructions = [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
nextReg() {
|
|
28
|
+
return `%v${this.regCount++}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
emit(inst) {
|
|
32
|
+
this.instructions.push(inst);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getStaticPattern(exp, index) {
|
|
36
|
+
if (!this.typePatternGraph || !exp.patterns) return null;
|
|
37
|
+
const patternId = this.typePatternGraph.find(exp.patterns[index]);
|
|
38
|
+
return patternToPropertyList(exportPatternGraph(this.typePatternGraph, patternId));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function compile(exp, inputReg, builder) {
|
|
43
|
+
switch (exp.op) {
|
|
44
|
+
case "identity": {
|
|
45
|
+
const dest = builder.nextReg();
|
|
46
|
+
builder.emit({ op: "id", dest, src: inputReg });
|
|
47
|
+
return dest;
|
|
48
|
+
}
|
|
49
|
+
case "empty": {
|
|
50
|
+
builder.emit({ op: "fail" });
|
|
51
|
+
return builder.nextReg();
|
|
52
|
+
}
|
|
53
|
+
case "filter": {
|
|
54
|
+
const dest = builder.nextReg();
|
|
55
|
+
builder.emit({
|
|
56
|
+
op: "guard_pattern",
|
|
57
|
+
dest,
|
|
58
|
+
src: inputReg,
|
|
59
|
+
pattern: builder.getStaticPattern(exp, 0),
|
|
60
|
+
exp
|
|
61
|
+
});
|
|
62
|
+
return dest;
|
|
63
|
+
}
|
|
64
|
+
case "code": {
|
|
65
|
+
const dest = builder.nextReg();
|
|
66
|
+
builder.emit({
|
|
67
|
+
op: "guard_code",
|
|
68
|
+
dest,
|
|
69
|
+
src: inputReg,
|
|
70
|
+
code: exp.code,
|
|
71
|
+
exp
|
|
72
|
+
});
|
|
73
|
+
return dest;
|
|
74
|
+
}
|
|
75
|
+
case "dot": {
|
|
76
|
+
const dest = builder.nextReg();
|
|
77
|
+
builder.emit({
|
|
78
|
+
op: "project_field",
|
|
79
|
+
dest,
|
|
80
|
+
src: inputReg,
|
|
81
|
+
label: exp.dot,
|
|
82
|
+
pattern: builder.getStaticPattern(exp, 1),
|
|
83
|
+
exp
|
|
84
|
+
});
|
|
85
|
+
return dest;
|
|
86
|
+
}
|
|
87
|
+
case "div": {
|
|
88
|
+
const dest = builder.nextReg();
|
|
89
|
+
builder.emit({
|
|
90
|
+
op: "project_variant",
|
|
91
|
+
dest,
|
|
92
|
+
src: inputReg,
|
|
93
|
+
tag: exp.div,
|
|
94
|
+
pattern: builder.getStaticPattern(exp, 1),
|
|
95
|
+
exp
|
|
96
|
+
});
|
|
97
|
+
return dest;
|
|
98
|
+
}
|
|
99
|
+
case "vid": {
|
|
100
|
+
const dest = builder.nextReg();
|
|
101
|
+
builder.emit({
|
|
102
|
+
op: "make_variant",
|
|
103
|
+
dest,
|
|
104
|
+
tag: exp.vid,
|
|
105
|
+
src: inputReg
|
|
106
|
+
});
|
|
107
|
+
return dest;
|
|
108
|
+
}
|
|
109
|
+
case "ref": {
|
|
110
|
+
const dest = builder.nextReg();
|
|
111
|
+
if (exp.ref.startsWith("_")) {
|
|
112
|
+
builder.emit({
|
|
113
|
+
op: "call_intrinsic",
|
|
114
|
+
dest,
|
|
115
|
+
symbol: exp.ref,
|
|
116
|
+
src: inputReg,
|
|
117
|
+
pattern: builder.getStaticPattern(exp, 0),
|
|
118
|
+
exp
|
|
119
|
+
});
|
|
120
|
+
} else {
|
|
121
|
+
builder.emit({
|
|
122
|
+
op: "call",
|
|
123
|
+
dest,
|
|
124
|
+
func: exp.ref,
|
|
125
|
+
src: inputReg,
|
|
126
|
+
exp
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return dest;
|
|
130
|
+
}
|
|
131
|
+
case "comp": {
|
|
132
|
+
let currentReg = inputReg;
|
|
133
|
+
const compInputPattern = builder.getStaticPattern(exp, 0);
|
|
134
|
+
if (compInputPattern) {
|
|
135
|
+
const guarded = builder.nextReg();
|
|
136
|
+
builder.emit({ op: "guard_pattern", dest: guarded, src: currentReg, pattern: compInputPattern, exp });
|
|
137
|
+
currentReg = guarded;
|
|
138
|
+
}
|
|
139
|
+
for (const subExp of exp.comp) {
|
|
140
|
+
currentReg = compile(subExp, currentReg, builder);
|
|
141
|
+
}
|
|
142
|
+
return currentReg;
|
|
143
|
+
}
|
|
144
|
+
case "product": {
|
|
145
|
+
const dest = builder.nextReg();
|
|
146
|
+
const branches = [];
|
|
147
|
+
for (const { label, exp: fieldExp } of exp.product) {
|
|
148
|
+
const branchBuilder = new KVMBuilder(builder.typePatternGraph);
|
|
149
|
+
const branchInput = "%in";
|
|
150
|
+
const branchOutput = compile(fieldExp, branchInput, branchBuilder);
|
|
151
|
+
branchBuilder.emit({ op: "return", src: branchOutput });
|
|
152
|
+
branches.push({
|
|
153
|
+
label,
|
|
154
|
+
body: branchBuilder.instructions
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
builder.emit({
|
|
158
|
+
op: "product",
|
|
159
|
+
dest,
|
|
160
|
+
src: inputReg,
|
|
161
|
+
branches,
|
|
162
|
+
pattern: builder.getStaticPattern(exp, 1),
|
|
163
|
+
exp
|
|
164
|
+
});
|
|
165
|
+
return dest;
|
|
166
|
+
}
|
|
167
|
+
case "union": {
|
|
168
|
+
const dest = builder.nextReg();
|
|
169
|
+
const branches = [];
|
|
170
|
+
for (const branchExp of exp.union) {
|
|
171
|
+
const branchBuilder = new KVMBuilder(builder.typePatternGraph);
|
|
172
|
+
const branchInput = "%in";
|
|
173
|
+
const branchOutput = compile(branchExp, branchInput, branchBuilder);
|
|
174
|
+
branchBuilder.emit({ op: "return", src: branchOutput });
|
|
175
|
+
branches.push({
|
|
176
|
+
body: branchBuilder.instructions
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
builder.emit({
|
|
180
|
+
op: "union",
|
|
181
|
+
dest,
|
|
182
|
+
src: inputReg,
|
|
183
|
+
branches,
|
|
184
|
+
exp
|
|
185
|
+
});
|
|
186
|
+
return dest;
|
|
187
|
+
}
|
|
188
|
+
default:
|
|
189
|
+
throw new Error(`Unsupported AST operation: ${exp.op}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function lowerToKVM(relDef, name, options = {}) {
|
|
194
|
+
const typePatternGraph = relDef.typePatternGraph || null;
|
|
195
|
+
const builder = new KVMBuilder(typePatternGraph);
|
|
196
|
+
const inputReg = "%in";
|
|
197
|
+
|
|
198
|
+
let currentReg = inputReg;
|
|
199
|
+
const inputPattern = builder.getStaticPattern(relDef.def, 0);
|
|
200
|
+
if (inputPattern) {
|
|
201
|
+
const guarded = builder.nextReg();
|
|
202
|
+
builder.emit({ op: "guard_pattern", dest: guarded, src: currentReg, pattern: inputPattern, exp: relDef.def });
|
|
203
|
+
currentReg = guarded;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const bodyOutput = compile(relDef.def, currentReg, builder);
|
|
207
|
+
|
|
208
|
+
let finalReg = bodyOutput;
|
|
209
|
+
const outputPattern = builder.getStaticPattern(relDef.def, 1);
|
|
210
|
+
if (outputPattern) {
|
|
211
|
+
const guarded = builder.nextReg();
|
|
212
|
+
builder.emit({ op: "guard_pattern", dest: guarded, src: finalReg, pattern: outputPattern, exp: relDef.def });
|
|
213
|
+
finalReg = guarded;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
builder.emit({ op: "return", src: finalReg });
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
name,
|
|
220
|
+
inputPattern,
|
|
221
|
+
outputPattern,
|
|
222
|
+
isConverged: relDef.typeDerivation?.status === "converged",
|
|
223
|
+
body: builder.instructions
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function executeBlock(instructions, inputVal, context) {
|
|
228
|
+
const registers = new Map();
|
|
229
|
+
registers.set("%in", inputVal);
|
|
230
|
+
|
|
231
|
+
for (const inst of instructions) {
|
|
232
|
+
const res = executeInstruction(inst, registers, context);
|
|
233
|
+
if (res === undefined) {
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
if (res.type === "return") {
|
|
237
|
+
return res.value;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function executeInstruction(inst, registers, context) {
|
|
244
|
+
const options = context.options || {};
|
|
245
|
+
switch (inst.op) {
|
|
246
|
+
case "id": {
|
|
247
|
+
registers.set(inst.dest, registers.get(inst.src));
|
|
248
|
+
return { type: "continue" };
|
|
249
|
+
}
|
|
250
|
+
case "fail": {
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
case "guard_pattern": {
|
|
254
|
+
const val = registers.get(inst.src);
|
|
255
|
+
if (options.envelopeFree) {
|
|
256
|
+
registers.set(inst.dest, val);
|
|
257
|
+
return { type: "continue" };
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
const constrained = constrainWithPattern(val, inst.pattern, inst.exp);
|
|
261
|
+
registers.set(inst.dest, constrained);
|
|
262
|
+
return { type: "continue" };
|
|
263
|
+
} catch (err) {
|
|
264
|
+
if (err instanceof TypeError) {
|
|
265
|
+
throw err;
|
|
266
|
+
}
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
case "guard_code": {
|
|
271
|
+
const val = registers.get(inst.src);
|
|
272
|
+
if (options.envelopeFree) {
|
|
273
|
+
registers.set(inst.dest, val);
|
|
274
|
+
return { type: "continue" };
|
|
275
|
+
}
|
|
276
|
+
if (verify(context.findCode, inst.code, val)) {
|
|
277
|
+
registers.set(inst.dest, val);
|
|
278
|
+
return { type: "continue" };
|
|
279
|
+
}
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
case "project_field": {
|
|
283
|
+
const val = registers.get(inst.src);
|
|
284
|
+
if (!(val instanceof Product)) return undefined;
|
|
285
|
+
const fieldVal = val.product[inst.label];
|
|
286
|
+
if (fieldVal === undefined) return undefined;
|
|
287
|
+
|
|
288
|
+
if (options.envelopeFree) {
|
|
289
|
+
registers.set(inst.dest, fieldVal);
|
|
290
|
+
} else {
|
|
291
|
+
const pattern = projectionPattern(inst.pattern, val.pattern, inst.label, inst.exp);
|
|
292
|
+
registers.set(inst.dest, withPattern(fieldVal, pattern));
|
|
293
|
+
}
|
|
294
|
+
return { type: "continue" };
|
|
295
|
+
}
|
|
296
|
+
case "project_variant": {
|
|
297
|
+
const val = registers.get(inst.src);
|
|
298
|
+
if (!(val instanceof Variant) || val.tag !== inst.tag) return undefined;
|
|
299
|
+
|
|
300
|
+
if (options.envelopeFree) {
|
|
301
|
+
registers.set(inst.dest, val.value);
|
|
302
|
+
} else {
|
|
303
|
+
const pattern = projectionPattern(inst.pattern, val.pattern, inst.tag, inst.exp);
|
|
304
|
+
registers.set(inst.dest, withPattern(val.value, pattern));
|
|
305
|
+
}
|
|
306
|
+
return { type: "continue" };
|
|
307
|
+
}
|
|
308
|
+
case "make_variant": {
|
|
309
|
+
const val = registers.get(inst.src);
|
|
310
|
+
if (options.envelopeFree) {
|
|
311
|
+
registers.set(inst.dest, new Variant(inst.tag, val));
|
|
312
|
+
} else {
|
|
313
|
+
registers.set(
|
|
314
|
+
inst.dest,
|
|
315
|
+
new Variant(inst.tag, val, composePattern("open-union", [[inst.tag, val.pattern]]))
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
return { type: "continue" };
|
|
319
|
+
}
|
|
320
|
+
case "call": {
|
|
321
|
+
const val = registers.get(inst.src);
|
|
322
|
+
const relDef = context.rels[inst.func];
|
|
323
|
+
if (!relDef) {
|
|
324
|
+
throw new Error(`Unknown ref: '${inst.func}'`);
|
|
325
|
+
}
|
|
326
|
+
if (options.requireConverged && relDef.typeDerivation?.status !== "converged") {
|
|
327
|
+
throw new Error(`Cannot run '${inst.func}' without envelopes: type derivation is not converged`);
|
|
328
|
+
}
|
|
329
|
+
if (options.trace) {
|
|
330
|
+
console.log(`[Trace] Calling ${inst.func} with:`, val ? val.toString() : "null");
|
|
331
|
+
}
|
|
332
|
+
if (!relDef._kvmFunc) {
|
|
333
|
+
relDef._kvmFunc = lowerToKVM(relDef, inst.func);
|
|
334
|
+
}
|
|
335
|
+
const res = executeKVM(relDef._kvmFunc, val, context);
|
|
336
|
+
if (options.trace) {
|
|
337
|
+
console.log(`[Trace] Returned from ${inst.func} ->:`, res ? res.toString() : "fail");
|
|
338
|
+
}
|
|
339
|
+
if (res === undefined) return undefined;
|
|
340
|
+
registers.set(inst.dest, res);
|
|
341
|
+
return { type: "continue" };
|
|
342
|
+
}
|
|
343
|
+
case "call_intrinsic": {
|
|
344
|
+
const val = registers.get(inst.src);
|
|
345
|
+
const builtinFunc = run.builtin[inst.symbol];
|
|
346
|
+
if (!builtinFunc) {
|
|
347
|
+
throw new Error(`Unknown builtin: '${inst.symbol}'`);
|
|
348
|
+
}
|
|
349
|
+
if (!options.envelopeFree && inst.pattern) {
|
|
350
|
+
try {
|
|
351
|
+
const constrained = constrainWithPattern(val, inst.pattern, inst.exp);
|
|
352
|
+
const res = builtinFunc(constrained);
|
|
353
|
+
registers.set(inst.dest, res);
|
|
354
|
+
return { type: "continue" };
|
|
355
|
+
} catch (err) {
|
|
356
|
+
throw err;
|
|
357
|
+
}
|
|
358
|
+
} else {
|
|
359
|
+
const res = builtinFunc(val);
|
|
360
|
+
registers.set(inst.dest, res);
|
|
361
|
+
return { type: "continue" };
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
case "product": {
|
|
365
|
+
const val = registers.get(inst.src);
|
|
366
|
+
const result = {};
|
|
367
|
+
const patternEntries = [];
|
|
368
|
+
for (const branch of inst.branches) {
|
|
369
|
+
const branchRes = executeBlock(branch.body, val, context);
|
|
370
|
+
if (branchRes === undefined) return undefined;
|
|
371
|
+
result[branch.label] = branchRes;
|
|
372
|
+
if (!options.envelopeFree) {
|
|
373
|
+
patternEntries.push([branch.label, branchRes.pattern]);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
let productValue;
|
|
377
|
+
if (options.envelopeFree) {
|
|
378
|
+
productValue = new Product(result);
|
|
379
|
+
} else {
|
|
380
|
+
const staticOutputPattern = inst.pattern;
|
|
381
|
+
if (staticOutputPattern) {
|
|
382
|
+
productValue = new Product(result, staticOutputPattern);
|
|
383
|
+
} else {
|
|
384
|
+
productValue = new Product(result, composePattern("closed-product", patternEntries));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
registers.set(inst.dest, productValue);
|
|
388
|
+
return { type: "continue" };
|
|
389
|
+
}
|
|
390
|
+
case "union": {
|
|
391
|
+
const val = registers.get(inst.src);
|
|
392
|
+
for (const branch of inst.branches) {
|
|
393
|
+
const branchRes = executeBlock(branch.body, val, context);
|
|
394
|
+
if (branchRes !== undefined) {
|
|
395
|
+
registers.set(inst.dest, branchRes);
|
|
396
|
+
return { type: "continue" };
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return undefined;
|
|
400
|
+
}
|
|
401
|
+
case "return": {
|
|
402
|
+
return { type: "return", value: registers.get(inst.src) };
|
|
403
|
+
}
|
|
404
|
+
default:
|
|
405
|
+
throw new Error(`Unknown instruction op: ${inst.op}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export function executeKVM(kvmFunc, inputVal, context) {
|
|
410
|
+
const options = context.options || {};
|
|
411
|
+
if (options.requireConverged && !kvmFunc.isConverged) {
|
|
412
|
+
throw new Error(`Cannot run '${kvmFunc.name}' without envelopes: type derivation is not converged`);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const result = executeBlock(kvmFunc.body, inputVal, context);
|
|
416
|
+
if (result === undefined) return undefined;
|
|
417
|
+
|
|
418
|
+
if (options.envelopeFree && kvmFunc.isConverged) {
|
|
419
|
+
return withPattern(result, kvmFunc.outputPattern);
|
|
420
|
+
}
|
|
421
|
+
return result;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export default {
|
|
425
|
+
lowerToKVM,
|
|
426
|
+
executeKVM
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
function usage() {
|
|
430
|
+
const prog = argv[1] || "kvm.mjs";
|
|
431
|
+
console.error(`Usage: node ${prog} [ options ] ( k-expr | -k file ) [ input-file ]`);
|
|
432
|
+
console.error("Options:");
|
|
433
|
+
console.error(" --lib file Load a .klib dependency before compiling. May be repeated.");
|
|
434
|
+
console.error(" --envelope-free Run the interpreter in envelope-free mode.");
|
|
435
|
+
console.error(" -h, --help Show this help.");
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async function main() {
|
|
439
|
+
const args = argv.slice(2);
|
|
440
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
441
|
+
usage();
|
|
442
|
+
return exit(0);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
let envelopeFree = false;
|
|
446
|
+
const libraries = [];
|
|
447
|
+
|
|
448
|
+
// Parse options
|
|
449
|
+
while (args.length > 0) {
|
|
450
|
+
if (args[0] === "--envelope-free") {
|
|
451
|
+
envelopeFree = true;
|
|
452
|
+
args.shift();
|
|
453
|
+
} else if (args[0] === "--lib") {
|
|
454
|
+
args.shift();
|
|
455
|
+
const libPath = args.shift();
|
|
456
|
+
if (!libPath) throw new Error("--lib requires a file argument");
|
|
457
|
+
const libBuffer = fs.readFileSync(libPath);
|
|
458
|
+
libraries.push(loadLibrary(decodeObject(libBuffer)));
|
|
459
|
+
} else {
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
let kScriptStr = (function (arg) {
|
|
465
|
+
if (arg == null) {
|
|
466
|
+
throw new Error("Missing script argument");
|
|
467
|
+
}
|
|
468
|
+
if (arg === "-k") {
|
|
469
|
+
const fileArg = args.shift();
|
|
470
|
+
if (!fileArg) throw new Error("-k requires a file argument");
|
|
471
|
+
return fs.readFileSync(fileArg, "utf8");
|
|
472
|
+
} else {
|
|
473
|
+
return arg;
|
|
474
|
+
}
|
|
475
|
+
})(args.shift());
|
|
476
|
+
|
|
477
|
+
const inputStream = (function (arg) {
|
|
478
|
+
if (arg == null) {
|
|
479
|
+
return stdin;
|
|
480
|
+
}
|
|
481
|
+
return fs.createReadStream(arg);
|
|
482
|
+
})(args.shift());
|
|
483
|
+
|
|
484
|
+
const defs = annotate(kScriptStr, { libraries });
|
|
485
|
+
const mainRel = defs.rels.__main__;
|
|
486
|
+
if (!mainRel) {
|
|
487
|
+
throw new Error("No main relation (__main__) defined in script");
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const kvmFunc = lowerToKVM(mainRel, "__main__");
|
|
491
|
+
|
|
492
|
+
const buffer = [];
|
|
493
|
+
inputStream.on("data", (data) => buffer.push(Buffer.isBuffer(data) ? data : Buffer.from(data)));
|
|
494
|
+
inputStream.on("end", () => {
|
|
495
|
+
try {
|
|
496
|
+
const inputBuffer = Buffer.concat(buffer);
|
|
497
|
+
const { pattern: inputPattern, value } = decodeWire(inputBuffer);
|
|
498
|
+
const context = {
|
|
499
|
+
rels: defs.rels,
|
|
500
|
+
findCode: codes.find,
|
|
501
|
+
options: {
|
|
502
|
+
envelopeFree
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
const result = executeKVM(kvmFunc, value, context);
|
|
506
|
+
if (result === undefined) {
|
|
507
|
+
throw new Error("kVM expression evaluated to undefined");
|
|
508
|
+
}
|
|
509
|
+
stdout.write(encodeToWire(result, result.pattern));
|
|
510
|
+
} catch (error) {
|
|
511
|
+
console.error(error.stack || error.message || String(error));
|
|
512
|
+
exit(1);
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (isMainEntrypoint(import.meta.url, argv[1])) {
|
|
518
|
+
main().catch((error) => {
|
|
519
|
+
console.error(error.stack || error.message || String(error));
|
|
520
|
+
usage();
|
|
521
|
+
exit(1);
|
|
522
|
+
});
|
|
523
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fraczak/k",
|
|
3
|
-
"version": "6.2.
|
|
3
|
+
"version": "6.2.1",
|
|
4
4
|
"description": "k — a language for composing first-order partial functions over algebraic data types (products and tagged unions); includes runtime, type derivation, REPL, and codec toolchain",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"scripts": {
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"k-show": "./codecs/show.mjs",
|
|
31
31
|
"k-unit": "./codecs/unit.mjs",
|
|
32
32
|
"k-utf16": "./codecs/utf16.mjs",
|
|
33
|
-
"k-utf8": "./codecs/utf8.mjs"
|
|
33
|
+
"k-utf8": "./codecs/utf8.mjs",
|
|
34
|
+
"k-vm": "./kvm.mjs"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"jiwson": "^0.1.2"
|
package/run.mjs
CHANGED
|
@@ -483,4 +483,4 @@ run_converged.defs = null;
|
|
|
483
483
|
const run_coverged = run_converged;
|
|
484
484
|
|
|
485
485
|
export default run;
|
|
486
|
-
export { run, run_rel, run_converged, run_coverged };
|
|
486
|
+
export { run, run_rel, run_converged, run_coverged, constrainWithPattern, intersectPatterns, projectionPattern, verify };
|
package/scripts/run-tests.mjs
CHANGED
|
@@ -20,6 +20,7 @@ const tests = [
|
|
|
20
20
|
["tests/test-hash-normalization.mjs", node, ["tests/test-hash-normalization.mjs"]],
|
|
21
21
|
["tests/test-k-object.mjs", node, ["tests/test-k-object.mjs"]],
|
|
22
22
|
["tests/test-repl.mjs", node, ["tests/test-repl.mjs"]],
|
|
23
|
+
["tests/test-kvm.mjs", node, ["tests/test-kvm.mjs"]],
|
|
23
24
|
["tests/test-ieee-arithmetic.mjs", node, ["tests/test-ieee-arithmetic.mjs"]],
|
|
24
25
|
["tests/integration.sh", path.join(root, "tests/integration.sh"), []]
|
|
25
26
|
];
|