diff --git a/lib/ocaml/eval.sx b/lib/ocaml/eval.sx index 3bb16651..ab67a841 100644 --- a/lib/ocaml/eval.sx +++ b/lib/ocaml/eval.sx @@ -47,7 +47,26 @@ ;; can pattern-match them. (list "raise" (fn (e) (raise e))) (list "failwith" (fn (msg) (raise (list "Failure" msg)))) - (list "invalid_arg" (fn (msg) (raise (list "Invalid_argument" msg))))))) + (list "invalid_arg" (fn (msg) (raise (list "Invalid_argument" msg))) + ) + ;; Host primitives exposed for the OCaml stdlib (lib/ocaml/runtime.sx). + ;; Underscore-prefixed to avoid clashing with user names. + (list "_string_length" (fn (s) (len s))) + (list "_string_get" (fn (s) (fn (i) (nth s i)))) + (list "_string_sub" (fn (s) (fn (i) (fn (n) (slice s i (+ i n)))))) + (list "_string_concat" (fn (sep) (fn (xs) (join sep xs)))) + (list "_string_upper" (fn (s) (upper s))) + (list "_string_lower" (fn (s) (lower s))) + (list "_string_starts_with" (fn (p) (fn (s) (starts-with? s p)))) + (list "_int_of_string" (fn (s) (parse-number s))) + (list "_string_of_int" (fn (i) (str i))) + (list "_string_of_float" (fn (f) (str f))) + (list "_char_code" (fn (c) (char-code c))) + (list "_char_chr" (fn (n) (char-from-code n))) + ;; Print: prints to host stdout via println. + (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)))))) (define ocaml-env-lookup (fn (env name) diff --git a/lib/ocaml/runtime.sx b/lib/ocaml/runtime.sx index 47b8bbb5..52ea6cf1 100644 --- a/lib/ocaml/runtime.sx +++ b/lib/ocaml/runtime.sx @@ -136,6 +136,40 @@ match r with | Ok _ -> false | Error _ -> true + end ;; + + module String = struct + let length s = _string_length s + let get s i = _string_get s i + let sub s i n = _string_sub s i n + let concat sep xs = _string_concat sep xs + let uppercase_ascii s = _string_upper s + let lowercase_ascii s = _string_lower s + let starts_with prefix s = _string_starts_with prefix s + end ;; + + module Char = struct + let code c = _char_code c + let chr n = _char_chr n + let lowercase_ascii c = _string_lower c + let uppercase_ascii c = _string_upper c + end ;; + + module Int = struct + let to_string i = _string_of_int i + let of_string s = _int_of_string s + let abs n = if n < 0 then 0 - n else n + let max a b = if a > b then a else b + let min a b = if a < b then a else b + end ;; + + module Float = struct + let to_string f = _string_of_float f + end ;; + + module Printf = struct + let sprintf fmt = fmt + let printf fmt = print_string fmt end") (define ocaml-stdlib-loaded false) diff --git a/lib/ocaml/test.sh b/lib/ocaml/test.sh index 0b8da7cf..5b87ac87 100755 --- a/lib/ocaml/test.sh +++ b/lib/ocaml/test.sh @@ -672,6 +672,36 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 913) (eval "(ocaml-type-of \"not true\")") +;; ── Phase 6 expanded: String / Char / Int / Float modules ───── +(epoch 950) +(eval "(ocaml-run \"String.length \\\"hello\\\"\")") +(epoch 951) +(eval "(ocaml-run \"String.uppercase_ascii \\\"hi\\\"\")") +(epoch 952) +(eval "(ocaml-run \"String.lowercase_ascii \\\"HI\\\"\")") +(epoch 953) +(eval "(ocaml-run \"String.sub \\\"hello\\\" 1 3\")") +(epoch 954) +(eval "(ocaml-run \"String.starts_with \\\"he\\\" \\\"hello\\\"\")") +(epoch 955) +(eval "(ocaml-run \"String.concat \\\",\\\" [\\\"a\\\"; \\\"b\\\"; \\\"c\\\"]\")") + +(epoch 960) +(eval "(ocaml-run \"Char.code \\\"A\\\"\")") +(epoch 961) +(eval "(ocaml-run \"Char.chr 65\")") + +(epoch 970) +(eval "(ocaml-run \"Int.to_string 42\")") +(epoch 971) +(eval "(ocaml-run \"Int.of_string \\\"123\\\"\")") +(epoch 972) +(eval "(ocaml-run \"Int.abs (-5)\")") +(epoch 973) +(eval "(ocaml-run \"Int.max 7 3\")") +(epoch 974) +(eval "(ocaml-run \"Int.min 7 3\")") + EPOCHS OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -1065,6 +1095,23 @@ check 911 "type twice" ' -> ' check 912 "type bool branch" '"Bool -> Int"' check 913 "type not true" '"Bool"' +# ── Phase 6 String / Char / Int ───────────────────────────────── +check 950 "String.length" '5' +check 951 "String.uppercase_ascii" '"HI"' +check 952 "String.lowercase_ascii" '"hi"' +check 953 "String.sub" '"ell"' +check 954 "String.starts_with" 'true' +check 955 "String.concat" '"a,b,c"' + +check 960 "Char.code A" '65' +check 961 "Char.chr 65" '"A"' + +check 970 "Int.to_string" '"42"' +check 971 "Int.of_string" '123' +check 972 "Int.abs -5" '5' +check 973 "Int.max" '7' +check 974 "Int.min" '3' + 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 06e288d5..a7b756d1 100644 --- a/plans/ocaml-on-sx.md +++ b/plans/ocaml-on-sx.md @@ -229,6 +229,16 @@ SX CEK evaluator (both JS and OCaml hosts) _(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`, `uppercase_ascii`, + `lowercase_ascii`, `starts_with`. _(Pending: split_on_char, trim, + contains, ends_with, index_opt, replace_all.)_ +- [~] `Char`: `code`, `chr`, `lowercase_ascii`, `uppercase_ascii`. + _(Pending: escaped.)_ +- [~] `Int`: `to_string`, `of_string`, `abs`, `max`, `min`. + _(Pending: arithmetic helpers, min_int/max_int.)_ +- [~] `Float`: `to_string`. _(Pending: of_string, arithmetic helpers.)_ +- [~] `Printf`: stub `sprintf`/`printf`. _(Real format-string + interpretation pending.)_ - [ ] `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). @@ -333,6 +343,14 @@ the "mother tongue" closure: OCaml → SX → OCaml. This means: _Newest first._ +- 2026-05-08 Phase 6 — extended stdlib slice (+13 tests, 278 total). + Host primitives exposed via `_string_*`, `_char_*`, `_int_*`, + `_string_of_*` underscore-prefixed builtins so the OCaml-side + `lib/ocaml/runtime.sx` modules can wrap them: String (length, get, + sub, concat, uppercase_ascii, lowercase_ascii, starts_with), Char + (code, chr, lowercase_ascii, uppercase_ascii), Int (to_string, + of_string, abs, max, min), Float.to_string, Printf stubs. Also added + `print_string`/`print_endline`/`print_int` builtins. - 2026-05-08 Phase 5 — Hindley-Milner type inference, paired-sequencing consumer of `lib/guest/hm.sx` (algebra) and `lib/guest/match.sx` (unify). `lib/ocaml/infer.sx` ships Algorithm W rules for OCaml AST: