diff --git a/lib/ocaml/eval.sx b/lib/ocaml/eval.sx index 2171a545..0f2ee6e5 100644 --- a/lib/ocaml/eval.sx +++ b/lib/ocaml/eval.sx @@ -490,6 +490,22 @@ (let ((target (ocaml-eval (nth lhs-ast 1) env)) (fname (nth lhs-ast 2))) (begin (dict-set! target fname new-val) nil))) + ((= (ocaml-tag-of lhs-ast) "array-get") + ;; (:array-get ARR I) <- v : rewrite the underlying + ;; list in the ref cell with one cell changed. + (let ((arr (ocaml-eval (nth lhs-ast 1) env)) + (i (ocaml-eval (nth lhs-ast 2) env))) + (begin + (define replace + (fn (lst k) + (cond + ((= lst (list)) (list)) + ((= k 0) (cons new-val (rest lst))) + (else + (cons (first lst) + (replace (rest lst) (- k 1))))))) + (set-nth! arr 0 (replace (nth arr 0) i)) + nil))) (else (error (str "ocaml-eval: <- expects a field-access lhs, got " @@ -602,6 +618,11 @@ (let ((s (ocaml-eval (nth ast 1) env)) (i (ocaml-eval (nth ast 2) env))) (nth s i))) + ((= tag "array-get") + ;; (:array-get ARR I) — Array.get on a ref-of-list. + (let ((arr (ocaml-eval (nth ast 1) env)) + (i (ocaml-eval (nth ast 2) env))) + (nth (nth arr 0) 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 2d1757f9..f84e6263 100644 --- a/lib/ocaml/parser.sx +++ b/lib/ocaml/parser.sx @@ -574,6 +574,16 @@ ;; `(:field (:field M "x") "y")`. (let ((head (parse-atom))) (begin + ;; Module-path detector: head is :con, or :field chain + ;; whose innermost subject is :con. Used to choose between + ;; (:let-open M EXPR) and (:array-get ARR I) after `.(`. + (define is-module-path? + (fn (h) + (cond + ((not (list? h)) false) + ((= (first h) "con") true) + ((= (first h) "field") (is-module-path? (nth h 1))) + (else false)))) (define loop (fn () (when (at-op? ".") @@ -586,7 +596,10 @@ (let ((inner (parse-expr))) (begin (consume! "op" ")") - (set! head (list :let-open head inner)) + (set! head + (if (is-module-path? head) + (list :let-open head inner) + (list :array-get head inner))) (loop))))) ((at-op? "[") (begin diff --git a/lib/ocaml/test.sh b/lib/ocaml/test.sh index 3cdd55a5..52a026eb 100755 --- a/lib/ocaml/test.sh +++ b/lib/ocaml/test.sh @@ -1278,6 +1278,14 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 5022) (eval "(ocaml-run \"List.map ((-) 10) [1;2;3]\")") +;; ── arr.(i) and arr.(i) <- v ──────────────────────────────── +(epoch 5030) +(eval "(ocaml-run \"let a = Array.make 5 7 in a.(2) <- 99; a.(2) + a.(0)\")") +(epoch 5031) +(eval "(ocaml-run \"let a = Array.init 4 (fun i -> i + 1) in a.(0) + a.(1) + a.(2) + a.(3)\")") +(epoch 5032) +(eval "(ocaml-run \"let a = Array.make 5 0 in for i = 0 to 4 do a.(i) <- i * i done; a.(3) + a.(4)\")") + EPOCHS OUTPUT=$(timeout 360 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -2030,6 +2038,11 @@ check 5020 "fold_left (+) sum" '15' check 5021 "let f = (*) in f 6 7" '42' check 5022 "List.map ((-) 10) [1;2;3]" '(9 8 7)' +# ── arr.(i) and arr.(i) <- v ──────────────────────────────────── +check 5030 "a.(2) <- 99; a.(2) + a.(0)" '106' +check 5031 "Array.init 4 + sum a.(0..3)" '10' +check 5032 "for + a.(i) <- i*i + sum" '25' + 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 52345331..3163d670 100644 --- a/plans/ocaml-on-sx.md +++ b/plans/ocaml-on-sx.md @@ -407,6 +407,18 @@ _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 4 — `arr.(i)` and `arr.(i) <- v` array indexing + syntax (+3 tests, 515 total). parse-atom-postfix's `.(...)` branch + now disambiguates between let-open and array-get based on whether + the head is a module path (`:con` or a `:field` chain rooted in a + `:con`). Module paths still emit `(:let-open M EXPR)`; everything + else emits `(:array-get ARR I)`. Eval handles `:array-get` by + reading the cell's underlying list at index. The `<-` assignment + handler now also accepts `:array-get` lhs and rewrites the cell + with one position changed. Lets us write idiomatic OCaml array code: + let a = Array.make 5 0 in + for i = 0 to 4 do a.(i) <- i * i done; + a.(3) + a.(4) (* = 25 *) - 2026-05-09 Phase 6 — Array module (ref-of-list backing) + (op) operator sections (+6 tests, 512 total). Array implements make/length/get/set/init/iter/iteri/map/mapi/fold_left/to_list/