diff --git a/lib/ocaml/eval.sx b/lib/ocaml/eval.sx index 25d0226c..e6176172 100644 --- a/lib/ocaml/eval.sx +++ b/lib/ocaml/eval.sx @@ -300,6 +300,51 @@ (fn-val arg-val)))))) ((= tag "match") (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") (let ((name (nth ast 1)) (params (nth ast 2)) (rhs (nth ast 3)) (body (nth ast 4))) diff --git a/lib/ocaml/parser.sx b/lib/ocaml/parser.sx index 07df0622..3e548b5c 100644 --- a/lib/ocaml/parser.sx +++ b/lib/ocaml/parser.sx @@ -573,6 +573,33 @@ (begin (advance-tok!) (one) (loop))))) (loop) (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! parse-expr-no-seq (fn @@ -582,6 +609,8 @@ ((at-kw? "let") (begin (advance-tok!) (parse-let))) ((at-kw? "if") (begin (advance-tok!) (parse-if))) ((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))))) (set! parse-expr diff --git a/lib/ocaml/test.sh b/lib/ocaml/test.sh index d06fda43..abd32a5d 100755 --- a/lib/ocaml/test.sh +++ b/lib/ocaml/test.sh @@ -495,6 +495,18 @@ cat > "$TMPFILE" << 'EPOCHS' (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\")") +;; ── 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 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 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)) 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 77c018ec..4c0975e2 100644 --- a/plans/ocaml-on-sx.md +++ b/plans/ocaml-on-sx.md @@ -155,7 +155,8 @@ SX CEK evaluator (both JS and OCaml hosts) - [x] Unit `()` value; `ignore`. - [x] References: `ref`, `!`, `:=`. - [ ] 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. - [ ] 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._ +- 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 that boxes its argument in a one-element list (the mutable cell); prefix `!` parses to `(:deref EXPR)` and reads `(nth cell 0)`; `:=`