ocaml: phase 6 stdlib slice (List/Option/Result, +23 tests, 248 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 45s

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.
This commit is contained in:
2026-05-08 08:49:44 +00:00
parent 5603ecc3a6
commit 19f1cad11d
4 changed files with 288 additions and 11 deletions

View File

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

173
lib/ocaml/runtime.sx Normal file
View File

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

View File

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

View File

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