From f895a118fbb4229f3a9ed77ad028a2f32d885758 Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 8 May 2026 23:58:37 +0000 Subject: [PATCH] ocaml: phase 4 's.[i]' string indexing syntax (+3 tests, 484 total) parse-atom-postfix now dispatches three cases after consuming '.': .field -> existing field/module access .(EXPR) -> existing local-open .[EXPR] -> new string-get syntax (this commit) Eval reduces (:string-get S I) to host (nth S I), which already returns a one-character string for OCaml's char model. Lets us write idiomatic OCaml string traversal: let s = "hi" in let n = ref 0 in for i = 0 to String.length s - 1 do n := !n + Char.code s.[i] done; !n (* = 209 *) --- lib/ocaml/eval.sx | 5 +++++ lib/ocaml/parser.sx | 8 ++++++++ lib/ocaml/test.sh | 13 +++++++++++++ plans/ocaml-on-sx.md | 6 ++++++ 4 files changed, 32 insertions(+) diff --git a/lib/ocaml/eval.sx b/lib/ocaml/eval.sx index 6a22b22f..83111b9a 100644 --- a/lib/ocaml/eval.sx +++ b/lib/ocaml/eval.sx @@ -557,6 +557,11 @@ ((dict? target) (get target fname)) (else (error (str "ocaml-eval: not a record/module on .field: " target))))))) + ((= tag "string-get") + ;; (:string-get S I) — evaluate s.[i] as a char access. + (let ((s (ocaml-eval (nth ast 1) env)) + (i (ocaml-eval (nth ast 2) env))) + (nth s i))) ((= tag "for") ;; (:for NAME LO HI DIR BODY) — DIR is "ascend" or "descend". (let ((name (nth ast 1)) diff --git a/lib/ocaml/parser.sx b/lib/ocaml/parser.sx index c686a257..6b0f4abd 100644 --- a/lib/ocaml/parser.sx +++ b/lib/ocaml/parser.sx @@ -571,6 +571,14 @@ (consume! "op" ")") (set! head (list :let-open head inner)) (loop))))) + ((at-op? "[") + (begin + (advance-tok!) + (let ((idx-expr (parse-expr))) + (begin + (consume! "op" "]") + (set! head (list :string-get head idx-expr)) + (loop))))) (else (let ((tok (peek-tok))) (begin diff --git a/lib/ocaml/test.sh b/lib/ocaml/test.sh index cdaf806b..47fac6a2 100755 --- a/lib/ocaml/test.sh +++ b/lib/ocaml/test.sh @@ -1194,6 +1194,14 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 4912) (eval "(ocaml-run \"Option.(map (fun x -> x * 10) (Some 4))\")") +;; ── s.[i] string indexing ───────────────────────────────────── +(epoch 4920) +(eval "(ocaml-run \"let s = \\\"hello\\\" in s.[0]\")") +(epoch 4921) +(eval "(ocaml-run \"let s = \\\"abc\\\" in Char.code s.[2]\")") +(epoch 4922) +(eval "(ocaml-run \"let s = \\\"hi\\\" in let n = ref 0 in for i = 0 to String.length s - 1 do n := !n + Char.code s.[i] done; !n\")") + EPOCHS OUTPUT=$(timeout 360 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -1893,6 +1901,11 @@ check 4910 "M.(expr) length" '3' check 4911 "M.(expr) map" '(2 3 4)' check 4912 "M.(expr) Option map" '("Some" 40)' +# ── s.[i] string indexing ──────────────────────────────────────── +check 4920 "s.[0] hello" '"h"' +check 4921 "Char.code s.[2] abc" '99' +check 4922 "for i s.[i] sum hi" '209' + 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 1dec9472..ab531d58 100644 --- a/plans/ocaml-on-sx.md +++ b/plans/ocaml-on-sx.md @@ -407,6 +407,12 @@ _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-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)`. + Pairs with the existing `M.(expr)` and `.field` postfixes — all three + share one dot loop. `let s = "hi" in for i = 0 to String.length s - + 1 do n := !n + Char.code s.[i] done; !n` returns 209 (h+i). - 2026-05-08 Phase 5.1 — roman.ml baseline (Roman numeral greedy encoding). Side-quest: top-level `let () = expr` was unsupported by ocaml-parse-program — now parse-decl-let recognises `()` as a unit