ocaml: phase 2 for/while loops (+5 tests, 194 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 52s

Parser: for i = lo to|downto hi do body done, while cond do body done.
AST: (:for NAME LO HI :ascend|:descend BODY) and (:while COND BODY).
Eval re-binds the loop var per iteration; both forms evaluate to unit.
This commit is contained in:
2026-05-08 08:11:13 +00:00
parent a11f3c33b6
commit 9b8b0b4325
4 changed files with 98 additions and 1 deletions

View File

@@ -300,6 +300,51 @@
(fn-val arg-val)))))) (fn-val arg-val))))))
((= tag "match") ((= tag "match")
(ocaml-match-eval (nth ast 1) (nth ast 2) env)) (ocaml-match-eval (nth ast 1) (nth ast 2) env))
((= tag "for")
;; (:for NAME LO HI DIR BODY) — DIR is "ascend" or "descend".
(let ((name (nth ast 1))
(lo (ocaml-eval (nth ast 2) env))
(hi (ocaml-eval (nth ast 3) env))
(dir (nth ast 4))
(body (nth ast 5)))
(begin
(cond
((= dir "ascend")
(let ((i lo))
(begin
(define loop
(fn ()
(when (<= i hi)
(begin
(ocaml-eval body
(ocaml-env-extend env name i))
(set! i (+ i 1))
(loop)))))
(loop))))
((= dir "descend")
(let ((i lo))
(begin
(define loop
(fn ()
(when (>= i hi)
(begin
(ocaml-eval body
(ocaml-env-extend env name i))
(set! i (- i 1))
(loop)))))
(loop)))))
nil)))
((= tag "while")
(let ((cond-ast (nth ast 1)) (body (nth ast 2)))
(begin
(define loop
(fn ()
(when (ocaml-eval cond-ast env)
(begin
(ocaml-eval body env)
(loop)))))
(loop)
nil)))
((= tag "let") ((= tag "let")
(let ((name (nth ast 1)) (params (nth ast 2)) (let ((name (nth ast 1)) (params (nth ast 2))
(rhs (nth ast 3)) (body (nth ast 4))) (rhs (nth ast 3)) (body (nth ast 4)))

View File

@@ -573,6 +573,33 @@
(begin (advance-tok!) (one) (loop))))) (begin (advance-tok!) (one) (loop)))))
(loop) (loop)
(cons :match (cons scrut (list cases))))))))) (cons :match (cons scrut (list cases)))))))))
(define parse-for
(fn ()
(let ((name (ocaml-tok-value (consume! "ident" nil))))
(begin
(consume! "op" "=")
(let ((lo (parse-expr-no-seq)))
(let ((dir
(cond
((at-kw? "to") (begin (advance-tok!) :ascend))
((at-kw? "downto") (begin (advance-tok!) :descend))
(else (error "ocaml-parse: expected to/downto in for")))))
(let ((hi (parse-expr-no-seq)))
(begin
(consume! "keyword" "do")
(let ((body (parse-expr)))
(begin
(consume! "keyword" "done")
(list :for name lo hi dir body)))))))))))
(define parse-while
(fn ()
(let ((cond-expr (parse-expr-no-seq)))
(begin
(consume! "keyword" "do")
(let ((body (parse-expr)))
(begin
(consume! "keyword" "done")
(list :while cond-expr body)))))))
(set! (set!
parse-expr-no-seq parse-expr-no-seq
(fn (fn
@@ -582,6 +609,8 @@
((at-kw? "let") (begin (advance-tok!) (parse-let))) ((at-kw? "let") (begin (advance-tok!) (parse-let)))
((at-kw? "if") (begin (advance-tok!) (parse-if))) ((at-kw? "if") (begin (advance-tok!) (parse-if)))
((at-kw? "match") (begin (advance-tok!) (parse-match))) ((at-kw? "match") (begin (advance-tok!) (parse-match)))
((at-kw? "for") (begin (advance-tok!) (parse-for)))
((at-kw? "while") (begin (advance-tok!) (parse-while)))
(else (parse-tuple))))) (else (parse-tuple)))))
(set! (set!
parse-expr parse-expr

View File

@@ -495,6 +495,18 @@ cat > "$TMPFILE" << 'EPOCHS'
(epoch 605) (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\")") (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\")")
;; ── for / while loops ──────────────────────────────────────────
(epoch 620)
(eval "(ocaml-run \"let s = ref 0 in for i = 1 to 5 do s := !s + i done; !s\")")
(epoch 621)
(eval "(ocaml-run \"let s = ref 0 in for i = 5 downto 1 do s := !s + i done; !s\")")
(epoch 622)
(eval "(ocaml-run \"let i = ref 0 in let s = ref 0 in while !i < 5 do i := !i + 1; s := !s + !i done; !s\")")
(epoch 623)
(eval "(ocaml-run \"let s = ref 0 in for i = 1 to 100 do s := !s + i done; !s\")")
(epoch 624)
(eval "(ocaml-run \"let p = ref 1 in for i = 1 to 5 do p := !p * i done; !p\")")
EPOCHS EPOCHS
OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
@@ -789,6 +801,13 @@ check 603 "ref captured by closure" '115'
check 604 "ref of string" '"b"' check 604 "ref of string" '"b"'
check 605 "ref + recursion" '15' check 605 "ref + recursion" '15'
# ── for / while ─────────────────────────────────────────────────
check 620 "for 1..5 sum" '15'
check 621 "for 5 downto 1 sum" '15'
check 622 "while loop" '15'
check 623 "for 1..100 sum" '5050'
check 624 "for 1..5 product = 120" '120'
TOTAL=$((PASS + FAIL)) TOTAL=$((PASS + FAIL))
if [ $FAIL -eq 0 ]; then if [ $FAIL -eq 0 ]; then
echo "ok $PASS/$TOTAL OCaml-on-SX tests passed" echo "ok $PASS/$TOTAL OCaml-on-SX tests passed"

View File

@@ -155,7 +155,8 @@ SX CEK evaluator (both JS and OCaml hosts)
- [x] Unit `()` value; `ignore`. - [x] Unit `()` value; `ignore`.
- [x] References: `ref`, `!`, `:=`. - [x] References: `ref`, `!`, `:=`.
- [ ] Mutable record fields. - [ ] Mutable record fields.
- [ ] `for i = lo to hi do ... done` loop; `while cond do ... done`. - [x] `for i = lo to hi do ... done` loop; `while cond do ... done` (incl.
`downto` direction).
- [ ] `try`/`with` — maps to SX `guard`; `raise` via perform. - [ ] `try`/`with` — maps to SX `guard`; `raise` via perform.
- [ ] Tests in `lib/ocaml/tests/eval.sx` — 50+ tests, pure + imperative. - [ ] Tests in `lib/ocaml/tests/eval.sx` — 50+ tests, pure + imperative.
@@ -320,6 +321,9 @@ the "mother tongue" closure: OCaml → SX → OCaml. This means:
_Newest first._ _Newest first._
- 2026-05-08 Phase 2 — `for`/`while` loops. `(:for NAME LO HI DIR BODY)`
with `:ascend`/`:descend` direction (`to`/`downto`); `(:while COND BODY)`.
Both eval to unit and re-bind the loop var per iteration. 194/194 (+5).
- 2026-05-08 Phase 2 — references (`ref`/`!`/`:=`). `ref` is a builtin - 2026-05-08 Phase 2 — references (`ref`/`!`/`:=`). `ref` is a builtin
that boxes its argument in a one-element list (the mutable cell); that boxes its argument in a one-element list (the mutable cell);
prefix `!` parses to `(:deref EXPR)` and reads `(nth cell 0)`; `:=` prefix `!` parses to `(:deref EXPR)` and reads `(nth cell 0)`; `:=`