From 0234ae329ede164c89d1756f0a734eb444ebdb32 Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 9 May 2026 00:13:11 +0000 Subject: [PATCH] ocaml: phase 5.1 caesar.ml baseline (ROT13 + s.[i] + Char ops) Side-quests required to land caesar.ml: 1. Top-level 'let r = expr in body' is now an expression decl, not a broken decl-let. ocaml-parse-program's dispatch now checks has-matching-in? at every top-level let; if matched, slices via skip-let-rhs-boundary (which already opens depth on a leading let with matching in) and ocaml-parse on the slice, wrapping as :expr. 2. runtime.sx: added String.make / String.init / String.map. Used by caesar.ml's encode = String.init n (fun i -> shift_char s.[i] k). 3. baseline run.sh per-program timeout 240->480s (system load on the shared host frequently exceeds 240s for large baselines). caesar.ml exercises: * the new top-level let-in expression dispatch * s.[i] string indexing * Char.code / Char.chr round-trip math * String.init with a closure that captures k Test value: Char.code r.[0] + Char.code r.[4] after ROT13(ROT13('hello')) = 104 + 111 = 215. --- lib/ocaml/baseline/caesar.ml | 14 ++++++++++++++ lib/ocaml/baseline/expected.json | 1 + lib/ocaml/baseline/run.sh | 2 +- lib/ocaml/parser.sx | 17 ++++++++++++++++- lib/ocaml/runtime.sx | 14 ++++++++++++++ plans/ocaml-on-sx.md | 11 +++++++++++ 6 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 lib/ocaml/baseline/caesar.ml diff --git a/lib/ocaml/baseline/caesar.ml b/lib/ocaml/baseline/caesar.ml new file mode 100644 index 00000000..356014d2 --- /dev/null +++ b/lib/ocaml/baseline/caesar.ml @@ -0,0 +1,14 @@ +let shift_char c k = + let n = Char.code c in + if n >= 97 && n <= 122 then + Char.chr (((n - 97 + k) mod 26 + 26) mod 26 + 97) + else c + +let encode s k = + String.init (String.length s) (fun i -> shift_char s.[i] k) +;; + +(* ROT13 round-trip: encode (encode "hello" 13) 13 = "hello". + Sum the codes of two chars to give a deterministic integer check. *) +let r = encode (encode "hello" 13) 13 in +Char.code r.[0] + Char.code r.[4] diff --git a/lib/ocaml/baseline/expected.json b/lib/ocaml/baseline/expected.json index 86ceac8d..63f273b1 100644 --- a/lib/ocaml/baseline/expected.json +++ b/lib/ocaml/baseline/expected.json @@ -2,6 +2,7 @@ "anagrams.ml": 3, "bfs.ml": 6, "btree.ml": 39, + "caesar.ml": 215, "calc.ml": 13, "closures.ml": 315, "exception_handle.ml": 4, diff --git a/lib/ocaml/baseline/run.sh b/lib/ocaml/baseline/run.sh index c26fbdb0..4d1143d4 100755 --- a/lib/ocaml/baseline/run.sh +++ b/lib/ocaml/baseline/run.sh @@ -36,7 +36,7 @@ for f in lib/ocaml/baseline/*.ml; do (eval "(ocaml-run-program (file-read \"$f\"))") EOF - output=$(timeout 240 "$SX_SERVER" < "$TMP" 2>/dev/null) + output=$(timeout 480 "$SX_SERVER" < "$TMP" 2>/dev/null) rm -f "$TMP" result=$(echo "$output" | awk ' diff --git a/lib/ocaml/parser.sx b/lib/ocaml/parser.sx index 6b0f4abd..23f5f13e 100644 --- a/lib/ocaml/parser.sx +++ b/lib/ocaml/parser.sx @@ -1603,7 +1603,22 @@ (cond ((= (ocaml-tok-type (peek-tok)) "eof") nil) ((at-kw? "let") - (begin (append! decls (parse-decl-let)) (loop))) + (cond + ;; `let r = expr in body` at the top level is an + ;; expression-let, not a decl. Detect by scanning + ;; for a matching `in` at this depth — has-matching-in? + ;; walks the same boundaries as the decl scanner. + ((has-matching-in?) + (let ((expr-start (cur-pos))) + (begin + (skip-let-rhs-boundary!) + (let ((expr-src (slice src expr-start (cur-pos)))) + (let ((expr (ocaml-parse expr-src))) + (begin + (append! decls (list :expr expr)) + (loop))))))) + (else + (begin (append! decls (parse-decl-let)) (loop))))) ((at-kw? "module") (begin (append! decls (parse-decl-module)) (loop))) ((at-kw? "open") diff --git a/lib/ocaml/runtime.sx b/lib/ocaml/runtime.sx index d1ceec8f..9958cf38 100644 --- a/lib/ocaml/runtime.sx +++ b/lib/ocaml/runtime.sx @@ -360,6 +360,20 @@ let split_on_char c s = _string_split_on_char c s let replace_all s a b = _string_replace s a b let index_of s sub = _string_index_of s sub + let make n c = + let rec aux i acc = if i = 0 then acc else aux (i - 1) (acc ^ c) in + aux n \"\" + let init n f = + let rec aux i acc = + if i >= n then acc else aux (i + 1) (acc ^ f i) + in + aux 0 \"\" + let map f s = + let rec aux i acc = + if i >= _string_length s then acc + else aux (i + 1) (acc ^ f (_string_get s i)) + in + aux 0 \"\" end ;; module Bytes = struct diff --git a/plans/ocaml-on-sx.md b/plans/ocaml-on-sx.md index ab531d58..a0e456a9 100644 --- a/plans/ocaml-on-sx.md +++ b/plans/ocaml-on-sx.md @@ -407,6 +407,17 @@ _Newest first._ binary search tree (`type 'a tree = Leaf | Node of 'a * 'a tree * 'a tree`) with insert + in-order traversal. Tests parametric ADT, recursive match, List.append, List.fold_left. +- 2026-05-09 Phase 5.1 — caesar.ml baseline (ROT13 with String.init + + s.[i] + Char.code/chr). Side-quests: + (1) top-level `let r = expr in body` is now treated as an expression + decl when has-matching-in? returns true at the dispatcher. Slices via + skip-let-rhs-boundary which already opens depth on a leading let + with matching in; + (2) added String.make / String.init / String.map to runtime; + (3) bumped lib/ocaml/baseline/run.sh per-program timeout 240→480s + for headroom on contended hosts. + Test = `Char.code r.[0] + Char.code r.[4]` after ROT13 round-trip on + "hello" → 215 (h+o). - 2026-05-08 Phase 4 — `s.[i]` string indexing syntax (+3 tests, 484 total). parse-atom-postfix now handles `.[expr]` after `.`, emitting `(:string-get S I)`; eval reduces to host `(nth s i)`.