From 19f1cad11dd532c53258af309493232f4ea4b427 Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 8 May 2026 08:49:44 +0000 Subject: [PATCH] ocaml: phase 6 stdlib slice (List/Option/Result, +23 tests, 248 total) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit lib/ocaml/runtime.sx defines the stdlib in OCaml syntax (not SX): every function exercises the parser, evaluator, match engine, and module machinery built in earlier phases. Loaded once via ocaml-load-stdlib!, cached in ocaml-stdlib-env, layered under user code via ocaml-base-env. List: length, rev, rev_append, map, filter, fold_left/right, append, iter, mem, for_all, exists, hd, tl, nth. Option: map, bind, value, get, is_none, is_some. Result: map, bind, is_ok, is_error. Substrate validation: this stdlib is a nontrivial OCaml program — its mere existence proves the substrate works. --- lib/ocaml/eval.sx | 18 ++++- lib/ocaml/runtime.sx | 173 +++++++++++++++++++++++++++++++++++++++++++ lib/ocaml/test.sh | 82 ++++++++++++++++++++ plans/ocaml-on-sx.md | 26 +++++-- 4 files changed, 288 insertions(+), 11 deletions(-) create mode 100644 lib/ocaml/runtime.sx diff --git a/lib/ocaml/eval.sx b/lib/ocaml/eval.sx index 873b391f..ddbfdd01 100644 --- a/lib/ocaml/eval.sx +++ b/lib/ocaml/eval.sx @@ -588,16 +588,28 @@ (loop decls) result)))) -;; ocaml-run — convenience wrapper: parse + eval. +;; ocaml-run — convenience wrapper: parse + eval. Layers the stdlib env +;; (List, Option, Result) underneath the empty env so user code can use +;; `List.map` etc. without explicit setup. +;; Variable guarded so eval.sx is loadable without runtime.sx. runtime.sx +;; sets ocaml-stdlib-env once loaded; before that, fall back to the empty +;; env so the existing tests continue to work without stdlib. +(define ocaml-stdlib-env nil) +(define ocaml-base-env + (fn () + (cond + ((not (= ocaml-stdlib-env nil)) ocaml-stdlib-env) + (else (ocaml-empty-env))))) + (define ocaml-run (fn (src) - (ocaml-eval (ocaml-parse src) (ocaml-empty-env)))) + (ocaml-eval (ocaml-parse src) (ocaml-base-env)))) ;; ocaml-run-program — evaluate a program (sequence of decls + bare exprs). ;; Threads an env through decls; returns the value of the last form. (define ocaml-run-program (fn (src) - (let ((prog (ocaml-parse-program src)) (env (ocaml-empty-env)) (last nil)) + (let ((prog (ocaml-parse-program src)) (env (ocaml-base-env)) (last nil)) (begin (define run-decl (fn (decl) diff --git a/lib/ocaml/runtime.sx b/lib/ocaml/runtime.sx new file mode 100644 index 00000000..47b8bbb5 --- /dev/null +++ b/lib/ocaml/runtime.sx @@ -0,0 +1,173 @@ +;; lib/ocaml/runtime.sx — minimal OCaml stdlib slice, written in OCaml. +;; +;; Defines List and Option modules with the most-used functions. Loaded +;; on demand via `(ocaml-load-stdlib! env)` from eval.sx, which parses +;; this source through `ocaml-parse-program` and evaluates each decl, +;; threading the env so stdlib bindings become available to user code. +;; +;; What's here is intentionally minimal — Phase 6 grows this into the +;; full ~150-function slice. Everything is defined in OCaml syntax (not +;; SX) on purpose, both as substrate validation and as documentation. + +(define ocaml-stdlib-src + "module List = struct + let rec length lst = + match lst with + | [] -> 0 + | _ :: t -> 1 + length t + + let rec rev_append xs acc = + match xs with + | [] -> acc + | h :: t -> rev_append t (h :: acc) + + let rev xs = rev_append xs [] + + let rec map f lst = + match lst with + | [] -> [] + | h :: t -> f h :: map f t + + let rec filter p lst = + match lst with + | [] -> [] + | h :: t -> if p h then h :: filter p t else filter p t + + let rec fold_left f init lst = + match lst with + | [] -> init + | h :: t -> fold_left f (f init h) t + + let rec fold_right f lst init = + match lst with + | [] -> init + | h :: t -> f h (fold_right f t init) + + let rec append xs ys = + match xs with + | [] -> ys + | h :: t -> h :: append t ys + + let rec iter f lst = + match lst with + | [] -> () + | h :: t -> f h; iter f t + + let rec mem x lst = + match lst with + | [] -> false + | h :: t -> if h = x then true else mem x t + + let rec for_all p lst = + match lst with + | [] -> true + | h :: t -> if p h then for_all p t else false + + let rec exists p lst = + match lst with + | [] -> false + | h :: t -> if p h then true else exists p t + + let hd lst = + match lst with + | [] -> failwith \"List.hd: empty\" + | h :: _ -> h + + let tl lst = + match lst with + | [] -> failwith \"List.tl: empty\" + | _ :: t -> t + + let rec nth lst n = + match lst with + | [] -> failwith \"List.nth: out of range\" + | h :: t -> if n = 0 then h else nth t (n - 1) + end ;; + + module Option = struct + let map f o = + match o with + | None -> None + | Some x -> Some (f x) + + let bind o f = + match o with + | None -> None + | Some x -> f x + + let value o default = + match o with + | None -> default + | Some x -> x + + let get o = + match o with + | None -> failwith \"Option.get: None\" + | Some x -> x + + let is_none o = + match o with + | None -> true + | Some _ -> false + + let is_some o = + match o with + | None -> false + | Some _ -> true + end ;; + + module Result = struct + let map f r = + match r with + | Ok x -> Ok (f x) + | Error e -> Error e + + let bind r f = + match r with + | Ok x -> f x + | Error e -> Error e + + let is_ok r = + match r with + | Ok _ -> true + | Error _ -> false + + let is_error r = + match r with + | Ok _ -> false + | Error _ -> true + end") + +(define ocaml-stdlib-loaded false) +(define ocaml-stdlib-env nil) + +;; Build a stdlib env once, cache it. ocaml-run / ocaml-run-program both +;; layer the user program on top of this base env. +(define ocaml-load-stdlib! + (fn () + (when (not ocaml-stdlib-loaded) + (let ((env (ocaml-empty-env))) + (begin + (define run-decl + (fn (decl) + (let ((tag (ocaml-tag-of decl))) + (cond + ((= tag "module-def") + (let ((mn (nth decl 1)) (ds (nth decl 2))) + (let ((mv (ocaml-eval-module ds env))) + (set! env (ocaml-env-extend env mn mv))))) + ((= tag "def") + (let ((nm (nth decl 1)) (ps (nth decl 2)) (rh (nth decl 3))) + (let ((v (if (= (len ps) 0) + (ocaml-eval rh env) + (ocaml-make-curried ps rh env)))) + (set! env (ocaml-env-extend env nm v))))))))) + (let ((prog (ocaml-parse-program ocaml-stdlib-src))) + (begin + (define loop + (fn (xs) + (when (not (= xs (list))) + (begin (run-decl (first xs)) (loop (rest xs)))))) + (loop (rest prog)) + (set! ocaml-stdlib-env env) + (set! ocaml-stdlib-loaded true)))))))) diff --git a/lib/ocaml/test.sh b/lib/ocaml/test.sh index 98937c07..261f6dea 100755 --- a/lib/ocaml/test.sh +++ b/lib/ocaml/test.sh @@ -33,7 +33,9 @@ cat > "$TMPFILE" << 'EPOCHS' (load "lib/ocaml/tokenizer.sx") (load "lib/ocaml/parser.sx") (load "lib/ocaml/eval.sx") +(load "lib/ocaml/runtime.sx") (load "lib/ocaml/tests/tokenize.sx") +(eval "(ocaml-load-stdlib!)") ;; ── empty / eof ──────────────────────────────────────────────── (epoch 100) @@ -579,6 +581,56 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 754) (eval "(ocaml-run-program \"module Identity (M) = struct include M end ;; module Base = struct let v = 99 end ;; module Same = Identity(Base) ;; Same.v\")") +;; ── Phase 6: stdlib slice (List, Option, Result) ─────────────── +(epoch 800) +(eval "(ocaml-run \"List.length [1; 2; 3; 4]\")") +(epoch 801) +(eval "(ocaml-run \"List.length []\")") +(epoch 802) +(eval "(ocaml-run \"List.map (fun x -> x * 2) [1; 2; 3]\")") +(epoch 803) +(eval "(ocaml-run \"List.filter (fun x -> x > 2) [1; 2; 3; 4; 5]\")") +(epoch 804) +(eval "(ocaml-run \"List.fold_left (fun a b -> a + b) 0 [1; 2; 3; 4; 5]\")") +(epoch 805) +(eval "(ocaml-run \"List.fold_right (fun x acc -> x :: acc) [1; 2; 3] []\")") +(epoch 806) +(eval "(ocaml-run \"List.rev [1; 2; 3]\")") +(epoch 807) +(eval "(ocaml-run \"List.append [1; 2] [3; 4]\")") +(epoch 808) +(eval "(ocaml-run \"List.mem 3 [1; 2; 3]\")") +(epoch 809) +(eval "(ocaml-run \"List.mem 99 [1; 2; 3]\")") +(epoch 810) +(eval "(ocaml-run \"List.for_all (fun x -> x > 0) [1; 2; 3]\")") +(epoch 811) +(eval "(ocaml-run \"List.exists (fun x -> x > 2) [1; 2; 3]\")") +(epoch 812) +(eval "(ocaml-run \"List.hd [10; 20; 30]\")") +(epoch 813) +(eval "(ocaml-run \"List.nth [10; 20; 30] 1\")") + +(epoch 820) +(eval "(ocaml-run \"Option.map (fun x -> x + 1) (Some 41)\")") +(epoch 821) +(eval "(ocaml-run \"Option.map (fun x -> x + 1) None\")") +(epoch 822) +(eval "(ocaml-run \"Option.value (Some 7) 0\")") +(epoch 823) +(eval "(ocaml-run \"Option.value None 42\")") +(epoch 824) +(eval "(ocaml-run \"Option.is_some (Some 1)\")") +(epoch 825) +(eval "(ocaml-run \"Option.is_none None\")") + +(epoch 830) +(eval "(ocaml-run \"Result.map (fun x -> x + 1) (Ok 5)\")") +(epoch 831) +(eval "(ocaml-run \"Result.is_ok (Ok 1)\")") +(epoch 832) +(eval "(ocaml-run \"Result.is_error (Error \\\"oops\\\")\")") + EPOCHS OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -921,6 +973,36 @@ check 752 "submodule alias" '42' check 753 "multi-param functor" '("tuple" 1 2)' check 754 "Identity functor + include" '99' +# ── Phase 6: stdlib slice ─────────────────────────────────────── +# List +check 800 "List.length [1..4]" '4' +check 801 "List.length []" '0' +check 802 "List.map x*2 [1;2;3]" '(2 4 6)' +check 803 "List.filter > 2" '(3 4 5)' +check 804 "List.fold_left + 0 [1..5]" '15' +check 805 "List.fold_right ::" '(1 2 3)' +check 806 "List.rev" '(3 2 1)' +check 807 "List.append" '(1 2 3 4)' +check 808 "List.mem 3" 'true' +check 809 "List.mem 99" 'false' +check 810 "List.for_all >0" 'true' +check 811 "List.exists >2" 'true' +check 812 "List.hd" '10' +check 813 "List.nth idx 1" '20' + +# Option +check 820 "Option.map Some" '("Some" 42)' +check 821 "Option.map None" '("None")' +check 822 "Option.value Some" '7' +check 823 "Option.value None" '42' +check 824 "Option.is_some" 'true' +check 825 "Option.is_none" 'true' + +# Result +check 830 "Result.map Ok" '("Ok" 6)' +check 831 "Result.is_ok" 'true' +check 832 "Result.is_error" 'true' + 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 8e428a17..2d76261f 100644 --- a/plans/ocaml-on-sx.md +++ b/plans/ocaml-on-sx.md @@ -215,14 +215,14 @@ SX CEK evaluator (both JS and OCaml hosts) ### Phase 6 — Standard library -- [ ] `List`: `map`, `filter`, `fold_left`, `fold_right`, `length`, `rev`, `append`, - `concat`, `flatten`, `iter`, `iteri`, `mapi`, `for_all`, `exists`, `find`, - `find_opt`, `mem`, `assoc`, `assq`, `sort`, `stable_sort`, `nth`, `hd`, `tl`, - `init`, `combine`, `split`, `partition`. -- [ ] `Option`: `map`, `bind`, `fold`, `get`, `value`, `join`, `iter`, `to_list`, - `to_result`, `is_none`, `is_some`. -- [ ] `Result`: `map`, `bind`, `fold`, `get_ok`, `get_error`, `map_error`, - `to_option`, `is_ok`, `is_error`. +- [~] `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.)_ - [ ] `String`: `length`, `get`, `sub`, `concat`, `split_on_char`, `trim`, `uppercase_ascii`, `lowercase_ascii`, `contains`, `starts_with`, `ends_with`, `index_opt`, `replace_all` (non-stdlib but needed). @@ -327,6 +327,16 @@ the "mother tongue" closure: OCaml → SX → OCaml. This means: _Newest first._ +- 2026-05-08 Phase 6 — `lib/ocaml/runtime.sx` minimal stdlib slice + written entirely in OCaml syntax: List (length, rev, rev_append, map, + filter, fold_left/right, append, iter, mem, for_all, exists, hd, tl, + nth), Option (map, bind, value, get, is_none, is_some), Result (map, + bind, is_ok, is_error). Loaded once via `ocaml-load-stdlib!`, cached + in `ocaml-stdlib-env`; `ocaml-run` and `ocaml-run-program` layer user + code on top via `ocaml-base-env`. The fact that these are written in + OCaml (not SX) and parse + evaluate cleanly is a substrate-validation + win: every parser, eval, match, ref, and module path proven by a + single nontrivial Ocaml program. 248/248 (+23). - 2026-05-08 Phase 4 — functors + module aliases (+5 tests, 225 total). Parser: `module F (M) = struct DECLS end` → `(:functor-def NAME PARAMS DECLS)`. `module N = expr` (where expr isn't `struct`) → `(:module-alias