@fraczak/k 7.2.0 → 8.1.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/CHANGELOG.md +90 -0
- package/DOCS/BIG_PICTURE.md +54 -7
- package/DOCS/CODECS.md +21 -13
- package/DOCS/CODEC_ARCHITECTURE.md +1 -1
- package/DOCS/DICTIONARY.md +13 -4
- package/DOCS/FILE_FORMATS.md +3 -2
- package/DOCS/JSON_APPROACHES_COMPARISON.md +302 -0
- package/DOCS/NAMED_PATTERNS.md +317 -0
- package/DOCS/PATTERNS.md +59 -11
- package/DOCS/REPL.md +13 -2
- package/DOCS/WEB_REPL_REQUIREMENTS.md +3 -2
- package/Examples/arithmetics.k +22 -17
- package/Examples/bnat.k +21 -11
- package/Examples/buda.k +37 -20
- package/Examples/byte.k +3 -1
- package/Examples/code.k +38 -39
- package/Examples/djson.k +789 -0
- package/Examples/esc.k +1 -1
- package/Examples/karatsuba-mult.k +16 -13
- package/Examples/list.k +8 -4
- package/Examples/nat.k +17 -11
- package/Examples/poly.k +11 -3
- package/Examples/vhdl_to_k.k +43 -31
- package/README.md +40 -16
- package/browser/repl-browser.mjs +2 -0
- package/browser/tree-view.mjs +35 -16
- package/browser/vfs-data.mjs +10 -8
- package/codecs/README.md +1 -1
- package/codecs/djson.mjs +375 -84
- package/core.k +79 -115
- package/index.mjs +3 -0
- package/named-patterns.mjs +267 -0
- package/object.mjs +31 -3
- package/package.json +1 -1
- package/parser.jison +68 -5
- package/parser.mjs +111 -60
- package/pretty.mjs +5 -0
- package/repl.mjs +188 -22
- package/scripts/build-repl-html.mjs +58 -4
- package/scripts/run-tests.mjs +10 -0
- package/symbol-table.mjs +20 -2
- package/tests/test-codecs.mjs +30 -17
- package/tests/test-djson-methods.mjs +285 -0
- package/tests/test-named-patterns.mjs +189 -0
- package/tests/test-repl-html.mjs +47 -4
- package/tests/test-repl.mjs +149 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// tests/test-djson-methods.mjs
|
|
2
|
+
// Test suite for standard dynamic JSON methods working on json? type.
|
|
3
|
+
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
import k from "../index.mjs";
|
|
10
|
+
import { Value } from "../Value.mjs";
|
|
11
|
+
import {
|
|
12
|
+
fromJsonValue,
|
|
13
|
+
toJsonValue
|
|
14
|
+
} from "../codecs/djson.mjs";
|
|
15
|
+
import {
|
|
16
|
+
textToStringValue,
|
|
17
|
+
stringValueToText
|
|
18
|
+
} from "../codecs/runtime/unicode-string.mjs";
|
|
19
|
+
|
|
20
|
+
const root = path.resolve(
|
|
21
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
22
|
+
".."
|
|
23
|
+
);
|
|
24
|
+
const libPath = path.join(root, "Examples", "djson.k");
|
|
25
|
+
const djsonK = readFileSync(libPath, "utf8");
|
|
26
|
+
|
|
27
|
+
function compileOp(op) {
|
|
28
|
+
return k.compile(`${djsonK}\n${op}\n`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function makeIndex(n) {
|
|
32
|
+
if (n === 0) {
|
|
33
|
+
return Value.variant(
|
|
34
|
+
"0",
|
|
35
|
+
Value.variant("_", Value.product({}))
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
const s = n.toString(2);
|
|
39
|
+
let v = Value.variant("_", Value.product({}));
|
|
40
|
+
for (let i = s.length - 1; i >= 0; i--) {
|
|
41
|
+
v = Value.variant(s[i], v);
|
|
42
|
+
}
|
|
43
|
+
return v;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log("==> Running Dynamic JSON (json?) Methods Test Suite");
|
|
47
|
+
|
|
48
|
+
// Test 1: Object Accessors (get, has?, set, del)
|
|
49
|
+
console.log("--> Test 1: Object Accessors (get, has?, set, del)");
|
|
50
|
+
{
|
|
51
|
+
const fnGet = compileOp("get");
|
|
52
|
+
const fnHas = compileOp("has?");
|
|
53
|
+
const fnSet = compileOp("set");
|
|
54
|
+
const fnDel = compileOp("del");
|
|
55
|
+
|
|
56
|
+
const doc = {
|
|
57
|
+
name: "Alice",
|
|
58
|
+
age: 30,
|
|
59
|
+
tags: ["developer", "k"]
|
|
60
|
+
};
|
|
61
|
+
const kDoc = fromJsonValue(doc);
|
|
62
|
+
|
|
63
|
+
// get "name"
|
|
64
|
+
const resName = fnGet(Value.product({
|
|
65
|
+
key: textToStringValue("name"),
|
|
66
|
+
json: kDoc
|
|
67
|
+
}));
|
|
68
|
+
assert.equal(toJsonValue(resName), "Alice");
|
|
69
|
+
|
|
70
|
+
// get "age"
|
|
71
|
+
const resAge = fnGet(Value.product({
|
|
72
|
+
key: textToStringValue("age"),
|
|
73
|
+
json: kDoc
|
|
74
|
+
}));
|
|
75
|
+
assert.equal(toJsonValue(resAge), 30);
|
|
76
|
+
|
|
77
|
+
// has? hit
|
|
78
|
+
const hit = fnHas(Value.product({
|
|
79
|
+
key: textToStringValue("name"),
|
|
80
|
+
json: kDoc
|
|
81
|
+
}));
|
|
82
|
+
assert.ok(hit);
|
|
83
|
+
|
|
84
|
+
// has? miss
|
|
85
|
+
const miss = fnHas(Value.product({
|
|
86
|
+
key: textToStringValue("missing"),
|
|
87
|
+
json: kDoc
|
|
88
|
+
}));
|
|
89
|
+
assert.equal(miss, undefined);
|
|
90
|
+
|
|
91
|
+
// set "city" = "Paris"
|
|
92
|
+
const withCity = fnSet(Value.product({
|
|
93
|
+
key: textToStringValue("city"),
|
|
94
|
+
val: fromJsonValue("Paris"),
|
|
95
|
+
json: kDoc
|
|
96
|
+
}));
|
|
97
|
+
assert.equal(toJsonValue(withCity).city, "Paris");
|
|
98
|
+
assert.equal(toJsonValue(withCity).name, "Alice");
|
|
99
|
+
|
|
100
|
+
// del "age"
|
|
101
|
+
const withoutAge = fnDel(Value.product({
|
|
102
|
+
key: textToStringValue("age"),
|
|
103
|
+
json: withCity
|
|
104
|
+
}));
|
|
105
|
+
assert.equal(toJsonValue(withoutAge).age, undefined);
|
|
106
|
+
assert.equal(toJsonValue(withoutAge).city, "Paris");
|
|
107
|
+
|
|
108
|
+
console.log(" Passed: Object get, has?, set, del on json?.");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Test 2: List Accessors (nth, has_index?, set_nth, del_nth, push)
|
|
112
|
+
console.log("--> Test 2: List Accessors (nth, set_nth, del_nth, push)");
|
|
113
|
+
{
|
|
114
|
+
const fnNth = compileOp("nth");
|
|
115
|
+
const fnHasIdx = compileOp("has_index?");
|
|
116
|
+
const fnSetNth = compileOp("set_nth");
|
|
117
|
+
const fnDelNth = compileOp("del_nth");
|
|
118
|
+
const fnPush = compileOp("push");
|
|
119
|
+
|
|
120
|
+
const list = [10, 20, 30];
|
|
121
|
+
const kList = fromJsonValue(list);
|
|
122
|
+
|
|
123
|
+
// nth 0, 1, 2
|
|
124
|
+
assert.equal(
|
|
125
|
+
toJsonValue(fnNth(Value.product({
|
|
126
|
+
index: makeIndex(0),
|
|
127
|
+
json: kList
|
|
128
|
+
}))),
|
|
129
|
+
10
|
|
130
|
+
);
|
|
131
|
+
assert.equal(
|
|
132
|
+
toJsonValue(fnNth(Value.product({
|
|
133
|
+
index: makeIndex(1),
|
|
134
|
+
json: kList
|
|
135
|
+
}))),
|
|
136
|
+
20
|
|
137
|
+
);
|
|
138
|
+
assert.equal(
|
|
139
|
+
toJsonValue(fnNth(Value.product({
|
|
140
|
+
index: makeIndex(2),
|
|
141
|
+
json: kList
|
|
142
|
+
}))),
|
|
143
|
+
30
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// has_index? hit and miss
|
|
147
|
+
assert.ok(fnHasIdx(Value.product({
|
|
148
|
+
index: makeIndex(1),
|
|
149
|
+
json: kList
|
|
150
|
+
})));
|
|
151
|
+
assert.equal(
|
|
152
|
+
fnHasIdx(Value.product({
|
|
153
|
+
index: makeIndex(99),
|
|
154
|
+
json: kList
|
|
155
|
+
})),
|
|
156
|
+
undefined
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// set_nth 1 -> 99
|
|
160
|
+
const updatedList = fnSetNth(Value.product({
|
|
161
|
+
index: makeIndex(1),
|
|
162
|
+
val: fromJsonValue(99),
|
|
163
|
+
json: kList
|
|
164
|
+
}));
|
|
165
|
+
assert.deepEqual(toJsonValue(updatedList), [10, 99, 30]);
|
|
166
|
+
|
|
167
|
+
// del_nth 1
|
|
168
|
+
const delList = fnDelNth(Value.product({
|
|
169
|
+
index: makeIndex(1),
|
|
170
|
+
json: updatedList
|
|
171
|
+
}));
|
|
172
|
+
assert.deepEqual(toJsonValue(delList), [10, 30]);
|
|
173
|
+
assert.equal(
|
|
174
|
+
fnHasIdx(Value.product({
|
|
175
|
+
index: makeIndex(1),
|
|
176
|
+
json: delList
|
|
177
|
+
})),
|
|
178
|
+
undefined
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// push 40
|
|
182
|
+
const pushed = fnPush(Value.product({
|
|
183
|
+
val: fromJsonValue(40),
|
|
184
|
+
json: kList
|
|
185
|
+
}));
|
|
186
|
+
assert.deepEqual(toJsonValue(pushed), [10, 20, 30, 40]);
|
|
187
|
+
|
|
188
|
+
console.log(" Passed: List nth, set_nth, del_nth, push on json?.");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Test 3: Keys and Values (keys, keys_raw, values, values_raw)
|
|
192
|
+
console.log("--> Test 3: Keys and Values (keys, values)");
|
|
193
|
+
{
|
|
194
|
+
const fnKeys = compileOp("keys");
|
|
195
|
+
const fnKeysRaw = compileOp("keys_raw");
|
|
196
|
+
const fnValues = compileOp("values");
|
|
197
|
+
const fnValuesRaw = compileOp("values_raw");
|
|
198
|
+
|
|
199
|
+
const doc = { b: 2, a: 1, c: 3 };
|
|
200
|
+
const kDoc = fromJsonValue(doc);
|
|
201
|
+
|
|
202
|
+
// keys (json? list of string)
|
|
203
|
+
const resKeys = fnKeys(kDoc);
|
|
204
|
+
assert.deepEqual(toJsonValue(resKeys), ["a", "b", "c"]);
|
|
205
|
+
|
|
206
|
+
// keys_raw (raw K cons-list of strings)
|
|
207
|
+
const resKeysRaw = fnKeysRaw(kDoc);
|
|
208
|
+
const rawKeyStrings = [];
|
|
209
|
+
let curr = resKeysRaw;
|
|
210
|
+
while (curr && curr.type === "<>" && curr.tag === "cons") {
|
|
211
|
+
rawKeyStrings.push(stringValueToText(curr.value.product.car));
|
|
212
|
+
curr = curr.value.product.cdr;
|
|
213
|
+
}
|
|
214
|
+
assert.deepEqual(rawKeyStrings, ["a", "b", "c"]);
|
|
215
|
+
|
|
216
|
+
// values on object (json? list of values)
|
|
217
|
+
const resValues = fnValues(kDoc);
|
|
218
|
+
assert.deepEqual(toJsonValue(resValues), [1, 2, 3]);
|
|
219
|
+
|
|
220
|
+
// values on list (json? identity)
|
|
221
|
+
const listJson = fromJsonValue([100, 200]);
|
|
222
|
+
assert.deepEqual(toJsonValue(fnValues(listJson)), [100, 200]);
|
|
223
|
+
|
|
224
|
+
console.log(" Passed: Keys and values extraction on json?.");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Test 4: Cardinality and Length (length, length_nat)
|
|
228
|
+
console.log("--> Test 4: Cardinality and Length (length, length_nat)");
|
|
229
|
+
{
|
|
230
|
+
const fnLength = compileOp("length");
|
|
231
|
+
|
|
232
|
+
const kObj = fromJsonValue({ x: 1, y: 2, z: 3, w: 4 });
|
|
233
|
+
const kArr = fromJsonValue(["a", "b", "c"]);
|
|
234
|
+
const kStr = fromJsonValue("hello");
|
|
235
|
+
|
|
236
|
+
// length returns json? number (float64)
|
|
237
|
+
assert.equal(toJsonValue(fnLength(kObj)), 4);
|
|
238
|
+
assert.equal(toJsonValue(fnLength(kArr)), 3);
|
|
239
|
+
assert.equal(toJsonValue(fnLength(kStr)), 5);
|
|
240
|
+
|
|
241
|
+
console.log(
|
|
242
|
+
" Passed: Length queries on json? object, list, string."
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Test 5: Type Predicates and type_of
|
|
247
|
+
console.log("--> Test 5: Type Predicates and type_of");
|
|
248
|
+
{
|
|
249
|
+
const fnTypeOf = compileOp("type_of");
|
|
250
|
+
const fnIsObj = compileOp("is_object?");
|
|
251
|
+
const fnIsList = compileOp("is_list?");
|
|
252
|
+
const fnIsStr = compileOp("is_string?");
|
|
253
|
+
const fnIsNum = compileOp("is_number?");
|
|
254
|
+
const fnIsBool = compileOp("is_bool?");
|
|
255
|
+
const fnIsNull = compileOp("is_null?");
|
|
256
|
+
|
|
257
|
+
const kObj = fromJsonValue({ a: 1 });
|
|
258
|
+
const kArr = fromJsonValue([1, 2]);
|
|
259
|
+
const kStr = fromJsonValue("hi");
|
|
260
|
+
const kNum = fromJsonValue(3.14);
|
|
261
|
+
const kBool = fromJsonValue(true);
|
|
262
|
+
const kNull = fromJsonValue(null);
|
|
263
|
+
|
|
264
|
+
assert.equal(fnTypeOf(kObj).tag, "object");
|
|
265
|
+
assert.equal(fnTypeOf(kArr).tag, "list");
|
|
266
|
+
assert.equal(fnTypeOf(kStr).tag, "string");
|
|
267
|
+
assert.equal(fnTypeOf(kNum).tag, "number");
|
|
268
|
+
assert.equal(fnTypeOf(kBool).tag, "bool");
|
|
269
|
+
assert.equal(fnTypeOf(kNull).tag, "null");
|
|
270
|
+
|
|
271
|
+
assert.ok(fnIsObj(kObj));
|
|
272
|
+
assert.equal(fnIsObj(kArr), undefined);
|
|
273
|
+
|
|
274
|
+
assert.ok(fnIsList(kArr));
|
|
275
|
+
assert.equal(fnIsList(kObj), undefined);
|
|
276
|
+
|
|
277
|
+
assert.ok(fnIsStr(kStr));
|
|
278
|
+
assert.ok(fnIsNum(kNum));
|
|
279
|
+
assert.ok(fnIsBool(kBool));
|
|
280
|
+
assert.ok(fnIsNull(kNull));
|
|
281
|
+
|
|
282
|
+
console.log(" Passed: Type predicates and type_of inspection.");
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
console.log("==> All Dynamic JSON Methods Tests Passed Successfully!");
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// tests/test-named-patterns.mjs
|
|
2
|
+
// Comprehensive test suite for named patterns and type schemes.
|
|
3
|
+
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import k from "../index.mjs";
|
|
6
|
+
import { Value } from "../Value.mjs";
|
|
7
|
+
|
|
8
|
+
console.log("==> Running Named Patterns Test Suite");
|
|
9
|
+
|
|
10
|
+
// Test 1: 0-arity pattern and composite product
|
|
11
|
+
console.log("--> Test 1: 0-arity pattern (bit[]) and product (byte[])");
|
|
12
|
+
{
|
|
13
|
+
const script = `
|
|
14
|
+
bit[] = < {}0, {}1 >;
|
|
15
|
+
byte[] = {
|
|
16
|
+
bit[] b0, bit[] b1, bit[] b2, bit[] b3,
|
|
17
|
+
bit[] b4, bit[] b5, bit[] b6, bit[] b7
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
is_byte = ? byte[];
|
|
21
|
+
|
|
22
|
+
sample = {
|
|
23
|
+
{}|0 b0, {}|1 b1, {}|0 b2, {}|1 b3,
|
|
24
|
+
{}|0 b4, {}|1 b5, {}|0 b6, {}|1 b7
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
sample is_byte
|
|
28
|
+
`;
|
|
29
|
+
const fn = k.compile(script);
|
|
30
|
+
const out = fn(Value.product({}));
|
|
31
|
+
assert.equal(out.type, "{}");
|
|
32
|
+
assert.equal(out.product.b0.tag, "0");
|
|
33
|
+
assert.equal(out.product.b1.tag, "1");
|
|
34
|
+
console.log(" Passed: 0-arity patterns and composition.");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Test 2: Parameterized list pattern
|
|
38
|
+
console.log("--> Test 2: Parameterized pattern (list[X])");
|
|
39
|
+
{
|
|
40
|
+
const script = `
|
|
41
|
+
bit[] = < {}0, {}1 >;
|
|
42
|
+
list[X] = < {}nil, { X car, Y cdr } cons > = Y;
|
|
43
|
+
|
|
44
|
+
is_bit_list = ? list[bit[]];
|
|
45
|
+
|
|
46
|
+
empty = {}|nil is_bit_list;
|
|
47
|
+
one = { {}|1 car, {}|nil cdr }|cons is_bit_list;
|
|
48
|
+
two = {
|
|
49
|
+
{}|0 car,
|
|
50
|
+
{ {}|1 car, {}|nil cdr }|cons cdr
|
|
51
|
+
}|cons is_bit_list;
|
|
52
|
+
|
|
53
|
+
{ empty empty, one one, two two }
|
|
54
|
+
`;
|
|
55
|
+
const fn = k.compile(script);
|
|
56
|
+
const out = fn(Value.product({}));
|
|
57
|
+
assert.equal(out.product.empty.tag, "nil");
|
|
58
|
+
assert.equal(out.product.one.tag, "cons");
|
|
59
|
+
assert.equal(out.product.one.value.product.car.tag, "1");
|
|
60
|
+
assert.equal(out.product.two.tag, "cons");
|
|
61
|
+
assert.equal(out.product.two.value.product.car.tag, "0");
|
|
62
|
+
console.log(" Passed: Parameterized list pattern.");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Test 3: Multi-parameter templates and composition
|
|
66
|
+
console.log("--> Test 3: Multi-parameter templates (pair, dict)");
|
|
67
|
+
{
|
|
68
|
+
const script = `
|
|
69
|
+
bit[] = < {}0, {}1 >;
|
|
70
|
+
pair[A, B] = { A key, B val };
|
|
71
|
+
list[X] = < {}nil, { X car, Y cdr } cons > = Y;
|
|
72
|
+
dict[K, V] = list[pair[K, V]];
|
|
73
|
+
|
|
74
|
+
is_dict = ? dict[bit[], bit[]];
|
|
75
|
+
|
|
76
|
+
entry = { {}|0 key, {}|1 val };
|
|
77
|
+
d = { entry car, {}|nil cdr }|cons is_dict;
|
|
78
|
+
|
|
79
|
+
d
|
|
80
|
+
`;
|
|
81
|
+
const fn = k.compile(script);
|
|
82
|
+
const out = fn(Value.product({}));
|
|
83
|
+
assert.equal(out.tag, "cons");
|
|
84
|
+
assert.equal(out.value.product.car.product.key.tag, "0");
|
|
85
|
+
assert.equal(out.value.product.car.product.val.tag, "1");
|
|
86
|
+
console.log(" Passed: Multi-parameter template composition.");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Test 4: Existential internal variables (type unification across fields)
|
|
90
|
+
console.log("--> Test 4: Existential variables (same_pair[])");
|
|
91
|
+
{
|
|
92
|
+
const scriptOk = `
|
|
93
|
+
same_pair[] = { T first, T second };
|
|
94
|
+
check = ? same_pair[];
|
|
95
|
+
{ {}|0 first, {}|1 second } check
|
|
96
|
+
`;
|
|
97
|
+
const fnOk = k.compile(scriptOk);
|
|
98
|
+
const outOk = fnOk(Value.product({}));
|
|
99
|
+
assert.equal(outOk.product.first.tag, "0");
|
|
100
|
+
assert.equal(outOk.product.second.tag, "1");
|
|
101
|
+
|
|
102
|
+
const scriptBad = `
|
|
103
|
+
same_pair[] = { T first, T second };
|
|
104
|
+
check = ? same_pair[];
|
|
105
|
+
{ {} first, {}|0 second } check
|
|
106
|
+
`;
|
|
107
|
+
assert.throws(
|
|
108
|
+
() => k.compile(scriptBad),
|
|
109
|
+
/Cannot unify/
|
|
110
|
+
);
|
|
111
|
+
console.log(" Passed: Existential variables unified across fields.");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Test 5: Recursive tree with internal knot and list
|
|
115
|
+
console.log("--> Test 5: Recursive tree with knot T (tree[X])");
|
|
116
|
+
{
|
|
117
|
+
const script = `
|
|
118
|
+
list[X] = < {}nil, { X car, Y cdr } cons > = Y;
|
|
119
|
+
tree[X] = < {}nil, { X val, list[T] children } node > = T;
|
|
120
|
+
|
|
121
|
+
bit[] = < {}0, {}1 >;
|
|
122
|
+
is_tree = ? tree[bit[]];
|
|
123
|
+
|
|
124
|
+
leaf = { {}|1 val, {}|nil children }|node is_tree;
|
|
125
|
+
root = {
|
|
126
|
+
{}|0 val,
|
|
127
|
+
{ leaf car, {}|nil cdr }|cons children
|
|
128
|
+
}|node is_tree;
|
|
129
|
+
|
|
130
|
+
{ leaf leaf, root root }
|
|
131
|
+
`;
|
|
132
|
+
const fn = k.compile(script);
|
|
133
|
+
const out = fn(Value.product({}));
|
|
134
|
+
assert.equal(out.product.leaf.tag, "node");
|
|
135
|
+
assert.equal(out.product.leaf.value.product.val.tag, "1");
|
|
136
|
+
assert.equal(out.product.root.tag, "node");
|
|
137
|
+
assert.equal(out.product.root.value.product.val.tag, "0");
|
|
138
|
+
console.log(" Passed: Recursive tree template with list port.");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Test 6: Polymorphic relation with shared type variable
|
|
142
|
+
console.log("--> Test 6: Polymorphic relation ({ X car, list[X] cdr })");
|
|
143
|
+
{
|
|
144
|
+
const script = `
|
|
145
|
+
list[X] = < {}nil, { X car, Y cdr } cons > = Y;
|
|
146
|
+
head = ? { X car, list[X] cdr } .car;
|
|
147
|
+
|
|
148
|
+
bit[] = < {}0, {}1 >;
|
|
149
|
+
h1 = { {}|1 car, {}|nil cdr } head;
|
|
150
|
+
|
|
151
|
+
h1
|
|
152
|
+
`;
|
|
153
|
+
const fn = k.compile(script);
|
|
154
|
+
const out = fn(Value.product({}));
|
|
155
|
+
assert.equal(out.tag, "1");
|
|
156
|
+
console.log(" Passed: Polymorphic relation head with list[X].");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Test 7: Error handling for templates
|
|
160
|
+
console.log("--> Test 7: Error handling (unknown, arity, cycles, dupes)");
|
|
161
|
+
{
|
|
162
|
+
assert.throws(
|
|
163
|
+
() => k.compile("check = ? missing[]; ()"),
|
|
164
|
+
/Unknown pattern template 'missing\[\]'/
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
assert.throws(
|
|
168
|
+
() => k.compile("list[X] = {}; check = ? list[]; ()"),
|
|
169
|
+
/Pattern 'list' expects 1 argument, got 0/
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
assert.throws(
|
|
173
|
+
() => k.compile("pair[A, B] = {}; check = ? pair[{}]; ()"),
|
|
174
|
+
/Pattern 'pair' expects 2 arguments, got 1/
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
assert.throws(
|
|
178
|
+
() => k.compile("a[] = b[]; b[] = a[]; check = ? a[]; ()"),
|
|
179
|
+
/Cyclic pattern template dependency detected/
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
assert.throws(
|
|
183
|
+
() => k.compile("dupe[X, X] = {}; ()"),
|
|
184
|
+
/Duplicate parameter 'X' in pattern 'dupe'/
|
|
185
|
+
);
|
|
186
|
+
console.log(" Passed: Error conditions correctly handled.");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log("==> All Named Patterns Tests Passed Successfully!");
|
package/tests/test-repl-html.mjs
CHANGED
|
@@ -140,6 +140,7 @@ if (chromiumBin) {
|
|
|
140
140
|
const expectedFiles = [
|
|
141
141
|
"arithmetics.k",
|
|
142
142
|
"core.k",
|
|
143
|
+
"djson.k",
|
|
143
144
|
"djson.mjs",
|
|
144
145
|
"ieee.k",
|
|
145
146
|
"ieee.mjs",
|
|
@@ -522,9 +523,9 @@ if (chromiumBin) {
|
|
|
522
523
|
const v4Collapsed = v4 ? !v4.open : false;
|
|
523
524
|
|
|
524
525
|
// Click Expand All
|
|
525
|
-
const
|
|
526
|
-
const expandBtn =
|
|
527
|
-
const collapseBtn =
|
|
526
|
+
const toolbarActionBtns = lastEntry.querySelectorAll(".k-output-toolbar .k-btn-action");
|
|
527
|
+
const expandBtn = toolbarActionBtns[0];
|
|
528
|
+
const collapseBtn = toolbarActionBtns[1];
|
|
528
529
|
|
|
529
530
|
expandBtn.click();
|
|
530
531
|
const allOpenAfterExpand = v4.open;
|
|
@@ -536,13 +537,48 @@ if (chromiumBin) {
|
|
|
536
537
|
);
|
|
537
538
|
const v4CollapsedAgain = newV4 ? !newV4.open : false;
|
|
538
539
|
|
|
540
|
+
// Verify Copy buttons on Value and Pattern section headers
|
|
541
|
+
const patSection = lastEntry.querySelector(".k-tree-pat-section");
|
|
542
|
+
const copyValBtn = valSection?.querySelector("summary.k-tree-section-summary > .k-btn-copy-value");
|
|
543
|
+
const copyPatBtn = patSection?.querySelector("summary.k-tree-section-summary > .k-btn-copy-pattern");
|
|
544
|
+
|
|
545
|
+
const copiedTexts = [];
|
|
546
|
+
const origWriteText = navigator.clipboard?.writeText;
|
|
547
|
+
if (navigator.clipboard) {
|
|
548
|
+
navigator.clipboard.writeText = async (txt) => { copiedTexts.push(txt); };
|
|
549
|
+
} else {
|
|
550
|
+
Object.defineProperty(navigator, "clipboard", {
|
|
551
|
+
value: { writeText: async (txt) => { copiedTexts.push(txt); } },
|
|
552
|
+
configurable: true
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const valOpenBefore = valSection?.open;
|
|
557
|
+
copyValBtn?.click();
|
|
558
|
+
const valOpenAfter = valSection?.open;
|
|
559
|
+
|
|
560
|
+
const patOpenBefore = patSection?.open;
|
|
561
|
+
copyPatBtn?.click();
|
|
562
|
+
const patOpenAfter = patSection?.open;
|
|
563
|
+
|
|
564
|
+
if (origWriteText && navigator.clipboard) {
|
|
565
|
+
navigator.clipboard.writeText = origWriteText;
|
|
566
|
+
}
|
|
567
|
+
|
|
539
568
|
return {
|
|
540
569
|
v1Open,
|
|
541
570
|
v2Open,
|
|
542
571
|
v3Open,
|
|
543
572
|
v4Collapsed,
|
|
544
573
|
allOpenAfterExpand,
|
|
545
|
-
v4CollapsedAgain
|
|
574
|
+
v4CollapsedAgain,
|
|
575
|
+
toolbarBtnCount: toolbarActionBtns.length,
|
|
576
|
+
hasCopyValBtn: !!copyValBtn,
|
|
577
|
+
hasCopyPatBtn: !!copyPatBtn,
|
|
578
|
+
valDidNotToggle: valOpenBefore === valOpenAfter,
|
|
579
|
+
patDidNotToggle: patOpenBefore === patOpenAfter,
|
|
580
|
+
copiedValText: copiedTexts[0] || "",
|
|
581
|
+
copiedPatText: copiedTexts[1] || ""
|
|
546
582
|
};
|
|
547
583
|
})()`);
|
|
548
584
|
|
|
@@ -552,6 +588,13 @@ if (chromiumBin) {
|
|
|
552
588
|
assert.strictEqual(variantDepthAndActionsCheck.v4Collapsed, true, "Variant level 4 must be collapsed by default");
|
|
553
589
|
assert.strictEqual(variantDepthAndActionsCheck.allOpenAfterExpand, true, "Expand button should expand collapsed nodes");
|
|
554
590
|
assert.strictEqual(variantDepthAndActionsCheck.v4CollapsedAgain, true, "Collapse button should reset deeper nodes to collapsed");
|
|
591
|
+
assert.strictEqual(variantDepthAndActionsCheck.toolbarBtnCount, 2, "Toolbar action group should only have Expand and Collapse buttons");
|
|
592
|
+
assert.strictEqual(variantDepthAndActionsCheck.hasCopyValBtn, true, "Value section summary should have Copy button");
|
|
593
|
+
assert.strictEqual(variantDepthAndActionsCheck.hasCopyPatBtn, true, "Pattern section summary should have Copy button");
|
|
594
|
+
assert.strictEqual(variantDepthAndActionsCheck.valDidNotToggle, true, "Clicking Value Copy button should not toggle Value details");
|
|
595
|
+
assert.strictEqual(variantDepthAndActionsCheck.patDidNotToggle, true, "Clicking Pattern Copy button should not toggle Pattern details");
|
|
596
|
+
assert.strictEqual(variantDepthAndActionsCheck.copiedValText, "{}|_|0|1|0|1", "Value Copy button should copy K value definition");
|
|
597
|
+
assert(variantDepthAndActionsCheck.copiedPatText.startsWith("?<"), `Pattern Copy button should copy K pattern filter definition, got: ${variantDepthAndActionsCheck.copiedPatText}`);
|
|
555
598
|
|
|
556
599
|
// 4. Verify shortened welcome banner
|
|
557
600
|
const bannerCheck = await evaluateAsync(`(() => {
|