@fraczak/k 6.4.0 → 6.6.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 +17 -0
- package/Examples/arithmetics.k +86 -63
- package/Value.mjs +8 -2
- package/backends/arm64/src/runtime.c +20 -4
- package/backends/arm64/tests/integration.sh +1 -1
- package/backends/llvm/runtime/krt.c +387 -2
- package/backends/llvm/runtime/krt.h +1 -0
- package/backends/llvm/src/kvm2llvm.mjs +98 -29
- package/backends/llvm/tests/perf-support.mjs +1 -1
- package/backends/wasm/TECHNICAL-DEBT.md +11 -13
- package/backends/wasm/runtime.wat +375 -2
- package/backends/wasm/src/kvm2wasm.mjs +36 -0
- package/backends/wasm/src/wasm.mjs +48 -4
- package/backends/wasm/tests/test-wasm.mjs +8 -0
- package/browser/repl-browser.mjs +24 -7
- package/browser/tree-view.mjs +599 -0
- package/browser/vfs-data.mjs +3 -3
- package/codecs/runtime/show-value.mjs +10 -1
- package/package.json +1 -1
- package/repl.mjs +4 -0
- package/scripts/build-repl-html.mjs +301 -0
- package/scripts/perf-ieee.mjs +5 -2
- package/scripts/perf-int.mjs +331 -0
- package/scripts/run-tests.mjs +1 -0
- package/tests/test-repl-html.mjs +141 -0
- package/tests/test-tree-view.mjs +185 -0
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
import { isProduct, isVariant } from "../Value.mjs";
|
|
2
|
+
import { propertyListToFilter, valueToK } from "../codecs/runtime/show-value.mjs";
|
|
3
|
+
|
|
4
|
+
function escapeHtml(str) {
|
|
5
|
+
return String(str)
|
|
6
|
+
.replace(/&/g, "&")
|
|
7
|
+
.replace(/</g, "<")
|
|
8
|
+
.replace(/>/g, ">")
|
|
9
|
+
.replace(/"/g, """)
|
|
10
|
+
.replace(/'/g, "'");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function previewProductFields(val, maxItems = 3) {
|
|
14
|
+
if (!isProduct(val)) return "";
|
|
15
|
+
const keys = Object.keys(val.product);
|
|
16
|
+
if (keys.length === 0) return "{}";
|
|
17
|
+
const previewKeys = keys.slice(0, maxItems).map((k) => `.${k}`);
|
|
18
|
+
if (keys.length > maxItems) previewKeys.push("...");
|
|
19
|
+
return previewKeys.join(", ");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function previewValue(val, maxItems = 4) {
|
|
23
|
+
if (val === null || val === undefined) return String(val);
|
|
24
|
+
if (isProduct(val)) {
|
|
25
|
+
const keys = Object.keys(val.product);
|
|
26
|
+
if (keys.length === 0) return "{}";
|
|
27
|
+
const previewKeys = keys.slice(0, maxItems).map((k) => `.${k}`);
|
|
28
|
+
if (keys.length > maxItems) previewKeys.push("...");
|
|
29
|
+
return `{ ${previewKeys.join(", ")} }`;
|
|
30
|
+
}
|
|
31
|
+
if (isVariant(val)) {
|
|
32
|
+
const tags = [];
|
|
33
|
+
let curr = val;
|
|
34
|
+
while (isVariant(curr) && tags.length < maxItems) {
|
|
35
|
+
tags.push(curr.tag);
|
|
36
|
+
curr = curr.value;
|
|
37
|
+
}
|
|
38
|
+
if (isVariant(curr)) tags.push("...");
|
|
39
|
+
return `| ${tags.join(" | ")}`;
|
|
40
|
+
}
|
|
41
|
+
return String(val);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function renderValueNode(val, currentDepth = 1, depthLimit = 3) {
|
|
45
|
+
if (val === null || val === undefined) {
|
|
46
|
+
const el = document.createElement("span");
|
|
47
|
+
el.className = "k-tree-leaf k-val-nil";
|
|
48
|
+
el.textContent = String(val);
|
|
49
|
+
return el;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (isProduct(val)) {
|
|
53
|
+
const keys = Object.keys(val.product);
|
|
54
|
+
if (keys.length === 0) {
|
|
55
|
+
const el = document.createElement("span");
|
|
56
|
+
el.className = "k-tree-leaf k-val-empty";
|
|
57
|
+
el.textContent = "{}";
|
|
58
|
+
return el;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const details = document.createElement("details");
|
|
62
|
+
details.className = "k-tree-node k-tree-product";
|
|
63
|
+
const isOpen = currentDepth <= depthLimit;
|
|
64
|
+
details.open = isOpen;
|
|
65
|
+
|
|
66
|
+
const summary = document.createElement("summary");
|
|
67
|
+
summary.className = "k-tree-summary";
|
|
68
|
+
|
|
69
|
+
const arrow = document.createElement("span");
|
|
70
|
+
arrow.className = "k-tree-arrow";
|
|
71
|
+
arrow.textContent = "▶";
|
|
72
|
+
summary.appendChild(arrow);
|
|
73
|
+
|
|
74
|
+
const openBrace = document.createElement("span");
|
|
75
|
+
openBrace.className = "k-val-brace";
|
|
76
|
+
openBrace.textContent = "{";
|
|
77
|
+
summary.appendChild(openBrace);
|
|
78
|
+
|
|
79
|
+
const badge = document.createElement("span");
|
|
80
|
+
badge.className = "k-tree-badge";
|
|
81
|
+
badge.textContent = `${keys.length} ${keys.length === 1 ? "field" : "fields"}`;
|
|
82
|
+
summary.appendChild(badge);
|
|
83
|
+
|
|
84
|
+
const preview = document.createElement("span");
|
|
85
|
+
preview.className = "k-val-preview";
|
|
86
|
+
preview.textContent = previewProductFields(val, 3);
|
|
87
|
+
summary.appendChild(preview);
|
|
88
|
+
|
|
89
|
+
const closeBrace = document.createElement("span");
|
|
90
|
+
closeBrace.className = "k-val-brace";
|
|
91
|
+
closeBrace.textContent = "}";
|
|
92
|
+
summary.appendChild(closeBrace);
|
|
93
|
+
|
|
94
|
+
details.appendChild(summary);
|
|
95
|
+
|
|
96
|
+
const childrenContainer = document.createElement("div");
|
|
97
|
+
childrenContainer.className = "k-tree-children";
|
|
98
|
+
details.appendChild(childrenContainer);
|
|
99
|
+
|
|
100
|
+
let populated = false;
|
|
101
|
+
const populateChildren = () => {
|
|
102
|
+
if (populated) return;
|
|
103
|
+
populated = true;
|
|
104
|
+
for (const k of keys) {
|
|
105
|
+
const row = document.createElement("div");
|
|
106
|
+
row.className = "k-tree-row";
|
|
107
|
+
|
|
108
|
+
const keySpan = document.createElement("span");
|
|
109
|
+
keySpan.className = "k-tree-field-key";
|
|
110
|
+
keySpan.textContent = `.${k}:`;
|
|
111
|
+
row.appendChild(keySpan);
|
|
112
|
+
|
|
113
|
+
const childContainer = document.createElement("div");
|
|
114
|
+
childContainer.className = "k-tree-child-val";
|
|
115
|
+
childContainer.appendChild(
|
|
116
|
+
renderValueNode(val.product[k], isOpen ? currentDepth + 1 : 1, depthLimit)
|
|
117
|
+
);
|
|
118
|
+
row.appendChild(childContainer);
|
|
119
|
+
|
|
120
|
+
childrenContainer.appendChild(row);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
if (isOpen) {
|
|
125
|
+
populateChildren();
|
|
126
|
+
} else {
|
|
127
|
+
details.addEventListener("toggle", () => {
|
|
128
|
+
if (details.open) {
|
|
129
|
+
populateChildren();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return details;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (isVariant(val)) {
|
|
138
|
+
const isTerminal = isProduct(val.value) && Object.keys(val.value.product).length === 0;
|
|
139
|
+
if (isTerminal) {
|
|
140
|
+
const el = document.createElement("span");
|
|
141
|
+
el.className = "k-tree-leaf k-tree-variant-leaf";
|
|
142
|
+
el.innerHTML = `<span class="k-val-pipe">|</span> <span class="k-val-tag">${escapeHtml(val.tag)}</span>`;
|
|
143
|
+
return el;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const details = document.createElement("details");
|
|
147
|
+
details.className = "k-tree-node k-tree-variant";
|
|
148
|
+
const isOpen = currentDepth <= depthLimit;
|
|
149
|
+
details.open = isOpen;
|
|
150
|
+
|
|
151
|
+
const summary = document.createElement("summary");
|
|
152
|
+
summary.className = "k-tree-summary";
|
|
153
|
+
|
|
154
|
+
const arrow = document.createElement("span");
|
|
155
|
+
arrow.className = "k-tree-arrow";
|
|
156
|
+
arrow.textContent = "▶";
|
|
157
|
+
summary.appendChild(arrow);
|
|
158
|
+
|
|
159
|
+
const pipeSpan = document.createElement("span");
|
|
160
|
+
pipeSpan.className = "k-val-pipe";
|
|
161
|
+
pipeSpan.textContent = "|";
|
|
162
|
+
summary.appendChild(pipeSpan);
|
|
163
|
+
|
|
164
|
+
const tagSpan = document.createElement("span");
|
|
165
|
+
tagSpan.className = "k-val-tag";
|
|
166
|
+
tagSpan.textContent = val.tag;
|
|
167
|
+
summary.appendChild(tagSpan);
|
|
168
|
+
|
|
169
|
+
if (isVariant(val.value)) {
|
|
170
|
+
let chainCount = 0;
|
|
171
|
+
let curr = val.value;
|
|
172
|
+
while (isVariant(curr)) {
|
|
173
|
+
chainCount++;
|
|
174
|
+
curr = curr.value;
|
|
175
|
+
}
|
|
176
|
+
const badge = document.createElement("span");
|
|
177
|
+
badge.className = "k-tree-badge";
|
|
178
|
+
badge.textContent = `+${chainCount} tags`;
|
|
179
|
+
summary.appendChild(badge);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const preview = document.createElement("span");
|
|
183
|
+
preview.className = "k-val-preview";
|
|
184
|
+
preview.textContent = previewValue(val.value, 3);
|
|
185
|
+
summary.appendChild(preview);
|
|
186
|
+
|
|
187
|
+
details.appendChild(summary);
|
|
188
|
+
|
|
189
|
+
const childrenContainer = document.createElement("div");
|
|
190
|
+
childrenContainer.className = "k-tree-children";
|
|
191
|
+
details.appendChild(childrenContainer);
|
|
192
|
+
|
|
193
|
+
let populated = false;
|
|
194
|
+
const populateChild = () => {
|
|
195
|
+
if (populated) return;
|
|
196
|
+
populated = true;
|
|
197
|
+
const row = document.createElement("div");
|
|
198
|
+
row.className = "k-tree-row";
|
|
199
|
+
const childContainer = document.createElement("div");
|
|
200
|
+
childContainer.className = "k-tree-child-val";
|
|
201
|
+
childContainer.appendChild(
|
|
202
|
+
renderValueNode(val.value, isOpen ? currentDepth + 1 : 1, depthLimit)
|
|
203
|
+
);
|
|
204
|
+
row.appendChild(childContainer);
|
|
205
|
+
childrenContainer.appendChild(row);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
if (isOpen) {
|
|
209
|
+
populateChild();
|
|
210
|
+
} else {
|
|
211
|
+
details.addEventListener("toggle", () => {
|
|
212
|
+
if (details.open) {
|
|
213
|
+
populateChild();
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return details;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const el = document.createElement("span");
|
|
222
|
+
el.className = "k-tree-leaf k-val-primitive";
|
|
223
|
+
el.textContent = String(val);
|
|
224
|
+
return el;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function renderPatternTree(propertyList, depthLimit = 3) {
|
|
228
|
+
if (!propertyList || propertyList.length === 0) {
|
|
229
|
+
const el = document.createElement("span");
|
|
230
|
+
el.className = "k-tree-leaf k-pat-empty";
|
|
231
|
+
el.textContent = "(...)";
|
|
232
|
+
return el;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const refCount = new Array(propertyList.length).fill(0);
|
|
236
|
+
for (const [, edges] of propertyList) {
|
|
237
|
+
for (const [, target] of edges) {
|
|
238
|
+
if (target >= 0 && target < propertyList.length) {
|
|
239
|
+
refCount[target]++;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
refCount[0]++;
|
|
244
|
+
|
|
245
|
+
const varNames = new Map();
|
|
246
|
+
let varCounter = 0;
|
|
247
|
+
for (let i = 0; i < propertyList.length; i++) {
|
|
248
|
+
if (refCount[i] > 1) {
|
|
249
|
+
varNames.set(i, `X${varCounter++}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const definedNodes = new Set();
|
|
254
|
+
|
|
255
|
+
function renderNode(nodeId, currentDepth, maxDepth, ancestors = new Set()) {
|
|
256
|
+
if (nodeId == null || nodeId < 0 || nodeId >= propertyList.length) {
|
|
257
|
+
const el = document.createElement("span");
|
|
258
|
+
el.className = "k-tree-leaf k-pat-empty";
|
|
259
|
+
el.textContent = "(...)";
|
|
260
|
+
return el;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const hasVar = varNames.has(nodeId);
|
|
264
|
+
const varName = hasVar ? varNames.get(nodeId) : null;
|
|
265
|
+
|
|
266
|
+
// Cycle detection
|
|
267
|
+
if (ancestors.has(nodeId)) {
|
|
268
|
+
const el = document.createElement("span");
|
|
269
|
+
el.className = "k-tree-leaf k-pat-cycle";
|
|
270
|
+
el.innerHTML = `<span class="k-pat-var">${escapeHtml(varName || `X${nodeId}`)}</span> <span class="k-tree-badge k-badge-cycle">recursive</span>`;
|
|
271
|
+
return el;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Already defined on an earlier branch
|
|
275
|
+
if (hasVar && definedNodes.has(nodeId)) {
|
|
276
|
+
const el = document.createElement("span");
|
|
277
|
+
el.className = "k-tree-leaf k-pat-ref";
|
|
278
|
+
el.innerHTML = `<span class="k-pat-var">${escapeHtml(varName)}</span>`;
|
|
279
|
+
return el;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (hasVar) {
|
|
283
|
+
definedNodes.add(nodeId);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const newAncestors = new Set(ancestors);
|
|
287
|
+
newAncestors.add(nodeId);
|
|
288
|
+
|
|
289
|
+
const [kind, edges] = propertyList[nodeId];
|
|
290
|
+
|
|
291
|
+
if (kind === "any") {
|
|
292
|
+
const el = document.createElement("span");
|
|
293
|
+
el.className = "k-tree-leaf k-pat-any";
|
|
294
|
+
el.textContent = hasVar ? varName : "(...)";
|
|
295
|
+
return el;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const isProductPat = kind.endsWith("product");
|
|
299
|
+
const isOpenPat = kind.startsWith("open-");
|
|
300
|
+
const openChar = isProductPat ? "{" : "<";
|
|
301
|
+
const closeChar = isProductPat ? "}" : ">";
|
|
302
|
+
const itemLabel = isProductPat ? "field" : "variant";
|
|
303
|
+
|
|
304
|
+
if (edges.length === 0 && !isOpenPat) {
|
|
305
|
+
const el = document.createElement("span");
|
|
306
|
+
el.className = "k-tree-leaf k-pat-empty";
|
|
307
|
+
const varSuffix = hasVar ? `=${varName}` : "";
|
|
308
|
+
el.innerHTML = `<span class="k-pat-brace">${openChar}${closeChar}</span>${varSuffix ? `<span class="k-pat-vardef">${varSuffix}</span>` : ""}`;
|
|
309
|
+
return el;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const details = document.createElement("details");
|
|
313
|
+
details.className = `k-tree-node ${isProductPat ? "k-pat-product" : "k-pat-union"}`;
|
|
314
|
+
const isOpen = currentDepth <= maxDepth;
|
|
315
|
+
details.open = isOpen;
|
|
316
|
+
|
|
317
|
+
const summary = document.createElement("summary");
|
|
318
|
+
summary.className = "k-tree-summary";
|
|
319
|
+
|
|
320
|
+
const arrow = document.createElement("span");
|
|
321
|
+
arrow.className = "k-tree-arrow";
|
|
322
|
+
arrow.textContent = "▶";
|
|
323
|
+
summary.appendChild(arrow);
|
|
324
|
+
|
|
325
|
+
const openSpan = document.createElement("span");
|
|
326
|
+
openSpan.className = isProductPat ? "k-pat-brace" : "k-pat-union";
|
|
327
|
+
openSpan.textContent = openChar;
|
|
328
|
+
summary.appendChild(openSpan);
|
|
329
|
+
|
|
330
|
+
const badge = document.createElement("span");
|
|
331
|
+
badge.className = "k-tree-badge";
|
|
332
|
+
badge.textContent = `${edges.length} ${edges.length === 1 ? itemLabel : itemLabel + "s"}${isOpenPat ? ", ..." : ""}`;
|
|
333
|
+
summary.appendChild(badge);
|
|
334
|
+
|
|
335
|
+
if (hasVar) {
|
|
336
|
+
const varDefSpan = document.createElement("span");
|
|
337
|
+
varDefSpan.className = "k-pat-vardef";
|
|
338
|
+
varDefSpan.textContent = `=${varName}`;
|
|
339
|
+
summary.appendChild(varDefSpan);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const closeSpan = document.createElement("span");
|
|
343
|
+
closeSpan.className = isProductPat ? "k-pat-brace" : "k-pat-union";
|
|
344
|
+
closeSpan.textContent = closeChar;
|
|
345
|
+
summary.appendChild(closeSpan);
|
|
346
|
+
|
|
347
|
+
details.appendChild(summary);
|
|
348
|
+
|
|
349
|
+
const childrenContainer = document.createElement("div");
|
|
350
|
+
childrenContainer.className = "k-tree-children";
|
|
351
|
+
details.appendChild(childrenContainer);
|
|
352
|
+
|
|
353
|
+
let populated = false;
|
|
354
|
+
const populateChildren = () => {
|
|
355
|
+
if (populated) return;
|
|
356
|
+
populated = true;
|
|
357
|
+
for (const [label, target] of edges) {
|
|
358
|
+
const row = document.createElement("div");
|
|
359
|
+
row.className = "k-tree-row";
|
|
360
|
+
|
|
361
|
+
const labelSpan = document.createElement("span");
|
|
362
|
+
if (isProductPat) {
|
|
363
|
+
labelSpan.className = "k-pat-field-key";
|
|
364
|
+
labelSpan.textContent = `.${label}:`;
|
|
365
|
+
} else {
|
|
366
|
+
labelSpan.className = "k-pat-tag";
|
|
367
|
+
labelSpan.innerHTML = `<span class="k-val-pipe">|</span> ${escapeHtml(label)}`;
|
|
368
|
+
}
|
|
369
|
+
row.appendChild(labelSpan);
|
|
370
|
+
|
|
371
|
+
const childContainer = document.createElement("div");
|
|
372
|
+
childContainer.className = "k-tree-child-pat";
|
|
373
|
+
childContainer.appendChild(
|
|
374
|
+
renderNode(target, isOpen ? currentDepth + 1 : 1, maxDepth, newAncestors)
|
|
375
|
+
);
|
|
376
|
+
row.appendChild(childContainer);
|
|
377
|
+
|
|
378
|
+
childrenContainer.appendChild(row);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (isOpenPat) {
|
|
382
|
+
const row = document.createElement("div");
|
|
383
|
+
row.className = "k-tree-row";
|
|
384
|
+
const ellipsis = document.createElement("span");
|
|
385
|
+
ellipsis.className = "k-pat-ellipsis";
|
|
386
|
+
ellipsis.textContent = "...";
|
|
387
|
+
row.appendChild(ellipsis);
|
|
388
|
+
childrenContainer.appendChild(row);
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
if (isOpen) {
|
|
393
|
+
populateChildren();
|
|
394
|
+
} else {
|
|
395
|
+
details.addEventListener("toggle", () => {
|
|
396
|
+
if (details.open) {
|
|
397
|
+
populateChildren();
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return details;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return renderNode(0, 1, depthLimit);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export function createOutputEntryElement({
|
|
409
|
+
value,
|
|
410
|
+
rawText,
|
|
411
|
+
codecOutputs = [],
|
|
412
|
+
timingText = null
|
|
413
|
+
}) {
|
|
414
|
+
const container = document.createElement("div");
|
|
415
|
+
container.className = "entry-line line-output k-entry-container";
|
|
416
|
+
|
|
417
|
+
// Toolbar
|
|
418
|
+
const toolbar = document.createElement("div");
|
|
419
|
+
toolbar.className = "k-output-toolbar";
|
|
420
|
+
|
|
421
|
+
// Mode buttons group
|
|
422
|
+
const modeGroup = document.createElement("div");
|
|
423
|
+
modeGroup.className = "k-mode-group";
|
|
424
|
+
|
|
425
|
+
const treeBtn = document.createElement("button");
|
|
426
|
+
treeBtn.type = "button";
|
|
427
|
+
treeBtn.className = "k-btn-mode active";
|
|
428
|
+
treeBtn.innerHTML = `<span class="k-mode-icon">🌲</span> Tree`;
|
|
429
|
+
treeBtn.title = "View structured Value & Pattern trees";
|
|
430
|
+
|
|
431
|
+
const rawBtn = document.createElement("button");
|
|
432
|
+
rawBtn.type = "button";
|
|
433
|
+
rawBtn.className = "k-btn-mode";
|
|
434
|
+
rawBtn.innerHTML = `<span class="k-mode-icon">📄</span> Raw`;
|
|
435
|
+
rawBtn.title = "View raw K expression & pattern text";
|
|
436
|
+
|
|
437
|
+
modeGroup.appendChild(treeBtn);
|
|
438
|
+
modeGroup.appendChild(rawBtn);
|
|
439
|
+
toolbar.appendChild(modeGroup);
|
|
440
|
+
|
|
441
|
+
// Tree Action buttons group (Expand / Collapse / Copy)
|
|
442
|
+
const actionGroup = document.createElement("div");
|
|
443
|
+
actionGroup.className = "k-action-group";
|
|
444
|
+
|
|
445
|
+
const expandAllBtn = document.createElement("button");
|
|
446
|
+
expandAllBtn.type = "button";
|
|
447
|
+
expandAllBtn.className = "k-btn-action";
|
|
448
|
+
expandAllBtn.innerHTML = `⊞ Expand`;
|
|
449
|
+
expandAllBtn.title = "Expand tree nodes";
|
|
450
|
+
|
|
451
|
+
const collapseAllBtn = document.createElement("button");
|
|
452
|
+
collapseAllBtn.type = "button";
|
|
453
|
+
collapseAllBtn.className = "k-btn-action";
|
|
454
|
+
collapseAllBtn.innerHTML = `⊟ Collapse`;
|
|
455
|
+
collapseAllBtn.title = "Reset trees to top 3 levels";
|
|
456
|
+
|
|
457
|
+
const copyBtn = document.createElement("button");
|
|
458
|
+
copyBtn.type = "button";
|
|
459
|
+
copyBtn.className = "k-btn-action";
|
|
460
|
+
copyBtn.innerHTML = `📋 Copy`;
|
|
461
|
+
copyBtn.title = "Copy raw output to clipboard";
|
|
462
|
+
copyBtn.onclick = (e) => {
|
|
463
|
+
e.stopPropagation();
|
|
464
|
+
navigator.clipboard.writeText(rawText);
|
|
465
|
+
copyBtn.innerHTML = `✓ Copied`;
|
|
466
|
+
setTimeout(() => {
|
|
467
|
+
copyBtn.innerHTML = `📋 Copy`;
|
|
468
|
+
}, 1500);
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
actionGroup.appendChild(expandAllBtn);
|
|
472
|
+
actionGroup.appendChild(collapseAllBtn);
|
|
473
|
+
actionGroup.appendChild(copyBtn);
|
|
474
|
+
toolbar.appendChild(actionGroup);
|
|
475
|
+
|
|
476
|
+
container.appendChild(toolbar);
|
|
477
|
+
|
|
478
|
+
// 1. Tree View Wrapper
|
|
479
|
+
const treeWrapper = document.createElement("div");
|
|
480
|
+
treeWrapper.className = "k-tree-view";
|
|
481
|
+
|
|
482
|
+
// Value Section
|
|
483
|
+
const valSection = document.createElement("details");
|
|
484
|
+
valSection.className = "k-tree-section k-tree-value-section";
|
|
485
|
+
valSection.open = true;
|
|
486
|
+
|
|
487
|
+
const valSummary = document.createElement("summary");
|
|
488
|
+
valSummary.className = "k-tree-section-summary";
|
|
489
|
+
valSummary.innerHTML = `<span class="k-tree-arrow">▶</span> <span class="k-section-title">🌲 Value</span> <span class="k-section-preview">${escapeHtml(valueToK(value))}</span>`;
|
|
490
|
+
valSection.appendChild(valSummary);
|
|
491
|
+
|
|
492
|
+
const valBody = document.createElement("div");
|
|
493
|
+
valBody.className = "k-tree-section-body";
|
|
494
|
+
valBody.appendChild(renderValueNode(value, 1, 3));
|
|
495
|
+
valSection.appendChild(valBody);
|
|
496
|
+
treeWrapper.appendChild(valSection);
|
|
497
|
+
|
|
498
|
+
// Pattern Section (if pattern present)
|
|
499
|
+
let patBody = null;
|
|
500
|
+
if (value && value.pattern) {
|
|
501
|
+
const patSection = document.createElement("details");
|
|
502
|
+
patSection.className = "k-tree-section k-tree-pat-section";
|
|
503
|
+
patSection.open = true;
|
|
504
|
+
|
|
505
|
+
const patSummary = document.createElement("summary");
|
|
506
|
+
patSummary.className = "k-tree-section-summary";
|
|
507
|
+
const patPreview = propertyListToFilter(value.pattern);
|
|
508
|
+
patSummary.innerHTML = `<span class="k-tree-arrow">▶</span> <span class="k-section-title">🌿 Pattern</span> <span class="k-section-preview">?${escapeHtml(patPreview)}</span>`;
|
|
509
|
+
patSection.appendChild(patSummary);
|
|
510
|
+
|
|
511
|
+
patBody = document.createElement("div");
|
|
512
|
+
patBody.className = "k-tree-section-body";
|
|
513
|
+
patBody.appendChild(renderPatternTree(value.pattern, 3));
|
|
514
|
+
patSection.appendChild(patBody);
|
|
515
|
+
treeWrapper.appendChild(patSection);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Codecs Section (if any codec outputs)
|
|
519
|
+
if (codecOutputs.length > 0) {
|
|
520
|
+
const codecsBox = document.createElement("div");
|
|
521
|
+
codecsBox.className = "k-codecs-box";
|
|
522
|
+
for (const c of codecOutputs) {
|
|
523
|
+
const cRow = document.createElement("div");
|
|
524
|
+
cRow.className = "k-codec-row";
|
|
525
|
+
cRow.textContent = c;
|
|
526
|
+
codecsBox.appendChild(cRow);
|
|
527
|
+
}
|
|
528
|
+
treeWrapper.appendChild(codecsBox);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Timing (if any)
|
|
532
|
+
if (timingText) {
|
|
533
|
+
const timingRow = document.createElement("div");
|
|
534
|
+
timingRow.className = "k-timing-row";
|
|
535
|
+
timingRow.textContent = timingText;
|
|
536
|
+
treeWrapper.appendChild(timingRow);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
container.appendChild(treeWrapper);
|
|
540
|
+
|
|
541
|
+
// 2. Raw Text View Wrapper
|
|
542
|
+
const rawWrapper = document.createElement("div");
|
|
543
|
+
rawWrapper.className = "k-raw-view";
|
|
544
|
+
rawWrapper.style.display = "none";
|
|
545
|
+
|
|
546
|
+
const rawPre = document.createElement("pre");
|
|
547
|
+
rawPre.className = "k-raw-pre";
|
|
548
|
+
rawPre.textContent = rawText;
|
|
549
|
+
rawWrapper.appendChild(rawPre);
|
|
550
|
+
|
|
551
|
+
container.appendChild(rawWrapper);
|
|
552
|
+
|
|
553
|
+
// Mode switching logic
|
|
554
|
+
treeBtn.onclick = () => {
|
|
555
|
+
treeBtn.classList.add("active");
|
|
556
|
+
rawBtn.classList.remove("active");
|
|
557
|
+
treeWrapper.style.display = "";
|
|
558
|
+
rawWrapper.style.display = "none";
|
|
559
|
+
expandAllBtn.style.display = "";
|
|
560
|
+
collapseAllBtn.style.display = "";
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
rawBtn.onclick = () => {
|
|
564
|
+
rawBtn.classList.add("active");
|
|
565
|
+
treeBtn.classList.remove("active");
|
|
566
|
+
treeWrapper.style.display = "none";
|
|
567
|
+
rawWrapper.style.display = "";
|
|
568
|
+
expandAllBtn.style.display = "none";
|
|
569
|
+
collapseAllBtn.style.display = "none";
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
// Expand All / Collapse All logic
|
|
573
|
+
expandAllBtn.onclick = () => {
|
|
574
|
+
const allDetails = treeWrapper.querySelectorAll("details");
|
|
575
|
+
allDetails.forEach((d) => {
|
|
576
|
+
d.open = true;
|
|
577
|
+
d.dispatchEvent(new Event("toggle"));
|
|
578
|
+
});
|
|
579
|
+
// Repeat once to expand lazily populated children
|
|
580
|
+
setTimeout(() => {
|
|
581
|
+
const moreDetails = treeWrapper.querySelectorAll("details:not([open])");
|
|
582
|
+
moreDetails.forEach((d) => {
|
|
583
|
+
d.open = true;
|
|
584
|
+
d.dispatchEvent(new Event("toggle"));
|
|
585
|
+
});
|
|
586
|
+
}, 10);
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
collapseAllBtn.onclick = () => {
|
|
590
|
+
valBody.innerHTML = "";
|
|
591
|
+
valBody.appendChild(renderValueNode(value, 1, 3));
|
|
592
|
+
if (value && value.pattern && patBody) {
|
|
593
|
+
patBody.innerHTML = "";
|
|
594
|
+
patBody.appendChild(renderPatternTree(value.pattern, 3));
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
return container;
|
|
599
|
+
}
|