ocaml: phase 2 references ref/!/:= (+6 tests, 189 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 55s

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.
This commit is contained in:
2026-05-08 08:07:26 +00:00
parent 9b833a9442
commit a11f3c33b6
4 changed files with 51 additions and 6 deletions

View File

@@ -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)

View File

@@ -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)))))

View File

@@ -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"

View File

@@ -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