diff --git a/lib/ocaml/eval.sx b/lib/ocaml/eval.sx index 3b3afb9e..f22357d7 100644 --- a/lib/ocaml/eval.sx +++ b/lib/ocaml/eval.sx @@ -171,6 +171,17 @@ (fn (t) (fn (k) (has-key? (nth t 0) (str k))))) (list "_hashtbl_length" (fn (t) (len (keys (nth t 0))))) + (list "_hashtbl_remove" + (fn (t) (fn (k) + (let ((d (nth t 0))) + (begin + (set-nth! t 0 (dissoc d (str k))) + nil))))) + (list "_hashtbl_clear" + (fn (t) + (begin + (set-nth! t 0 {}) + nil))) ;; _lazy_force: evaluate the thunk on first force, cache result. ;; cell: one-elt list whose value is ("Thunk" expr env) or ;; ("Forced" v). diff --git a/lib/ocaml/runtime.sx b/lib/ocaml/runtime.sx index dbd6b7d0..8f40331b 100644 --- a/lib/ocaml/runtime.sx +++ b/lib/ocaml/runtime.sx @@ -774,6 +774,27 @@ | (k, v) :: rest -> go rest (f k v a) in go (_hashtbl_to_list t) acc + + (* OCaml's Hashtbl doesn't expose `keys` directly — it offers + `to_seq_keys` etc. We provide both: a plain list snapshot and + the keys/values projections that are commonly useful. *) + let bindings t = _hashtbl_to_list t + + let keys t = + List.map (fun pair -> match pair with (k, _) -> k) + (_hashtbl_to_list t) + + let values t = + List.map (fun pair -> match pair with (_, v) -> v) + (_hashtbl_to_list t) + + let to_seq t = _hashtbl_to_list t + let to_seq_keys = keys + let to_seq_values = values + + let remove t k = _hashtbl_remove t k + let reset t = _hashtbl_clear t + let clear t = _hashtbl_clear t end ;; module Map = struct diff --git a/lib/ocaml/test.sh b/lib/ocaml/test.sh index f395cc78..c17c1f92 100755 --- a/lib/ocaml/test.sh +++ b/lib/ocaml/test.sh @@ -1350,6 +1350,16 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 5092) (eval "(ocaml-run \"let pair = (5, 7) in let (x, y) = pair in x * y\")") +;; ── Hashtbl.keys / values / remove / clear ──────────────────── +(epoch 5100) +(eval "(ocaml-run \"let t = Hashtbl.create 4 in Hashtbl.add t \\\"a\\\" 1; Hashtbl.add t \\\"b\\\" 2; List.length (Hashtbl.keys t)\")") +(epoch 5101) +(eval "(ocaml-run \"let t = Hashtbl.create 4 in Hashtbl.add t \\\"a\\\" 5; Hashtbl.add t \\\"b\\\" 7; List.fold_left (+) 0 (Hashtbl.values t)\")") +(epoch 5102) +(eval "(ocaml-run \"let t = Hashtbl.create 4 in Hashtbl.add t \\\"x\\\" 1; Hashtbl.add t \\\"y\\\" 2; Hashtbl.remove t \\\"x\\\"; Hashtbl.length t\")") +(epoch 5103) +(eval "(ocaml-run \"let t = Hashtbl.create 4 in Hashtbl.add t \\\"a\\\" 1; Hashtbl.clear t; Hashtbl.length t\")") + EPOCHS OUTPUT=$(timeout 360 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -2145,6 +2155,12 @@ check 5090 "let (a, b) = (1,2)" '3' check 5091 "let (a, b, c) = (10,20,30)" '60' check 5092 "let pair; let (x, y) = pair" '35' +# ── Hashtbl.keys/values/remove/clear ──────────────────────────── +check 5100 "Hashtbl.keys length" '2' +check 5101 "Hashtbl.values sum" '12' +check 5102 "Hashtbl.remove + length" '1' +check 5103 "Hashtbl.clear + length" '0' + 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 ae9de5b5..e95cc8dd 100644 --- a/plans/ocaml-on-sx.md +++ b/plans/ocaml-on-sx.md @@ -407,6 +407,16 @@ _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.keys / values / bindings / remove / + clear / reset / to_seq / to_seq_keys / to_seq_values (+4 tests, 545 + total). Two new host primitives `_hashtbl_remove` and + `_hashtbl_clear`; the rest are pure OCaml-syntax helpers in + runtime.sx that map over `_hashtbl_to_list`. `keys` and `values` + pattern-match the (k, v) tuples to extract one side. Note: a + detour to also support top-level `let (a, b) = expr` was reverted + — `parse-decl-let` lives in the outer ocaml-parse-program scope + which doesn't have access to parse-pattern; will need a slice + + inner-parse trick later. - 2026-05-09 Phase 4 — `let PATTERN = expr in body` tuple destructuring (+3 tests, 541 total). When `let` is followed by `(`, parse-let now reads a full pattern, expects `=`, then `in`, and