ocaml: phase 4 'lazy EXPR' + Lazy.force (+2 tests, 496 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s

Tokenizer already had 'lazy' as a keyword. This commit wires it through:

  parser  : parse-prefix emits (:lazy EXPR), like the existing 'assert'
            handler.
  eval    : creates a one-element cell with state ('Thunk' expr env).
  host    : _lazy_force flips the cell to ('Forced' v) on first call
            and returns the cached value thereafter.
  runtime : module Lazy = struct let force lz = _lazy_force lz end.

Memoisation confirmed by tracking a side-effect counter through two
forces of the same lazy:

  let counter = ref 0 in
  let lz = lazy (counter := !counter + 1; 42) in
  let a = Lazy.force lz in
  let b = Lazy.force lz in
  (a + b) * 100 + !counter        = 8401   (= 84*100 + 1)
This commit is contained in:
2026-05-09 01:03:40 +00:00
parent 207dfc60ad
commit 9907c1c58c
5 changed files with 42 additions and 0 deletions

View File

@@ -116,6 +116,20 @@
(fn (t) (fn (k) (has-key? (nth t 0) (str k)))))
(list "_hashtbl_length"
(fn (t) (len (keys (nth t 0)))))
;; _lazy_force: evaluate the thunk on first force, cache result.
;; cell: one-elt list whose value is ("Thunk" expr env) or
;; ("Forced" v).
(list "_lazy_force"
(fn (cell)
(let ((state (nth cell 0)))
(cond
((= (first state) "Forced") (nth state 1))
(else
(let ((expr (nth state 1)) (env (nth state 2)))
(let ((v (ocaml-eval expr env)))
(begin
(set-nth! cell 0 (list "Forced" v))
v))))))))
;; _hashtbl_to_list: returns [(k, v); ...] as a list of pairs.
;; Keys are returned as the stringified form used internally.
(list "_hashtbl_to_list"
@@ -451,6 +465,11 @@
(cond
((= v false) (error "Assert_failure"))
(else nil))))
((= tag "lazy")
;; (:lazy EXPR) — create a one-element cell containing
;; ("Thunk" EXPR env); _lazy_force evaluates and caches.
(let ((expr (nth ast 1)))
(list (list "Thunk" expr env))))
((= tag "deref")
(let ((cell (ocaml-eval (nth ast 1) env)))
(nth cell 0)))

View File

@@ -624,6 +624,8 @@
(begin (advance-tok!) (list :not (parse-prefix))))
((at-kw? "assert")
(begin (advance-tok!) (list :assert (parse-prefix))))
((at-kw? "lazy")
(begin (advance-tok!) (list :lazy (parse-prefix))))
(else (parse-app)))))
(set!
parse-binop-rhs

View File

@@ -455,6 +455,10 @@
let printf fmt = sprintf fmt
end ;;
module Lazy = struct
let force lz = _lazy_force lz
end ;;
module Stack = struct
let create () = ref []
let push x s = s := x :: !s

View File

@@ -1228,6 +1228,12 @@ cat > "$TMPFILE" << 'EPOCHS'
(epoch 4951)
(eval "(ocaml-run \"let t = Hashtbl.create 4 in Hashtbl.add t \\\"x\\\" 10; Hashtbl.add t \\\"y\\\" 20; let total = ref 0 in Hashtbl.iter (fun _ v -> total := !total + v) t; !total\")")
;; ── lazy / Lazy.force ─────────────────────────────────────────
(epoch 4960)
(eval "(ocaml-run \"let x = lazy (1 + 2) in Lazy.force x\")")
(epoch 4961)
(eval "(ocaml-run \"let counter = ref 0 in let lz = lazy (counter := !counter + 1; 42) in let a = Lazy.force lz in let b = Lazy.force lz in (a + b) * 100 + !counter\")")
EPOCHS
OUTPUT=$(timeout 360 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
@@ -1948,6 +1954,10 @@ check 4944 "string_of_int + string_of_b" '"7-true"'
check 4950 "Hashtbl.fold sum 1+2+3" '6'
check 4951 "Hashtbl.iter ref accum 10+20" '30'
# ── lazy / Lazy.force ─────────────────────────────────────────
check 4960 "lazy 1+2 force" '3'
check 4961 "lazy memoization counter=1" '8401'
TOTAL=$((PASS + FAIL))
if [ $FAIL -eq 0 ]; then
echo "ok $PASS/$TOTAL OCaml-on-SX tests passed"