@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
package/scripts/perf-int.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
createState,
|
|
10
10
|
decodeWire,
|
|
11
11
|
evaluateInput,
|
|
12
|
+
executeKVM,
|
|
12
13
|
exportPatternGraph,
|
|
13
14
|
patternToPropertyList,
|
|
14
15
|
propertyListToPattern,
|
|
@@ -38,6 +39,7 @@ import {
|
|
|
38
39
|
runTimedIterations,
|
|
39
40
|
runTimedIterationsAsync,
|
|
40
41
|
shouldStrictFail,
|
|
42
|
+
safeDeepEqual,
|
|
41
43
|
toPlainObject,
|
|
42
44
|
tryCompileCase,
|
|
43
45
|
wireInput,
|
|
@@ -77,6 +79,9 @@ const iterations = parsePositiveIntEnv("ITERATIONS", 3);
|
|
|
77
79
|
// ARM64 options
|
|
78
80
|
const arm64WarmupIterations = parseNonNegativeIntEnv("ARM64_WARMUP_ITERATIONS", 1);
|
|
79
81
|
const arm64OptLevel = process.env.ARM64_OPT || "-O2";
|
|
82
|
+
if (!process.env.K_ARENA_CAPACITY_MB && !process.env.K_ARENA_MB) {
|
|
83
|
+
process.env.K_ARENA_CAPACITY_MB = "4096";
|
|
84
|
+
}
|
|
80
85
|
|
|
81
86
|
// LLVM options
|
|
82
87
|
const llvmWarmupIterations = parseNonNegativeIntEnv("LLVM_WARMUP_ITERATIONS", 1);
|
|
@@ -529,3 +534,329 @@ console.log(`Conformance validation: ${validationSummary.join(", ")} cases match
|
|
|
529
534
|
if (runLLVM && shouldStrictFail(testSuite)) process.exitCode = 1;
|
|
530
535
|
if (runWasm && testSuite.some(tc => tc.wasmConformance !== "ok")) process.exitCode = 1;
|
|
531
536
|
if (runARM64 && testSuite.some(tc => tc.arm64Conformance !== "ok")) process.exitCode = 1;
|
|
537
|
+
|
|
538
|
+
// =========================================================================
|
|
539
|
+
// Squaring Scale & Memory Profiling Benchmark
|
|
540
|
+
// Expression: s = {()x,()y}times; 987654321 s s s s s s s s s s
|
|
541
|
+
// =========================================================================
|
|
542
|
+
|
|
543
|
+
function countBitsValue(val) {
|
|
544
|
+
let count = 0;
|
|
545
|
+
let cur = val?.tag === "+" || val?.tag === "-" ? val.value : val;
|
|
546
|
+
while (cur && (cur.tag === "0" || cur.tag === "1")) {
|
|
547
|
+
count++;
|
|
548
|
+
cur = cur.value;
|
|
549
|
+
}
|
|
550
|
+
return count;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function getProcessMemory(pid) {
|
|
554
|
+
try {
|
|
555
|
+
const status = fs.readFileSync(`/proc/${pid}/status`, "utf8");
|
|
556
|
+
const vmrss = status.match(/VmRSS:\s+(\d+)\s+kB/);
|
|
557
|
+
const vmhwm = status.match(/VmHWM:\s+(\d+)\s+kB/);
|
|
558
|
+
return {
|
|
559
|
+
rssMb: vmrss ? Number((parseInt(vmrss[1], 10) / 1024).toFixed(1)) : null,
|
|
560
|
+
peakRssMb: vmhwm ? Number((parseInt(vmhwm[1], 10) / 1024).toFixed(1)) : null
|
|
561
|
+
};
|
|
562
|
+
} catch {
|
|
563
|
+
return { rssMb: null, peakRssMb: null };
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function getNodeMemory() {
|
|
568
|
+
const mem = process.memoryUsage();
|
|
569
|
+
return {
|
|
570
|
+
heapMb: Number((mem.heapUsed / (1024 * 1024)).toFixed(1)),
|
|
571
|
+
rssMb: Number((mem.rss / (1024 * 1024)).toFixed(1))
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const squaringSteps = parsePositiveIntEnv("SQUARING_STEPS", 10);
|
|
576
|
+
const squaringInputText = process.env.SQUARING_INPUT || "987654321";
|
|
577
|
+
const squaringStepTimeoutMs = parsePositiveIntEnv("SQUARING_STEP_TIMEOUT_MS", 60000);
|
|
578
|
+
|
|
579
|
+
console.log("\n=================== SQUARING SCALE & MEMORY BENCHMARK ===================");
|
|
580
|
+
console.log(`Expression: s = {()x,()y}times; ${squaringInputText} ${"s ".repeat(squaringSteps).trim()}`);
|
|
581
|
+
console.log(`Evaluation: ${squaringSteps} successive squarings doubling bit length each step`);
|
|
582
|
+
|
|
583
|
+
await evaluateInput("s = {()x,()y}times;", state);
|
|
584
|
+
codes.load(state.codes);
|
|
585
|
+
const sRel = prepareRelation(state, "s", {
|
|
586
|
+
source: `${arithmeticsLibrarySource}\ns = {()x,()y}times;\ns`,
|
|
587
|
+
sourceLabel: "@fraczak/k/Examples/arithmetics.k#s"
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
const sInitialVal = intValue(squaringInputText);
|
|
591
|
+
const { inputWire: sInitWire, inputPattern: sInputPattern } = wireInput(sInitialVal);
|
|
592
|
+
|
|
593
|
+
// Build executables for persistent servers
|
|
594
|
+
let sLlvmServer = null;
|
|
595
|
+
if (runLLVM) {
|
|
596
|
+
const sLlvmCompiled = tryCompileCase({
|
|
597
|
+
object: sRel.object,
|
|
598
|
+
relationName: sRel.relationName,
|
|
599
|
+
relHash: sRel.relHash,
|
|
600
|
+
inputPattern: sInputPattern,
|
|
601
|
+
cacheDir,
|
|
602
|
+
sourceLabel: "@fraczak/k/Examples/arithmetics.k#s"
|
|
603
|
+
});
|
|
604
|
+
if (sLlvmCompiled.status === "ok") {
|
|
605
|
+
sLlvmServer = new PersistentExecutable(sLlvmCompiled.exePath);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
let sWasmServer = null;
|
|
610
|
+
if (runWasm) {
|
|
611
|
+
try {
|
|
612
|
+
const sWasmPath = path.join(cacheDir, "perf_int_wasm_s.wasm");
|
|
613
|
+
const sWasmExe = path.join(cacheDir, "perf_int_wasm_s.exe");
|
|
614
|
+
const wasmBuf = await compileWasmArtifactFromObject(sRel.object, { entry: "__main__" });
|
|
615
|
+
fs.writeFileSync(sWasmPath, wasmBuf);
|
|
616
|
+
const runBin = fileURLToPath(import.meta.resolve("../backends/wasm/bin/k-wasm-run.mjs"));
|
|
617
|
+
fs.writeFileSync(sWasmExe, `#!/bin/sh\nexec node "${runBin}" "${sWasmPath}" "$@"\n`, { mode: 0o755 });
|
|
618
|
+
sWasmServer = new PersistentExecutable(sWasmExe);
|
|
619
|
+
} catch (e) {
|
|
620
|
+
console.log("Failed to build Wasm executable for 's':", e.message);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
let sArm64Server = null;
|
|
625
|
+
if (runARM64) {
|
|
626
|
+
try {
|
|
627
|
+
const sArm64Exe = path.join(cacheDir, "perf_int_arm64_s");
|
|
628
|
+
compileARM64ArtifactFromObject(sRel.object, {
|
|
629
|
+
entry: "__main__",
|
|
630
|
+
outputPath: sArm64Exe,
|
|
631
|
+
optLevel: arm64OptLevel
|
|
632
|
+
});
|
|
633
|
+
sArm64Server = new PersistentExecutable(sArm64Exe);
|
|
634
|
+
} catch (e) {
|
|
635
|
+
console.log("Failed to build ARM64 executable for 's':", e.message);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const squaringLanes = [];
|
|
640
|
+
if (runBaselines) {
|
|
641
|
+
squaringLanes.push({ id: "jsAware", name: "Native JS (Aware)", type: "jsAware", memLabel: "heap" });
|
|
642
|
+
squaringLanes.push({ id: "jsFree", name: "Native JS (Free)", type: "jsFree", memLabel: "heap" });
|
|
643
|
+
squaringLanes.push({ id: "kvm", name: "kVM (Env-Free)", type: "kvm", memLabel: "heap" });
|
|
644
|
+
}
|
|
645
|
+
if (runLLVM && sLlvmServer) {
|
|
646
|
+
squaringLanes.push({ id: "llvm", name: llvmLaneName(), type: "persistent", server: sLlvmServer, memLabel: "peak RSS" });
|
|
647
|
+
}
|
|
648
|
+
if (runWasm && sWasmServer) {
|
|
649
|
+
squaringLanes.push({ id: "wasm", name: wasmLaneName(), type: "persistent", server: sWasmServer, memLabel: "peak RSS" });
|
|
650
|
+
}
|
|
651
|
+
if (runARM64 && sArm64Server) {
|
|
652
|
+
squaringLanes.push({ id: "arm64", name: arm64LaneName(arm64OptLevel), type: "persistent", server: sArm64Server, memLabel: "peak RSS" });
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const squaringResults = {};
|
|
656
|
+
for (const lane of squaringLanes) squaringResults[lane.id] = [];
|
|
657
|
+
const stepMetadata = [];
|
|
658
|
+
|
|
659
|
+
try {
|
|
660
|
+
// 1. Persistent child processes (ARM64, Wasm, LLVM)
|
|
661
|
+
for (const lane of squaringLanes.filter(l => l.type === "persistent")) {
|
|
662
|
+
console.log(`==> Running ${lane.name} squaring lane...`);
|
|
663
|
+
let curWire = sInitWire;
|
|
664
|
+
for (let step = 1; step <= squaringSteps; step++) {
|
|
665
|
+
try {
|
|
666
|
+
const t0 = performance.now();
|
|
667
|
+
curWire = await lane.server.request(curWire);
|
|
668
|
+
const t1 = performance.now();
|
|
669
|
+
const timeMs = t1 - t0;
|
|
670
|
+
const mem = getProcessMemory(lane.server.child.pid);
|
|
671
|
+
squaringResults[lane.id].push({ status: "ok", timeMs, memMb: mem.peakRssMb, wire: curWire });
|
|
672
|
+
if (!stepMetadata[step - 1]) {
|
|
673
|
+
stepMetadata[step - 1] = {
|
|
674
|
+
wireLen: curWire.length,
|
|
675
|
+
bits: null,
|
|
676
|
+
canonicalWire: curWire
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
if (timeMs > squaringStepTimeoutMs) break;
|
|
680
|
+
} catch (e) {
|
|
681
|
+
squaringResults[lane.id].push({ status: "failed", error: e.message?.split("\n")[0] || String(e) });
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// 2. In-process baselines (JS Aware, JS Free, kVM)
|
|
688
|
+
if (runBaselines) {
|
|
689
|
+
// JS Aware
|
|
690
|
+
console.log("==> Running Native JS (Envelope-Aware) squaring lane...");
|
|
691
|
+
let curValAware = sInitialVal;
|
|
692
|
+
run.defs = state;
|
|
693
|
+
for (let step = 1; step <= squaringSteps; step++) {
|
|
694
|
+
try {
|
|
695
|
+
const t0 = performance.now();
|
|
696
|
+
curValAware = run(codes.find, sRel.relDef.def, curValAware, sRel.relDef.typePatternGraph);
|
|
697
|
+
const t1 = performance.now();
|
|
698
|
+
const timeMs = t1 - t0;
|
|
699
|
+
const mem = getNodeMemory();
|
|
700
|
+
squaringResults.jsAware.push({ status: "ok", timeMs, memMb: mem.heapMb, value: curValAware });
|
|
701
|
+
if (stepMetadata[step - 1] && stepMetadata[step - 1].bits == null) {
|
|
702
|
+
stepMetadata[step - 1].bits = countBitsValue(curValAware);
|
|
703
|
+
}
|
|
704
|
+
if (timeMs > squaringStepTimeoutMs) break;
|
|
705
|
+
} catch (e) {
|
|
706
|
+
squaringResults.jsAware.push({ status: "failed", error: e.message?.split("\n")[0] || String(e) });
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// JS Free
|
|
712
|
+
console.log("==> Running Native JS (Envelope-Free) squaring lane...");
|
|
713
|
+
let curValFree = sInitialVal;
|
|
714
|
+
run_converged.defs = state;
|
|
715
|
+
for (let step = 1; step <= squaringSteps; step++) {
|
|
716
|
+
try {
|
|
717
|
+
const t0 = performance.now();
|
|
718
|
+
curValFree = run_converged(codes.find, sRel.relDef.def, curValFree, sRel.relDef.typePatternGraph);
|
|
719
|
+
const t1 = performance.now();
|
|
720
|
+
const timeMs = t1 - t0;
|
|
721
|
+
const mem = getNodeMemory();
|
|
722
|
+
squaringResults.jsFree.push({ status: "ok", timeMs, memMb: mem.heapMb, value: curValFree });
|
|
723
|
+
if (stepMetadata[step - 1] && stepMetadata[step - 1].bits == null) {
|
|
724
|
+
stepMetadata[step - 1].bits = countBitsValue(curValFree);
|
|
725
|
+
}
|
|
726
|
+
if (timeMs > squaringStepTimeoutMs) break;
|
|
727
|
+
} catch (e) {
|
|
728
|
+
squaringResults.jsFree.push({ status: "failed", error: e.message?.split("\n")[0] || String(e) });
|
|
729
|
+
break;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// kVM
|
|
734
|
+
console.log("==> Running kVM Interpreter (Env-Free) squaring lane...");
|
|
735
|
+
let curValKvm = sInitialVal;
|
|
736
|
+
const kvmContext = { rels: state.rels, findCode: codes.find, options: { envelopeFree: true } };
|
|
737
|
+
for (let step = 1; step <= squaringSteps; step++) {
|
|
738
|
+
try {
|
|
739
|
+
const t0 = performance.now();
|
|
740
|
+
curValKvm = executeKVM(sRel.kvmFunc, curValKvm, kvmContext);
|
|
741
|
+
const t1 = performance.now();
|
|
742
|
+
const timeMs = t1 - t0;
|
|
743
|
+
const mem = getNodeMemory();
|
|
744
|
+
squaringResults.kvm.push({ status: "ok", timeMs, memMb: mem.heapMb, value: curValKvm });
|
|
745
|
+
if (stepMetadata[step - 1] && stepMetadata[step - 1].bits == null) {
|
|
746
|
+
stepMetadata[step - 1].bits = countBitsValue(curValKvm);
|
|
747
|
+
}
|
|
748
|
+
if (timeMs > squaringStepTimeoutMs) break;
|
|
749
|
+
} catch (e) {
|
|
750
|
+
squaringResults.kvm.push({ status: "failed", error: e.message?.split("\n")[0] || String(e) });
|
|
751
|
+
break;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
} finally {
|
|
757
|
+
if (sArm64Server) sArm64Server.close();
|
|
758
|
+
if (sWasmServer) sWasmServer.close();
|
|
759
|
+
if (sLlvmServer) sLlvmServer.close();
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// For any remaining steps without bit counts, decode canonicalWire safely to get bits:
|
|
763
|
+
for (let step = 1; step <= squaringSteps; step++) {
|
|
764
|
+
const meta = stepMetadata[step - 1];
|
|
765
|
+
if (meta && meta.bits == null && meta.canonicalWire) {
|
|
766
|
+
try {
|
|
767
|
+
const decoded = decodeWire(meta.canonicalWire).value;
|
|
768
|
+
meta.bits = countBitsValue(decoded);
|
|
769
|
+
} catch {}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// Display results table
|
|
774
|
+
console.log("\n======================== SQUARING SCALING & MEMORY BENCHMARK ========================");
|
|
775
|
+
console.log(`Expression: s = {()x,()y}times; ${squaringInputText} ${"s ".repeat(squaringSteps).trim()}`);
|
|
776
|
+
console.log(`Initial: ${squaringInputText} (${countBitsValue(sInitialVal)} bits)`);
|
|
777
|
+
console.log("----------------------------------------------------------------------------------------------------------------------------------");
|
|
778
|
+
const headerCols = ["Step", "Bits", "Wire(B)", ...squaringLanes.map(l => `${l.name} (${l.memLabel})`)];
|
|
779
|
+
const colWidths = [4, 6, 7, ...squaringLanes.map(l => Math.max(22, l.name.length + l.memLabel.length + 5))];
|
|
780
|
+
console.log(headerCols.map((h, i) => i < 3 ? h.padStart(colWidths[i]) : h.padEnd(colWidths[i])).join(" | "));
|
|
781
|
+
console.log(colWidths.map(w => "-".repeat(w)).join("-|-"));
|
|
782
|
+
|
|
783
|
+
for (let step = 1; step <= squaringSteps; step++) {
|
|
784
|
+
const meta = stepMetadata[step - 1] || { bits: "?", wireLen: "?" };
|
|
785
|
+
const cols = [
|
|
786
|
+
String(step).padStart(colWidths[0]),
|
|
787
|
+
String(meta.bits ?? "?").padStart(colWidths[1]),
|
|
788
|
+
String(meta.wireLen ?? "?").padStart(colWidths[2])
|
|
789
|
+
];
|
|
790
|
+
for (let li = 0; li < squaringLanes.length; li++) {
|
|
791
|
+
const lane = squaringLanes[li];
|
|
792
|
+
const res = squaringResults[lane.id][step - 1];
|
|
793
|
+
const w = colWidths[3 + li];
|
|
794
|
+
if (!res) {
|
|
795
|
+
cols.push("-".padEnd(w));
|
|
796
|
+
} else if (res.status === "ok") {
|
|
797
|
+
const t = res.timeMs < 10 ? `${res.timeMs.toFixed(2)}ms` : `${res.timeMs.toFixed(1)}ms`;
|
|
798
|
+
const m = res.memMb != null ? `${res.memMb}MB` : "";
|
|
799
|
+
cols.push(`${t} / ${m}`.padEnd(w));
|
|
800
|
+
} else {
|
|
801
|
+
let errText = res.error;
|
|
802
|
+
if (errText.includes("Maximum call stack")) {
|
|
803
|
+
errText = "Stack overflow (V8)";
|
|
804
|
+
} else if (errText.includes("status null") || errText.includes("failed with status")) {
|
|
805
|
+
errText = "Arena limit reached";
|
|
806
|
+
}
|
|
807
|
+
cols.push(errText.slice(0, w - 1).padEnd(w));
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
console.log(cols.join(" | "));
|
|
811
|
+
}
|
|
812
|
+
console.log("==================================================================================================================================");
|
|
813
|
+
|
|
814
|
+
// Conformance check across all computed steps
|
|
815
|
+
let squaringConformanceOk = true;
|
|
816
|
+
for (let step = 1; step <= squaringSteps; step++) {
|
|
817
|
+
const meta = stepMetadata[step - 1];
|
|
818
|
+
if (!meta?.canonicalWire) continue;
|
|
819
|
+
for (const lane of squaringLanes) {
|
|
820
|
+
const res = squaringResults[lane.id][step - 1];
|
|
821
|
+
if (res?.status === "ok") {
|
|
822
|
+
if (res.wire) {
|
|
823
|
+
if (Buffer.compare(res.wire, meta.canonicalWire) !== 0) {
|
|
824
|
+
squaringConformanceOk = false;
|
|
825
|
+
console.log(`Squaring conformance mismatch on step ${step} (${lane.name})`);
|
|
826
|
+
}
|
|
827
|
+
} else if (res.value) {
|
|
828
|
+
try {
|
|
829
|
+
const wireVal = decodeWire(meta.canonicalWire).value;
|
|
830
|
+
if (!safeDeepEqual(res.value, wireVal)) {
|
|
831
|
+
squaringConformanceOk = false;
|
|
832
|
+
console.log(`Squaring conformance mismatch on step ${step} (${lane.name})`);
|
|
833
|
+
}
|
|
834
|
+
} catch {}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
console.log("\nSquaring Performance & Scaling Summary:");
|
|
841
|
+
for (const lane of squaringLanes) {
|
|
842
|
+
const okSteps = squaringResults[lane.id].filter(r => r.status === "ok");
|
|
843
|
+
const maxStep = okSteps.length;
|
|
844
|
+
const lastOk = okSteps[okSteps.length - 1];
|
|
845
|
+
const peakMem = okSteps.reduce((max, r) => Math.max(max, r.memMb || 0), 0);
|
|
846
|
+
const failureReason = squaringResults[lane.id][maxStep]?.error;
|
|
847
|
+
let statusDetail = "";
|
|
848
|
+
if (failureReason) {
|
|
849
|
+
if (failureReason.includes("Maximum call stack")) statusDetail = " (V8 call stack overflow)";
|
|
850
|
+
else if (failureReason.includes("status null") || failureReason.includes("failed with status")) statusDetail = " (arena capacity limit reached)";
|
|
851
|
+
else statusDetail = ` (${failureReason.slice(0, 30)})`;
|
|
852
|
+
}
|
|
853
|
+
const timeStr = lastOk ? ` (step ${maxStep} in ${lastOk.timeMs < 10 ? lastOk.timeMs.toFixed(2) : lastOk.timeMs.toFixed(1)}ms)` : "";
|
|
854
|
+
console.log(` - ${lane.name.padEnd(32)}: reached Step ${String(maxStep).padStart(2)}/${squaringSteps}${timeStr}, peak ${lane.memLabel}: ${peakMem}MB${statusDetail}`);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
if (squaringConformanceOk) {
|
|
858
|
+
console.log("\nSquaring Conformance: All completed squaring steps match expected values across all backends!\n");
|
|
859
|
+
} else {
|
|
860
|
+
console.log("\nSquaring Conformance: Warning, some step outputs differed between backends!\n");
|
|
861
|
+
process.exitCode = 1;
|
|
862
|
+
}
|
package/scripts/run-tests.mjs
CHANGED
|
@@ -26,6 +26,7 @@ const tests = [
|
|
|
26
26
|
["conformance/run.mjs", node, ["conformance/run.mjs"]],
|
|
27
27
|
["tests/test-repl.mjs", node, ["tests/test-repl.mjs"]],
|
|
28
28
|
["tests/test-repl-html.mjs", node, ["tests/test-repl-html.mjs"]],
|
|
29
|
+
["tests/test-tree-view.mjs", node, ["tests/test-tree-view.mjs"]],
|
|
29
30
|
["tests/test-kvm.mjs", node, ["tests/test-kvm.mjs"]],
|
|
30
31
|
["tests/test-kvm-polymorphic.mjs", node, ["tests/test-kvm-polymorphic.mjs"]],
|
|
31
32
|
["tests/test-arithmetics.mjs", node, ["tests/test-arithmetics.mjs"]],
|
package/tests/test-repl-html.mjs
CHANGED
|
@@ -287,6 +287,147 @@ if (chromiumBin) {
|
|
|
287
287
|
})()`);
|
|
288
288
|
assert.strictEqual(selectionCheck, true, "Selection in terminal output should hold after click");
|
|
289
289
|
|
|
290
|
+
// === Test Tree View Functionality ===
|
|
291
|
+
console.log(" Verifying Tree View: initial depth <= 3, folding/unfolding, mode toggle, and pattern cycles...");
|
|
292
|
+
|
|
293
|
+
// 1. Evaluate a 5-level deep nested product: { { { { {} e } d } c } b }
|
|
294
|
+
await evaluateAsync(`window.kRepl.executeCommand("{ { { { {} e } d } c } b }")`);
|
|
295
|
+
|
|
296
|
+
const treeCheck = await evaluateAsync(`(() => {
|
|
297
|
+
const entries = document.querySelectorAll(".terminal-entry");
|
|
298
|
+
const lastEntry = entries[entries.length - 1];
|
|
299
|
+
const container = lastEntry.querySelector(".k-entry-container");
|
|
300
|
+
if (!container) return { error: "No .k-entry-container found" };
|
|
301
|
+
|
|
302
|
+
const treeView = container.querySelector(".k-tree-view");
|
|
303
|
+
const rawView = container.querySelector(".k-raw-view");
|
|
304
|
+
const treeBtn = container.querySelector(".k-btn-mode.active");
|
|
305
|
+
const rawBtn = container.querySelectorAll(".k-btn-mode")[1];
|
|
306
|
+
|
|
307
|
+
// Check Value section
|
|
308
|
+
const valSection = container.querySelector(".k-tree-value-section");
|
|
309
|
+
if (!valSection) return { error: "No value section" };
|
|
310
|
+
|
|
311
|
+
// Check product details nodes hierarchy
|
|
312
|
+
// Level 1: root product (depth 1)
|
|
313
|
+
const level1 = valSection.querySelector(".k-tree-section-body > .k-tree-node.k-tree-product");
|
|
314
|
+
if (!level1 || !level1.open) return { error: "Level 1 product should be open" };
|
|
315
|
+
|
|
316
|
+
// Level 2: .b product (depth 2)
|
|
317
|
+
const level2 = level1.querySelector(".k-tree-children .k-tree-node.k-tree-product");
|
|
318
|
+
if (!level2 || !level2.open) return { error: "Level 2 product should be open" };
|
|
319
|
+
|
|
320
|
+
// Level 3: .c product (depth 3)
|
|
321
|
+
const level3 = level2.querySelector(".k-tree-children .k-tree-node.k-tree-product");
|
|
322
|
+
if (!level3 || !level3.open) return { error: "Level 3 product should be open" };
|
|
323
|
+
|
|
324
|
+
// Level 4: .d product (depth 4) -> MUST BE COLLAPSED (open === false)
|
|
325
|
+
const level4 = level3.querySelector(".k-tree-children .k-tree-node.k-tree-product");
|
|
326
|
+
if (!level4) return { error: "Level 4 product node missing" };
|
|
327
|
+
if (level4.open) return { error: "Level 4 product should be collapsed (open === false)" };
|
|
328
|
+
|
|
329
|
+
// Check pattern section exists and has cycle or vardef
|
|
330
|
+
const patSection = container.querySelector(".k-tree-pat-section");
|
|
331
|
+
if (!patSection) return { error: "No pattern section" };
|
|
332
|
+
|
|
333
|
+
// Test Mode Toggle: click Raw
|
|
334
|
+
rawBtn.click();
|
|
335
|
+
const rawVisible = rawView.style.display !== "none" && treeView.style.display === "none";
|
|
336
|
+
|
|
337
|
+
// Click Tree back
|
|
338
|
+
container.querySelector(".k-btn-mode").click();
|
|
339
|
+
const treeVisible = treeView.style.display !== "none" && rawView.style.display === "none";
|
|
340
|
+
|
|
341
|
+
// Test Unfolding Level 4
|
|
342
|
+
level4.open = true;
|
|
343
|
+
level4.dispatchEvent(new Event("toggle"));
|
|
344
|
+
|
|
345
|
+
// After toggle, level 4's children should be populated with .e
|
|
346
|
+
const level4Text = level4.textContent;
|
|
347
|
+
const unfoldedHasE = level4Text.includes(".e") || level4Text.includes("42");
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
success: true,
|
|
351
|
+
rawVisible,
|
|
352
|
+
treeVisible,
|
|
353
|
+
unfoldedHasE
|
|
354
|
+
};
|
|
355
|
+
})()`);
|
|
356
|
+
|
|
357
|
+
assert.strictEqual(treeCheck.success, true, `Tree view validation failed: ${treeCheck.error}`);
|
|
358
|
+
assert.strictEqual(treeCheck.rawVisible, true, "Clicking Raw should show raw view and hide tree view");
|
|
359
|
+
assert.strictEqual(treeCheck.treeVisible, true, "Clicking Tree should restore tree view");
|
|
360
|
+
assert.strictEqual(treeCheck.unfoldedHasE, true, "Unfolding level 4 should lazily populate its children");
|
|
361
|
+
|
|
362
|
+
// 2. Test recursive pattern tree with cycle detection
|
|
363
|
+
await evaluateAsync(`window.kRepl.executeCommand("10")`);
|
|
364
|
+
const patCycleCheck = await evaluateAsync(`(() => {
|
|
365
|
+
const entries = document.querySelectorAll(".terminal-entry");
|
|
366
|
+
const lastEntry = entries[entries.length - 1];
|
|
367
|
+
const patSection = lastEntry.querySelector(".k-tree-pat-section");
|
|
368
|
+
if (!patSection) return { error: "No pattern section for 10" };
|
|
369
|
+
|
|
370
|
+
const cycleBadges = patSection.querySelectorAll(".k-badge-cycle");
|
|
371
|
+
const varDefs = patSection.querySelectorAll(".k-pat-vardef");
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
hasCycleBadge: cycleBadges.length > 0,
|
|
375
|
+
hasVarDef: varDefs.length > 0
|
|
376
|
+
};
|
|
377
|
+
})()`);
|
|
378
|
+
|
|
379
|
+
assert.strictEqual(patCycleCheck.hasCycleBadge, true, "Pattern tree for recursive type should have cycle badge");
|
|
380
|
+
assert.strictEqual(patCycleCheck.hasVarDef, true, "Pattern tree for recursive type should have variable definition (=X0)");
|
|
381
|
+
|
|
382
|
+
// 3. Test variant chain depth limits and Expand All / Collapse All on 10
|
|
383
|
+
const variantDepthAndActionsCheck = await evaluateAsync(`(() => {
|
|
384
|
+
const entries = document.querySelectorAll(".terminal-entry");
|
|
385
|
+
const lastEntry = entries[entries.length - 1];
|
|
386
|
+
const valSection = lastEntry.querySelector(".k-tree-value-section");
|
|
387
|
+
|
|
388
|
+
// Hierarchy for 10 (variant chain: + -> 1 -> 0 -> 1 -> ...)
|
|
389
|
+
const v1 = valSection?.querySelector(".k-tree-section-body > .k-tree-node.k-tree-variant");
|
|
390
|
+
const v2 = v1?.querySelector(".k-tree-children .k-tree-node.k-tree-variant");
|
|
391
|
+
const v3 = v2?.querySelector(".k-tree-children .k-tree-node.k-tree-variant");
|
|
392
|
+
const v4 = v3?.querySelector(".k-tree-children .k-tree-node.k-tree-variant");
|
|
393
|
+
|
|
394
|
+
const v1Open = !!v1?.open;
|
|
395
|
+
const v2Open = !!v2?.open;
|
|
396
|
+
const v3Open = !!v3?.open;
|
|
397
|
+
const v4Collapsed = v4 ? !v4.open : false;
|
|
398
|
+
|
|
399
|
+
// Click Expand All
|
|
400
|
+
const actionBtns = lastEntry.querySelectorAll(".k-btn-action");
|
|
401
|
+
const expandBtn = actionBtns[0];
|
|
402
|
+
const collapseBtn = actionBtns[1];
|
|
403
|
+
|
|
404
|
+
expandBtn.click();
|
|
405
|
+
const allOpenAfterExpand = v4.open;
|
|
406
|
+
|
|
407
|
+
// Click Collapse All
|
|
408
|
+
collapseBtn.click();
|
|
409
|
+
const newV4 = valSection?.querySelector(
|
|
410
|
+
".k-tree-section-body > .k-tree-node.k-tree-variant .k-tree-children .k-tree-node.k-tree-variant .k-tree-children .k-tree-node.k-tree-variant .k-tree-children .k-tree-node.k-tree-variant"
|
|
411
|
+
);
|
|
412
|
+
const v4CollapsedAgain = newV4 ? !newV4.open : false;
|
|
413
|
+
|
|
414
|
+
return {
|
|
415
|
+
v1Open,
|
|
416
|
+
v2Open,
|
|
417
|
+
v3Open,
|
|
418
|
+
v4Collapsed,
|
|
419
|
+
allOpenAfterExpand,
|
|
420
|
+
v4CollapsedAgain
|
|
421
|
+
};
|
|
422
|
+
})()`);
|
|
423
|
+
|
|
424
|
+
assert.strictEqual(variantDepthAndActionsCheck.v1Open, true, "Variant level 1 must be open");
|
|
425
|
+
assert.strictEqual(variantDepthAndActionsCheck.v2Open, true, "Variant level 2 must be open");
|
|
426
|
+
assert.strictEqual(variantDepthAndActionsCheck.v3Open, true, "Variant level 3 must be open");
|
|
427
|
+
assert.strictEqual(variantDepthAndActionsCheck.v4Collapsed, true, "Variant level 4 must be collapsed by default");
|
|
428
|
+
assert.strictEqual(variantDepthAndActionsCheck.allOpenAfterExpand, true, "Expand button should expand collapsed nodes");
|
|
429
|
+
assert.strictEqual(variantDepthAndActionsCheck.v4CollapsedAgain, true, "Collapse button should reset deeper nodes to collapsed");
|
|
430
|
+
|
|
290
431
|
ws.close();
|
|
291
432
|
console.log(" Browser execution verified successfully!");
|
|
292
433
|
} finally {
|