From a11f3c33b6831c4c91f189962b66cc81564abbfa Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 8 May 2026 08:07:26 +0000 Subject: [PATCH] ocaml: phase 2 references ref/!/:= (+6 tests, 189 total) ref is a builtin boxing its arg in a one-element list. Prefix ! parses to (:deref ...) and reads via (nth cell 0). := joins the binop precedence table at level 1 right-assoc and mutates via set-nth!. Closures share the underlying cell. --- lib/ocaml/eval.sx | 24 +++++++++++++++++++----- lib/ocaml/parser.sx | 3 +++ lib/ocaml/test.sh | 22 ++++++++++++++++++++++ plans/ocaml-on-sx.md | 8 +++++++- 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/lib/ocaml/eval.sx b/lib/ocaml/eval.sx index bebe0d26..25d0226c 100644 --- a/lib/ocaml/eval.sx +++ b/lib/ocaml/eval.sx @@ -38,7 +38,10 @@ (list "min" (fn (a) (fn (b) (if (< a b) a b)))) (list "fst" (fn (p) (nth p 1))) (list "snd" (fn (p) (nth p 2))) - (list "ignore" (fn (x) nil))))) + (list "ignore" (fn (x) nil)) + ;; References. A ref cell is a one-element list; ! reads it and + ;; := mutates it via set-nth!. + (list "ref" (fn (x) (list x)))))) (define ocaml-env-lookup (fn (env name) @@ -235,11 +238,22 @@ (else (error (str "ocaml-eval: unbound variable " name)))))) ((= tag "neg") (- 0 (ocaml-eval (nth ast 1) env))) ((= tag "not") (not (ocaml-eval (nth ast 1) env))) + ((= tag "deref") + (let ((cell (ocaml-eval (nth ast 1) env))) + (nth cell 0))) ((= tag "op") - (ocaml-eval-op - (nth ast 1) - (ocaml-eval (nth ast 2) env) - (ocaml-eval (nth ast 3) env))) + (let ((op (nth ast 1))) + (cond + ;; := mutates the lhs cell — short-circuit before generic + ;; eval-op so we still evaluate lhs (to obtain the cell). + ((= op ":=") + (let ((cell (ocaml-eval (nth ast 2) env)) + (new-val (ocaml-eval (nth ast 3) env))) + (begin (set-nth! cell 0 new-val) nil))) + (else + (ocaml-eval-op op + (ocaml-eval (nth ast 2) env) + (ocaml-eval (nth ast 3) env)))))) ((= tag "if") (if (ocaml-eval (nth ast 1) env) (ocaml-eval (nth ast 2) env) diff --git a/lib/ocaml/parser.sx b/lib/ocaml/parser.sx index 6c48ee9b..07df0622 100644 --- a/lib/ocaml/parser.sx +++ b/lib/ocaml/parser.sx @@ -48,6 +48,7 @@ (define ocaml-op-table (list + (list ":=" 1 :right) (list "||" 2 :right) (list "or" 2 :right) (list "&&" 3 :right) @@ -401,6 +402,8 @@ (cond ((at-op? "-") (begin (advance-tok!) (list :neg (parse-prefix)))) + ((at-op? "!") + (begin (advance-tok!) (list :deref (parse-prefix)))) ((at-kw? "not") (begin (advance-tok!) (list :not (parse-prefix)))) (else (parse-app))))) diff --git a/lib/ocaml/test.sh b/lib/ocaml/test.sh index be643746..d06fda43 100755 --- a/lib/ocaml/test.sh +++ b/lib/ocaml/test.sh @@ -481,6 +481,20 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 560) (eval "(ocaml-run \"match Pair (1, 2) with | Pair (a, b) -> a * b\")") +;; ── References (ref / ! / :=) ────────────────────────────────── +(epoch 600) +(eval "(ocaml-run \"let r = ref 5 in !r\")") +(epoch 601) +(eval "(ocaml-run \"let r = ref 5 in r := 10; !r\")") +(epoch 602) +(eval "(ocaml-run \"let r = ref 0 in r := !r + 1; r := !r + 1; !r\")") +(epoch 603) +(eval "(ocaml-run \"let r = ref 100 in let f x = r := !r + x in f 5; f 10; !r\")") +(epoch 604) +(eval "(ocaml-run \"let r = ref \\\"a\\\" in r := \\\"b\\\"; !r\")") +(epoch 605) +(eval "(ocaml-run \"let count = ref 0 in let rec loop n = if n = 0 then !count else (count := !count + n; loop (n - 1)) in loop 5\")") + EPOCHS OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -767,6 +781,14 @@ check 551 "match x -> x+1" '100' # ctor with tuple arg check 560 "Pair(a,b) → a*b" '2' +# ── References ────────────────────────────────────────────────── +check 600 "deref new ref" '5' +check 601 ":= then deref" '10' +check 602 "increment cell twice" '2' +check 603 "ref captured by closure" '115' +check 604 "ref of string" '"b"' +check 605 "ref + recursion" '15' + 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 e8ce1135..77c018ec 100644 --- a/plans/ocaml-on-sx.md +++ b/plans/ocaml-on-sx.md @@ -153,7 +153,7 @@ SX CEK evaluator (both JS and OCaml hosts) - [x] `if`/`then`/`else`, `begin`/`end`, sequence `;`. - [x] Arithmetic, comparison, boolean ops, string `^`, `mod`. - [x] Unit `()` value; `ignore`. -- [ ] References: `ref`, `!`, `:=`. +- [x] References: `ref`, `!`, `:=`. - [ ] Mutable record fields. - [ ] `for i = lo to hi do ... done` loop; `while cond do ... done`. - [ ] `try`/`with` — maps to SX `guard`; `raise` via perform. @@ -320,6 +320,12 @@ the "mother tongue" closure: OCaml → SX → OCaml. This means: _Newest first._ +- 2026-05-08 Phase 2 — references (`ref`/`!`/`:=`). `ref` is a builtin + that boxes its argument in a one-element list (the mutable cell); + prefix `!` parses to `(:deref EXPR)` and reads `(nth cell 0)`; `:=` + joins the precedence table at the lowest binop level (right-assoc) and + short-circuits in eval to mutate via `set-nth!`. Closures capture refs + by sharing the underlying list. 189/189 (+6). - 2026-05-08 Phase 3 — pattern matching evaluator + constructors (+18 tests, 183 total). Constructor application: `(:app (:con NAME) arg)` builds a tagged list `(NAME …args)` with tuple args flattened (so