Use match for value dispatch in evaluator and compiler

Convert large cond chains doing string equality dispatch to use the
match special form: step-eval-list (42 arms), step-continue (31 arms),
compile-list (30 arms), ho-setup-dispatch (7 arms), value-matches-type?
(10 arms). Also fix test-canonical.sx to use defsuite/deftest format
and load canonical.sx in both test runners.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-29 07:53:16 +00:00
parent 394c86b474
commit 8bba02fbc9
5 changed files with 1399 additions and 1414 deletions

View File

@@ -82,6 +82,18 @@ env["env-merge"] = function(a, b) { return Object.assign({}, a, b); };
// Missing primitives referenced by tests // Missing primitives referenced by tests
// primitive? is now in platform.py PRIMITIVES // primitive? is now in platform.py PRIMITIVES
env["contains-char?"] = function(s, c) { return typeof s === "string" && typeof c === "string" && s.indexOf(c) >= 0; };
env["escape-string"] = function(s) { return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\t/g, "\\t"); };
env["trim-right"] = function(s) { return typeof s === "string" ? s.trimEnd() : s; };
env["sha3-256"] = function(s) {
// Simple hash stub for testing — not real SHA3
var h = 0;
for (var i = 0; i < s.length; i++) { h = ((h << 5) - h + s.charCodeAt(i)) | 0; }
h = Math.abs(h);
var hex = h.toString(16);
while (hex.length < 64) hex = "0" + hex;
return hex;
};
env["upcase"] = function(s) { return s.toUpperCase(); }; env["upcase"] = function(s) { return s.toUpperCase(); };
env["downcase"] = function(s) { return s.toLowerCase(); }; env["downcase"] = function(s) { return s.toLowerCase(); };
env["make-keyword"] = function(name) { return new Sx.Keyword(name); }; env["make-keyword"] = function(name) { return new Sx.Keyword(name); };
@@ -291,6 +303,18 @@ if (fs.existsSync(harnessPath)) {
} }
} }
// Load canonical.sx (content-addressing, serialization)
const canonicalPath = path.join(projectDir, "spec", "canonical.sx");
if (fs.existsSync(canonicalPath)) {
const canonicalSrc = fs.readFileSync(canonicalPath, "utf8");
const canonicalExprs = Sx.parse(canonicalSrc);
for (const expr of canonicalExprs) {
try { Sx.eval(expr, env); } catch (e) {
console.error(`Error loading canonical.sx: ${e.message}`);
}
}
}
// Load compiler + VM from lib/ when running full tests // Load compiler + VM from lib/ when running full tests
if (fullBuild) { if (fullBuild) {
const libDir = path.join(projectDir, "lib"); const libDir = path.join(projectDir, "lib");

View File

@@ -390,6 +390,22 @@ let make_test_env () =
bind "defeffect" (fun _args -> Nil); bind "defeffect" (fun _args -> Nil);
(* --- Primitives for canonical.sx / content tests --- *) (* --- Primitives for canonical.sx / content tests --- *)
bind "symbol-name" (fun args ->
match args with
| [Symbol s] -> String s
| _ -> raise (Eval_error "symbol-name: expected symbol"));
bind "keyword-name" (fun args ->
match args with
| [Keyword k] -> String k
| _ -> raise (Eval_error "keyword-name: expected keyword"));
bind "trim-right" (fun args ->
match args with
| [String s] ->
let len = String.length s in
let i = ref (len - 1) in
while !i >= 0 && (s.[!i] = ' ' || s.[!i] = '\t' || s.[!i] = '\n' || s.[!i] = '\r') do decr i done;
String (String.sub s 0 (!i + 1))
| _ -> raise (Eval_error "trim-right: expected string"));
bind "contains-char?" (fun args -> bind "contains-char?" (fun args ->
match args with match args with
| [String s; String c] when String.length c = 1 -> | [String s; String c] when String.length c = 1 ->
@@ -809,6 +825,8 @@ let run_spec_tests env test_files =
with e -> Printf.eprintf "Warning: %s: %s\n%!" name (Printexc.to_string e)) with e -> Printf.eprintf "Warning: %s: %s\n%!" name (Printexc.to_string e))
end end
in in
(* Content-addressing, serialization *)
load_module "canonical.sx" spec_dir;
(* Render adapter for test-render-html.sx *) (* Render adapter for test-render-html.sx *)
load_module "render.sx" spec_dir; load_module "render.sx" spec_dir;
load_module "adapter-html.sx" web_dir; load_module "adapter-html.sx" web_dir;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,91 +1,97 @@
(test-group (defsuite
"canonical-serialize" "canonical-serialize"
(test "nil" (assert= (canonical-serialize nil) "nil")) (deftest "nil" (assert= (canonical-serialize nil) "nil"))
(test (deftest
"booleans" "booleans"
(assert= (canonical-serialize true) "true") (assert= (canonical-serialize true) "true")
(assert= (canonical-serialize false) "false")) (assert= (canonical-serialize false) "false"))
(test (deftest
"integers" "integers"
(assert= (canonical-serialize 0) "0") (assert= (canonical-serialize 0) "0")
(assert= (canonical-serialize 42) "42") (assert= (canonical-serialize 42) "42")
(assert= (canonical-serialize -7) "-7")) (assert= (canonical-serialize -7) "-7"))
(test (deftest
"strings" "strings"
(assert= (canonical-serialize "hello") "\"hello\"") (assert= (canonical-serialize "hello") "\"hello\"")
(assert= (canonical-serialize "") "\"\"") (assert= (canonical-serialize "") "\"\"")
(assert= (canonical-serialize "a\"b") "\"a\\\"b\"")) (assert= (canonical-serialize "a\"b") "\"a\\\"b\""))
(test (deftest
"symbols" "symbols"
(assert= (canonical-serialize (quote deref)) "deref") (assert= (canonical-serialize (quote deref)) "deref")
(assert= (canonical-serialize (quote swap!)) "swap!")) (assert= (canonical-serialize (quote swap!)) "swap!"))
(test (deftest
"keywords" "keywords"
(assert= (canonical-serialize :class) ":class") (assert= (canonical-serialize (make-keyword "class")) ":class")
(assert= (canonical-serialize :arity) ":arity")) (assert= (canonical-serialize (make-keyword "arity")) ":arity"))
(test "empty list" (assert= (canonical-serialize (list)) "()")) (deftest "empty list" (assert= (canonical-serialize (list)) "()"))
(test "flat list" (assert= (canonical-serialize (list 1 2 3)) "(1 2 3)")) (deftest
(test "flat list"
(assert= (canonical-serialize (list 1 2 3)) "(1 2 3)"))
(deftest
"nested list" "nested list"
(assert= (assert=
(canonical-serialize (canonical-serialize
(list (quote div) :class "flex" (list (quote h2) "title"))) (list
(quote div)
(make-keyword "class")
"flex"
(list (quote h2) "title")))
"(div :class \"flex\" (h2 \"title\"))")) "(div :class \"flex\" (h2 \"title\"))"))
(test (deftest
"dict keys sorted" "dict keys sorted"
(let (let
((d (dict "zebra" 1 "alpha" 2 "middle" 3))) ((d {:zebra 1 :middle 3 :alpha 2}))
(assert= (canonical-serialize d) "{:alpha 2 :middle 3 :zebra 1}"))) (assert= (canonical-serialize d) "{:alpha 2 :middle 3 :zebra 1}")))
(test (deftest
"dict with nested values" "dict with nested values"
(let (let
((d (dict "a" (list 1 2) "b" "hello"))) ((d {:b "hello" :a (list 1 2)}))
(assert= (canonical-serialize d) "{:a (1 2) :b \"hello\"}")))) (assert= (canonical-serialize d) "{:a (1 2) :b \"hello\"}"))))
(test-group (defsuite
"content-id" "content-id"
(test (deftest
"same expression same CID" "same expression same CID"
(assert= (content-id (list 1 2 3)) (content-id (list 1 2 3)))) (assert= (content-id (list 1 2 3)) (content-id (list 1 2 3))))
(test (deftest
"different expression different CID" "different expression different CID"
(assert (assert
(not (= (content-id (list 1 2 3)) (content-id (list 1 2 4)))))) (not (= (content-id (list 1 2 3)) (content-id (list 1 2 4))))))
(test (deftest
"CID is a hex string" "CID is a hex string"
(let (let
((cid (content-id 42))) ((cid (content-id 42)))
(assert (string? cid)) (assert (string? cid))
(assert= (len cid) 64))) (assert= (len cid) 64)))
(test (deftest
"short CID is 16 chars" "short CID is 16 chars"
(let ((cid (content-id-short 42))) (assert= (len cid) 16))) (let ((cid (content-id-short 42))) (assert= (len cid) 16)))
(test (deftest
"short CID is prefix of full CID" "short CID is prefix of full CID"
(let (let
((full (content-id 42)) (short (content-id-short 42))) ((full (content-id 42)) (short (content-id-short 42)))
(assert= short (slice full 0 16))))) (assert= short (slice full 0 16)))))
(test-group (defsuite
"bytecode-module" "bytecode-module"
(test (deftest
"make and query" "make and query"
(let (let
((m (make-bytecode-module 1 "abc123" (list (quote code) :bytecode (list 1 2 3))))) ((m (make-bytecode-module 1 "abc123" (list 1 2 3))))
(assert (bytecode-module? m)) (assert (bytecode-module? m))
(assert= (bytecode-module-version m) 1) (assert= (bytecode-module-version m) 1)
(assert= (bytecode-module-source-hash m) "abc123"))) (assert= (bytecode-module-source-hash m) "abc123")))
(test (deftest
"non-module fails predicate" "non-module fails predicate"
(assert (not (bytecode-module? (list 1 2 3)))) (assert (not (bytecode-module? (list 1 2 3))))
(assert (not (bytecode-module? "hello"))))) (assert (not (bytecode-module? "hello")))))
(test-group (defsuite
"provenance" "provenance"
(test (deftest
"make provenance record" "make provenance record"
(let (let
((p (make-provenance "src-cid" "bc-cid" "compiler-cid" "2026-03-27T00:00:00Z"))) ((p (make-provenance "src-cid" "bc-cid" "v1" "js")))
(assert= (first p) (quote provenance)) (assert= (first p) (quote provenance))
(assert= (nth p 2) "src-cid") (assert= (nth p 2) "src-cid")
(assert= (nth p 4) "bc-cid")))) (assert= (nth p 4) "bc-cid"))))