diff --git a/lib/ocaml/eval.sx b/lib/ocaml/eval.sx index 72e22faa..7835dd48 100644 --- a/lib/ocaml/eval.sx +++ b/lib/ocaml/eval.sx @@ -66,7 +66,30 @@ ;; Print: prints to host stdout via println. (list "print_string" (fn (s) (begin (print s) nil))) (list "print_endline" (fn (s) (begin (println s) nil))) - (list "print_int" (fn (i) (begin (print (str i)) nil)))))) + (list "print_int" (fn (i) (begin (print (str i)) nil))) + ;; Hashtbl primitives (one-element list cell holding a dict). + ;; Keys are coerced to strings via `str` so any value type works + ;; as a key (matches Hashtbl's polymorphic-key semantics). + (list "_hashtbl_create" (fn (n) (list {}))) + (list "_hashtbl_add" + (fn (t) (fn (k) (fn (v) + (begin + (set-nth! t 0 (merge (nth t 0) (dict (str k) v))) + nil))))) + (list "_hashtbl_replace" + (fn (t) (fn (k) (fn (v) + (begin + (set-nth! t 0 (merge (nth t 0) (dict (str k) v))) + nil))))) + (list "_hashtbl_find_opt" + (fn (t) (fn (k) + (cond + ((has-key? (nth t 0) (str k)) (list "Some" (get (nth t 0) (str k)))) + (else (list "None")))))) + (list "_hashtbl_mem" + (fn (t) (fn (k) (has-key? (nth t 0) (str k))))) + (list "_hashtbl_length" + (fn (t) (len (keys (nth t 0)))))))) (define ocaml-env-lookup (fn (env name) diff --git a/lib/ocaml/runtime.sx b/lib/ocaml/runtime.sx index 701dfb2c..8ad9cdc4 100644 --- a/lib/ocaml/runtime.sx +++ b/lib/ocaml/runtime.sx @@ -266,6 +266,19 @@ module Printf = struct let sprintf fmt = fmt let printf fmt = print_string fmt + end ;; + + module Hashtbl = struct + let create n = _hashtbl_create n + let add t k v = _hashtbl_add t k v + let replace t k v = _hashtbl_replace t k v + let find_opt t k = _hashtbl_find_opt t k + let find t k = + match _hashtbl_find_opt t k with + | None -> failwith \"Hashtbl.find: not found\" + | Some v -> v + let mem t k = _hashtbl_mem t k + let length t = _hashtbl_length t end") (define ocaml-stdlib-loaded false) diff --git a/lib/ocaml/test.sh b/lib/ocaml/test.sh index da64ab52..31587ac2 100755 --- a/lib/ocaml/test.sh +++ b/lib/ocaml/test.sh @@ -814,6 +814,20 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 1506) (eval "(ocaml-type-of \"[true; false]\")") +;; ── Hashtbl ──────────────────────────────────────────────────── +(epoch 1600) +(eval "(ocaml-run-program \"let t = Hashtbl.create 10;; Hashtbl.add t \\\"x\\\" 42;; Hashtbl.find t \\\"x\\\"\")") +(epoch 1601) +(eval "(ocaml-run-program \"let t = Hashtbl.create 10;; Hashtbl.add t 1 \\\"a\\\";; Hashtbl.add t 2 \\\"b\\\";; Hashtbl.length t\")") +(epoch 1602) +(eval "(ocaml-run-program \"let t = Hashtbl.create 10;; Hashtbl.add t 1 100;; Hashtbl.find_opt t 99\")") +(epoch 1603) +(eval "(ocaml-run-program \"let t = Hashtbl.create 10;; Hashtbl.add t 1 100;; Hashtbl.mem t 1\")") +(epoch 1604) +(eval "(ocaml-run-program \"let t = Hashtbl.create 10;; Hashtbl.add t \\\"a\\\" 1;; Hashtbl.replace t \\\"a\\\" 2;; Hashtbl.find t \\\"a\\\"\")") +(epoch 1605) +(eval "(ocaml-run-program \"let t = Hashtbl.create 10;; Hashtbl.add t \\\"k\\\" 5;; Hashtbl.find_opt t \\\"k\\\"\")") + EPOCHS OUTPUT=$(timeout 180 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -1288,6 +1302,14 @@ check 1504 "type fn -> list" 'list"' check 1505 "type fn -> tuple" ' * ' check 1506 "type Bool list" '"Bool list"' +# ── Hashtbl ───────────────────────────────────────────────────── +check 1600 "Hashtbl find" '42' +check 1601 "Hashtbl length" '2' +check 1602 "Hashtbl find_opt missing" '("None")' +check 1603 "Hashtbl mem" 'true' +check 1604 "Hashtbl replace" '2' +check 1605 "Hashtbl find_opt found" '("Some" 5)' + 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 cef10f0d..961e5b05 100644 --- a/plans/ocaml-on-sx.md +++ b/plans/ocaml-on-sx.md @@ -248,6 +248,9 @@ SX CEK evaluator (both JS and OCaml hosts) `iter`, `fold`, `to_list`. _(Pending: join/to_result.)_ - [~] `Result`: `map`, `bind`, `is_ok`, `is_error`, `get_ok`, `get_error`, `map_error`, `to_option`. _(Pending: fold/join.)_ +- [~] `Hashtbl`: `create`, `add`, `find`, `find_opt`, `replace`, `mem`, + `length`. Backed by a one-element list cell holding a SX dict; + keys coerced to strings via `str` for polymorphic-key support. - [~] `String`: `length`, `get`, `sub`, `concat`, `uppercase_ascii`, `lowercase_ascii`, `starts_with`. _(Pending: split_on_char, trim, contains, ends_with, index_opt, replace_all.)_ @@ -362,6 +365,10 @@ the "mother tongue" closure: OCaml → SX → OCaml. This means: _Newest first._ +- 2026-05-08 Phase 6 — `Hashtbl` (+6 tests, 332 total). Backing store is + a one-element list cell holding a SX dict; keys are coerced to + strings via `str` so any value type can serve as a key. API: create, + add, replace, find, find_opt, mem, length. - 2026-05-08 Phase 5 — HM extensions for tuples and lists (+7 tests, 326 total). Tuple type `(hm-con "*" TYPES)`, list type `(hm-con "list" (TYPE))`. `ocaml-infer-tuple` threads substitution through