From 88c02c7c7377fbcb25689aa66672870bc92c0bf4 Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 8 May 2026 12:49:23 +0000 Subject: [PATCH] ocaml: phase 6 expanded stdlib (+15 tests, 319 total) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit List: concat/flatten, init, find/find_opt, partition, mapi/iteri, assoc/assoc_opt. Option: iter/fold/to_list. Result: get_ok/get_error/ map_error/to_option. Fixed skip-to-boundary! in parser to track let..in / begin..end / struct..end / for/while..done nesting via a depth counter — without this, nested-let inside a top-level decl body trips over the decl-boundary detector. Stdlib functions like List.init / mapi / iteri use begin..end to make their nested-let intent explicit. --- lib/ocaml/parser.sx | 51 ++++++++++++++++------- lib/ocaml/runtime.sx | 96 ++++++++++++++++++++++++++++++++++++++++++++ lib/ocaml/test.sh | 53 ++++++++++++++++++++++++ plans/ocaml-on-sx.md | 22 +++++++--- 4 files changed, 202 insertions(+), 20 deletions(-) diff --git a/lib/ocaml/parser.sx b/lib/ocaml/parser.sx index dd7f59a0..b85dba56 100644 --- a/lib/ocaml/parser.sx +++ b/lib/ocaml/parser.sx @@ -895,22 +895,45 @@ (fn () (let ((t (peek-tok))) (if (= t nil) (len src) (get t :pos))))) + ;; skip-to-boundary! advances `idx` to the next top-level decl + ;; boundary, tracking `let`/`begin`/`struct` etc. nesting so that + ;; an inner `let ... in ...` doesn't terminate a top-level decl + ;; body. Boundary tokens (when at depth 0): + ;; ;; let module open include and type exception + ;; Boundary at any depth: eof. (define skip-to-boundary! - (fn - () - (cond - ((>= idx tok-len) nil) - ((= (ocaml-tok-type (peek-tok)) "eof") nil) - ((at-op? ";;") nil) - ((at-kw? "let") nil) - ((at-kw? "module") nil) - ((at-kw? "open") nil) - ((at-kw? "include") nil) - ((at-kw? "and") nil) - ((at-kw? "type") nil) - ((at-kw? "exception") nil) - (else (begin (advance-tok!) (skip-to-boundary!)))))) + (fn () + (let ((depth 0)) + (begin + (define step + (fn () + (cond + ((>= idx tok-len) nil) + ((= (ocaml-tok-type (peek-tok)) "eof") nil) + ((and (= depth 0) (at-op? ";;")) nil) + ((and (= depth 0) (at-kw? "let")) nil) + ((and (= depth 0) (at-kw? "module")) nil) + ((and (= depth 0) (at-kw? "open")) nil) + ((and (= depth 0) (at-kw? "include")) nil) + ((and (= depth 0) (at-kw? "and")) nil) + ((and (= depth 0) (at-kw? "type")) nil) + ((and (= depth 0) (at-kw? "exception")) nil) + ;; Track nested blocks that have explicit closing + ;; tokens. let..in / begin..end / struct..end / + ;; sig..end / for..done / while..done. `if`/`match`/ + ;; `try` don't have hard close tokens so we don't + ;; track them — their bodies are bounded by the + ;; surrounding expression structure. + ((or (at-kw? "let") (at-kw? "begin") (at-kw? "struct") + (at-kw? "sig") (at-kw? "for") (at-kw? "while")) + (begin (set! depth (+ depth 1)) (advance-tok!) (step))) + ((or (at-kw? "in") (at-kw? "end") (at-kw? "done")) + (begin + (when (> depth 0) (set! depth (- depth 1))) + (advance-tok!) (step))) + (else (begin (advance-tok!) (step)))))) + (step))))) (define parse-decl-let (fn () diff --git a/lib/ocaml/runtime.sx b/lib/ocaml/runtime.sx index 52ea6cf1..701dfb2c 100644 --- a/lib/ocaml/runtime.sx +++ b/lib/ocaml/runtime.sx @@ -82,6 +82,67 @@ match lst with | [] -> failwith \"List.nth: out of range\" | h :: t -> if n = 0 then h else nth t (n - 1) + + let rec concat lst = + match lst with + | [] -> [] + | h :: t -> append h (concat t) + + let flatten = concat + + let rec init n f = + if n = 0 then [] else + begin + let rec build i = + if i = n then [] else f i :: build (i + 1) + in build 0 + end + + let rec find_opt p lst = + match lst with + | [] -> None + | h :: t -> if p h then Some h else find_opt p t + + let rec find p lst = + match find_opt p lst with + | None -> failwith \"List.find: not found\" + | Some x -> x + + let rec partition p lst = + match lst with + | [] -> ([], []) + | h :: t -> + (match partition p t with + | (yes, no) -> + if p h then (h :: yes, no) else (yes, h :: no)) + + let rec mapi f lst = + begin + let rec go i xs = + match xs with + | [] -> [] + | h :: t -> f i h :: go (i + 1) t + in go 0 lst + end + + let rec iteri f lst = + begin + let rec go i xs = + match xs with + | [] -> () + | h :: t -> f i h; go (i + 1) t + in go 0 lst + end + + let rec assoc k lst = + match lst with + | [] -> failwith \"List.assoc: not found\" + | (k2, v) :: t -> if k = k2 then v else assoc k t + + let rec assoc_opt k lst = + match lst with + | [] -> None + | (k2, v) :: t -> if k = k2 then Some v else assoc_opt k t end ;; module Option = struct @@ -114,6 +175,21 @@ match o with | None -> false | Some _ -> true + + let iter f o = + match o with + | None -> () + | Some x -> f x + + let fold none_v f o = + match o with + | None -> none_v + | Some x -> f x + + let to_list o = + match o with + | None -> [] + | Some x -> [x] end ;; module Result = struct @@ -136,6 +212,26 @@ match r with | Ok _ -> false | Error _ -> true + + let get_ok r = + match r with + | Ok x -> x + | Error _ -> failwith \"Result.get_ok: Error\" + + let get_error r = + match r with + | Ok _ -> failwith \"Result.get_error: Ok\" + | Error e -> e + + let map_error f r = + match r with + | Ok x -> Ok x + | Error e -> Error (f e) + + let to_option r = + match r with + | Ok x -> Some x + | Error _ -> None end ;; module String = struct diff --git a/lib/ocaml/test.sh b/lib/ocaml/test.sh index 29ba8e52..e887b0c2 100755 --- a/lib/ocaml/test.sh +++ b/lib/ocaml/test.sh @@ -764,6 +764,40 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 1323) (eval "(ocaml-run-program \"exception E of string ;; try raise (E \\\"oops\\\") with | E s -> s\")") +;; ── Phase 6 expanded stdlib (List/Option/Result extensions) ─── +(epoch 1400) +(eval "(ocaml-run \"List.concat [[1;2];[3];[4;5]]\")") +(epoch 1401) +(eval "(ocaml-run \"List.init 5 (fun i -> i * 10)\")") +(epoch 1402) +(eval "(ocaml-run \"List.find_opt (fun x -> x > 2) [1;2;3;4]\")") +(epoch 1403) +(eval "(ocaml-run \"List.find_opt (fun x -> x > 99) [1;2;3]\")") +(epoch 1404) +(eval "(ocaml-run \"List.mapi (fun i x -> i + x) [10;20;30]\")") +(epoch 1405) +(eval "(ocaml-run \"List.partition (fun x -> x > 2) [1;2;3;4]\")") +(epoch 1406) +(eval "(ocaml-run \"List.assoc 2 [(1, \\\"a\\\"); (2, \\\"b\\\"); (3, \\\"c\\\")]\")") +(epoch 1407) +(eval "(ocaml-run \"List.assoc_opt 99 [(1, \\\"a\\\")]\")") + +(epoch 1410) +(eval "(ocaml-run \"Option.fold 0 (fun x -> x * 10) (Some 7)\")") +(epoch 1411) +(eval "(ocaml-run \"Option.fold 0 (fun x -> x * 10) None\")") +(epoch 1412) +(eval "(ocaml-run \"Option.to_list (Some 7)\")") +(epoch 1413) +(eval "(ocaml-run \"Option.to_list None\")") + +(epoch 1420) +(eval "(ocaml-run \"Result.get_ok (Ok 42)\")") +(epoch 1421) +(eval "(ocaml-run \"Result.to_option (Ok 1)\")") +(epoch 1422) +(eval "(ocaml-run \"Result.map_error (fun e -> e + 1) (Error 5)\")") + EPOCHS OUTPUT=$(timeout 180 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -1210,6 +1244,25 @@ check 1321 "exception arg" '("exception-def" "MyExn"' check 1322 "raise+catch with arg" '5' check 1323 "raise+catch string arg" '"oops"' +# ── Phase 6 expanded stdlib ───────────────────────────────────── +check 1400 "List.concat" '(1 2 3 4 5)' +check 1401 "List.init" '(0 10 20 30 40)' +check 1402 "List.find_opt found" '("Some" 3)' +check 1403 "List.find_opt missing" '("None")' +check 1404 "List.mapi" '(10 21 32)' +check 1405 "List.partition" '("tuple" (3 4) (1 2))' +check 1406 "List.assoc" '"b"' +check 1407 "List.assoc_opt missing" '("None")' + +check 1410 "Option.fold Some" '70' +check 1411 "Option.fold None" '0' +check 1412 "Option.to_list Some" '(7)' +check 1413 "Option.to_list None" '()' + +check 1420 "Result.get_ok" '42' +check 1421 "Result.to_option Ok" '("Some" 1)' +check 1422 "Result.map_error" '("Error" 6)' + 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 417d34f7..75ad9e6a 100644 --- a/plans/ocaml-on-sx.md +++ b/plans/ocaml-on-sx.md @@ -240,12 +240,13 @@ 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`. _(Pending: concat/flatten, iteri/mapi, find/find_opt, - assoc/assq, sort, init, combine, split, partition.)_ -- [~] `Option`: `map`, `bind`, `value`, `get`, `is_none`, `is_some`. - _(Pending: fold/join/iter/to_list/to_result.)_ -- [~] `Result`: `map`, `bind`, `is_ok`, `is_error`. _(Pending: - fold/get_ok/get_error/map_error/to_option.)_ + `rev_append`, `concat`/`flatten`, `init`, `iteri`, `mapi`, `find`, + `find_opt`, `assoc`, `assoc_opt`, `partition`. _(Pending: + sort/stable_sort, 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`, + `get_error`, `map_error`, `to_option`. _(Pending: fold/join.)_ - [~] `String`: `length`, `get`, `sub`, `concat`, `uppercase_ascii`, `lowercase_ascii`, `starts_with`. _(Pending: split_on_char, trim, contains, ends_with, index_opt, replace_all.)_ @@ -360,6 +361,15 @@ the "mother tongue" closure: OCaml → SX → OCaml. This means: _Newest first._ +- 2026-05-08 Phase 6 — expanded stdlib slice (+15 tests, 319 total). + List: concat/flatten, init, find/find_opt, partition, mapi/iteri, + assoc/assoc_opt. Option: iter, fold, to_list. Result: get_ok, + get_error, map_error, to_option. Also fixed parser's + skip-to-boundary! to track `let..in` / `begin..end` / `struct..end` + / `for/while..done` nesting via a depth counter so nested let + expressions inside top-level decl bodies don't trip over the + decl-boundary detector. Stdlib functions like `init` use `begin..end` + to make nested-let intent explicit. - 2026-05-08 Phase 3 — `exception` declarations (+4 tests, 304 total). `exception NAME [of TYPE]` parses to `(:exception-def NAME [ARG-SRC])`. Runtime is a no-op: exception values are just tagged ctor values, so