diff --git a/lib/ocaml/eval.sx b/lib/ocaml/eval.sx index 7835dd48..74032419 100644 --- a/lib/ocaml/eval.sx +++ b/lib/ocaml/eval.sx @@ -67,6 +67,14 @@ (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))) + ;; Polymorphic compare — returns negative / 0 / positive like + ;; OCaml's Stdlib.compare. Defers to host SX `<` and `>`. + (list "compare" + (fn (a) (fn (b) + (cond + ((< a b) -1) + ((> a b) 1) + (else 0))))) ;; 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). diff --git a/lib/ocaml/runtime.sx b/lib/ocaml/runtime.sx index 8ad9cdc4..28e101a7 100644 --- a/lib/ocaml/runtime.sx +++ b/lib/ocaml/runtime.sx @@ -143,6 +143,20 @@ match lst with | [] -> None | (k2, v) :: t -> if k = k2 then Some v else assoc_opt k t + + let rec sort cmp xs = + begin + let rec insert x ys = + match ys with + | [] -> [x] + | h :: t -> if cmp x h <= 0 then x :: ys else h :: insert x t + in + match xs with + | [] -> [] + | h :: t -> insert h (sort cmp t) + end + + let stable_sort = sort end ;; module Option = struct diff --git a/lib/ocaml/test.sh b/lib/ocaml/test.sh index 31587ac2..03076b0b 100755 --- a/lib/ocaml/test.sh +++ b/lib/ocaml/test.sh @@ -828,6 +828,22 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 1605) (eval "(ocaml-run-program \"let t = Hashtbl.create 10;; Hashtbl.add t \\\"k\\\" 5;; Hashtbl.find_opt t \\\"k\\\"\")") +;; ── List.sort + compare ──────────────────────────────────────── +(epoch 1700) +(eval "(ocaml-run \"compare 1 2\")") +(epoch 1701) +(eval "(ocaml-run \"compare 5 5\")") +(epoch 1702) +(eval "(ocaml-run \"compare 9 1\")") +(epoch 1703) +(eval "(ocaml-run \"List.sort compare [3; 1; 4; 1; 5; 9; 2; 6]\")") +(epoch 1704) +(eval "(ocaml-run \"List.sort (fun a b -> b - a) [3; 1; 4]\")") +(epoch 1705) +(eval "(ocaml-run \"List.sort compare []\")") +(epoch 1706) +(eval "(ocaml-run \"List.sort compare [\\\"b\\\"; \\\"a\\\"; \\\"c\\\"]\")") + EPOCHS OUTPUT=$(timeout 180 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -1310,6 +1326,15 @@ check 1603 "Hashtbl mem" 'true' check 1604 "Hashtbl replace" '2' check 1605 "Hashtbl find_opt found" '("Some" 5)' +# ── List.sort + compare ───────────────────────────────────────── +check 1700 "compare 1<2" '-1' +check 1701 "compare 5=5" '0' +check 1702 "compare 9>1" '1' +check 1703 "List.sort ascending" '(1 1 2 3 4 5 6 9)' +check 1704 "List.sort descending" '(4 3 1)' +check 1705 "List.sort empty" '()' +check 1706 "List.sort strings" '("a" "b" "c")' + 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 961e5b05..5ef6223c 100644 --- a/plans/ocaml-on-sx.md +++ b/plans/ocaml-on-sx.md @@ -242,8 +242,8 @@ SX CEK evaluator (both JS and OCaml hosts) - [~] `List`: `map`, `filter`, `fold_left`, `fold_right`, `length`, `rev`, `append`, `iter`, `for_all`, `exists`, `mem`, `nth`, `hd`, `tl`, `rev_append`, `concat`/`flatten`, `init`, `iteri`, `mapi`, `find`, - `find_opt`, `assoc`, `assoc_opt`, `partition`. _(Pending: - sort/stable_sort, combine, split.)_ + `find_opt`, `assoc`, `assoc_opt`, `partition`, `sort`, + `stable_sort` (insertion sort, O(n²)). _(Pending: combine, split.)_ - [~] `Option`: `map`, `bind`, `value`, `get`, `is_none`, `is_some`, `iter`, `fold`, `to_list`. _(Pending: join/to_result.)_ - [~] `Result`: `map`, `bind`, `is_ok`, `is_error`, `get_ok`, @@ -365,6 +365,11 @@ the "mother tongue" closure: OCaml → SX → OCaml. This means: _Newest first._ +- 2026-05-08 Phase 6 — `List.sort` + polymorphic `compare` (+7 tests, + 339 total). `compare` is a host primitive that returns -1/0/1 like + Stdlib.compare, defers to host SX `<`/`>`. `List.sort` is implemented + in OCaml as insertion sort: O(n²) but correct, and passes all tests + including descending custom comparator and string sort. - 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,