From d9979eaf6c2eb7ff06d75ab07ca11d64d0e4b575 Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 8 May 2026 18:35:31 +0000 Subject: [PATCH] ocaml: phase 2 mutable record fields r.f <- v (+4 tests, 451 total) <- added to op-table at level 1 (same as :=). Eval short-circuits on <- to mutate the lhs's field via host SX dict-set!. The lhs must be a :field expression; otherwise raises. Tested: let r = { x = 1; y = 2 } in r.x <- 5; r.x (5) let r = { x = 0 } in for i = 1 to 5 do r.x <- r.x + i done; r.x (15) let r = { name = ...; age = 30 } in r.name <- "Alice"; r.name The 'mutable' keyword in record type decls is parsed-and-discarded; runtime semantics: every field is mutable. Phase 2 closes this gap without changing the dict-based record representation. --- lib/ocaml/eval.sx | 12 ++++++++++++ lib/ocaml/parser.sx | 1 + lib/ocaml/test.sh | 16 ++++++++++++++++ plans/ocaml-on-sx.md | 11 +++++++++++ 4 files changed, 40 insertions(+) diff --git a/lib/ocaml/eval.sx b/lib/ocaml/eval.sx index 22fa3b2c..e1b67468 100644 --- a/lib/ocaml/eval.sx +++ b/lib/ocaml/eval.sx @@ -442,6 +442,18 @@ (let ((cell (ocaml-eval (nth ast 2) env)) (new-val (ocaml-eval (nth ast 3) env))) (begin (set-nth! cell 0 new-val) nil))) + ;; <- mutates a record field. The lhs must be a (:field ...). + ((= op "<-") + (let ((lhs-ast (nth ast 2)) (new-val (ocaml-eval (nth ast 3) env))) + (cond + ((= (ocaml-tag-of lhs-ast) "field") + (let ((target (ocaml-eval (nth lhs-ast 1) env)) + (fname (nth lhs-ast 2))) + (begin (dict-set! target fname new-val) nil))) + (else + (error + (str "ocaml-eval: <- expects a field-access lhs, got " + (ocaml-tag-of lhs-ast))))))) (else (ocaml-eval-op op (ocaml-eval (nth ast 2) env) diff --git a/lib/ocaml/parser.sx b/lib/ocaml/parser.sx index cd9ef2f2..70e3ff25 100644 --- a/lib/ocaml/parser.sx +++ b/lib/ocaml/parser.sx @@ -49,6 +49,7 @@ ocaml-op-table (list (list ":=" 1 :right) + (list "<-" 1 :right) (list "||" 2 :right) (list "or" 2 :right) (list "&&" 3 :right) diff --git a/lib/ocaml/test.sh b/lib/ocaml/test.sh index bcf66f4a..b9ad1f6c 100755 --- a/lib/ocaml/test.sh +++ b/lib/ocaml/test.sh @@ -1106,6 +1106,16 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 4002) (eval "(ocaml-run-program \"type point = { x : int; y : int };; let p = { x = 3; y = 4 };; p.x + p.y\")") +;; ── Mutable record fields (r.f <- v) ────────────────────────── +(epoch 4100) +(eval "(ocaml-run \"let r = { x = 1; y = 2 } in r.x <- 5; r.x\")") +(epoch 4101) +(eval "(ocaml-run \"let r = { x = 0 } in for i = 1 to 5 do r.x <- r.x + i done; r.x\")") +(epoch 4102) +(eval "(ocaml-run \"let r = { name = \\\"Bob\\\"; age = 30 } in r.name <- \\\"Alice\\\"; r.name\")") +(epoch 4103) +(eval "(ocaml-run \"let r = { x = 1; y = 2 } in r.x <- r.y * 10; r.x\")") + EPOCHS OUTPUT=$(timeout 180 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -1751,6 +1761,12 @@ check 4000 "record type decl" '("type-def-record" "point" () (("x") check 4001 "mutable field decl" '("mutable" "x")' check 4002 "record decl + use" '7' +# ── Mutable record fields ────────────────────────────────────── +check 4100 "r.x <- 5; r.x" '5' +check 4101 "for-loop accum r.x" '15' +check 4102 "r.name <- str" '"Alice"' +check 4103 "r.x <- r.y * 10" '20' + 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 cb036366..d8d9ac78 100644 --- a/plans/ocaml-on-sx.md +++ b/plans/ocaml-on-sx.md @@ -157,6 +157,10 @@ SX CEK evaluator (both JS and OCaml hosts) - [x] Arithmetic, comparison, boolean ops, string `^`, `mod`. - [x] Unit `()` value; `ignore`. - [x] References: `ref`, `!`, `:=`. +- [x] Mutable record fields via `r.f <- v` — uses host SX `dict-set!` + to mutate the underlying record dict in place. All record fields + are de-facto mutable (the `mutable` keyword in type-decls is + currently parsed-and-discarded). - [ ] Mutable record fields. - [x] `for i = lo to hi do ... done` loop; `while cond do ... done` (incl. `downto` direction). @@ -403,6 +407,13 @@ _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 2 — mutable record fields `r.f <- v` (+4 tests, 451 + total). `<-` added to op-table at level 1 (same as `:=`). Eval + short-circuits on `<-` to mutate the lhs's field via host SX + `dict-set!`. Tested with for-loop accumulator (`for i = 1 to 5 do + r.x <- r.x + i done`) and string-field reassignment. The `mutable` + keyword in record-type decls is parsed-and-discarded; runtime + semantics: every field is mutable. - 2026-05-08 Phase 1+3 — record type declarations `type r = { x : int; mutable y : string }` (+3 tests, 447 total). Parser dispatches on `{` after `=` to parse field list (`mutable` keyword tracked).