ocaml: phase 6 expanded stdlib (+15 tests, 319 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 54s

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.
This commit is contained in:
2026-05-08 12:49:23 +00:00
parent bc557a5ad2
commit 88c02c7c73
4 changed files with 202 additions and 20 deletions

View File

@@ -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 ()

View File

@@ -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

View File

@@ -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"

View File

@@ -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