From 207dfc60ad3b1c2346b7172edf8df678747ccaee Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 9 May 2026 00:53:32 +0000 Subject: [PATCH] ocaml: phase 6 Hashtbl.iter / Hashtbl.fold (+2 tests, 494 total) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New host primitive _hashtbl_to_list returns the entries as a list of OCaml tuples — ('tuple' k v) form, matching the AST representation that the pattern-match VM (:ptuple) expects. Without that exact shape, '(k, v) :: rest' patterns fail to match. Hashtbl.iter / Hashtbl.fold in runtime walk that list with the user fn. This closes a long-standing gap: previously Hashtbl was opaque once values were written (we could only find_opt one key at a time). let t = Hashtbl.create 4 in Hashtbl.add t "a" 1; Hashtbl.add t "b" 2; Hashtbl.add t "c" 3; Hashtbl.fold (fun _ v acc -> acc + v) t 0 = 6 --- lib/ocaml/eval.sx | 18 +++++++++++++++++- lib/ocaml/runtime.sx | 18 ++++++++++++++++++ lib/ocaml/test.sh | 10 ++++++++++ plans/ocaml-on-sx.md | 6 ++++++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/lib/ocaml/eval.sx b/lib/ocaml/eval.sx index 76ed38a7..c88b73ff 100644 --- a/lib/ocaml/eval.sx +++ b/lib/ocaml/eval.sx @@ -115,7 +115,23 @@ (list "_hashtbl_mem" (fn (t) (fn (k) (has-key? (nth t 0) (str k))))) (list "_hashtbl_length" - (fn (t) (len (keys (nth t 0)))))))) + (fn (t) (len (keys (nth t 0))))) + ;; _hashtbl_to_list: returns [(k, v); ...] as a list of pairs. + ;; Keys are returned as the stringified form used internally. + (list "_hashtbl_to_list" + (fn (t) + (let ((d (nth t 0)) (out (list))) + (begin + (define ks (keys d)) + (define loop + (fn (xs) + (when (not (= xs (list))) + (begin + (append! out + (list "tuple" (first xs) (get d (first xs)))) + (loop (rest xs)))))) + (loop ks) + out))))))) (define ocaml-env-lookup (fn (env name) diff --git a/lib/ocaml/runtime.sx b/lib/ocaml/runtime.sx index b9fa6373..3b269af4 100644 --- a/lib/ocaml/runtime.sx +++ b/lib/ocaml/runtime.sx @@ -530,6 +530,24 @@ | Some v -> v let mem t k = _hashtbl_mem t k let length t = _hashtbl_length t + + (* iter / fold over (k, v) pairs. Keys come back as their string + representation since the host coerces all keys via `str`. *) + let iter f t = + let rec go xs = + match xs with + | [] -> () + | (k, v) :: rest -> f k v; go rest + in + go (_hashtbl_to_list t) + + let fold f t acc = + let rec go xs a = + match xs with + | [] -> a + | (k, v) :: rest -> go rest (f k v a) + in + go (_hashtbl_to_list t) acc end ;; module Map = struct diff --git a/lib/ocaml/test.sh b/lib/ocaml/test.sh index 68a5fd2c..cea5865b 100755 --- a/lib/ocaml/test.sh +++ b/lib/ocaml/test.sh @@ -1222,6 +1222,12 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 4944) (eval "(ocaml-run \"string_of_int 7 ^ \\\"-\\\" ^ string_of_bool true\")") +;; ── Hashtbl.iter / Hashtbl.fold ───────────────────────────── +(epoch 4950) +(eval "(ocaml-run \"let t = Hashtbl.create 4 in Hashtbl.add t \\\"a\\\" 1; Hashtbl.add t \\\"b\\\" 2; Hashtbl.add t \\\"c\\\" 3; Hashtbl.fold (fun _ v acc -> acc + v) t 0\")") +(epoch 4951) +(eval "(ocaml-run \"let t = Hashtbl.create 4 in Hashtbl.add t \\\"x\\\" 10; Hashtbl.add t \\\"y\\\" 20; let total = ref 0 in Hashtbl.iter (fun _ v -> total := !total + v) t; !total\")") + EPOCHS OUTPUT=$(timeout 360 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -1938,6 +1944,10 @@ check 4942 "sprintf %s = %d" '"answer = 42"' check 4943 "sprintf %d%% literal percent" '"50%"' check 4944 "string_of_int + string_of_b" '"7-true"' +# ── Hashtbl.iter / Hashtbl.fold ───────────────────────────────── +check 4950 "Hashtbl.fold sum 1+2+3" '6' +check 4951 "Hashtbl.iter ref accum 10+20" '30' + TOTAL=$((PASS + FAIL)) if [ $FAIL -eq 0 ]; then echo "ok $PASS/$TOTAL OCaml-on-SX tests passed" diff --git a/plans/ocaml-on-sx.md b/plans/ocaml-on-sx.md index bb65f08a..ab95d4e4 100644 --- a/plans/ocaml-on-sx.md +++ b/plans/ocaml-on-sx.md @@ -407,6 +407,12 @@ _Newest first._ binary search tree (`type 'a tree = Leaf | Node of 'a * 'a tree * 'a tree`) with insert + in-order traversal. Tests parametric ADT, recursive match, List.append, List.fold_left. +- 2026-05-09 Phase 6 — Hashtbl.iter / Hashtbl.fold (+2 tests, 494 + total). New host primitive `_hashtbl_to_list` returns the entries + as a list of OCaml tuples (`("tuple" k v)` form, matching the AST + representation that pattern matching expects). Hashtbl.iter / fold + in runtime walk that list with the user fn. Closes a long-standing + gap: previously Hashtbl was opaque after writing to it. - 2026-05-09 Phase 6 — Printf.sprintf with %d/%s/%f/%c/%b/%% (+4 tests) and global `string_of_int`/`string_of_float`/`string_of_bool` (+1 test). 492 total. sprintf walks fmt char-by-char accumulating