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
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:
@@ -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)))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -407,6 +407,13 @@ _Newest first._
|
||||
binary search tree (`type 'a tree = Leaf | Node of 'a * 'a tree *
|
||||
'a tree`) with insert + in-order traversal. Tests parametric ADT,
|
||||
recursive match, List.append, List.fold_left.
|
||||
- 2026-05-09 Phase 4 — `lazy EXPR` + `Lazy.force` (+2 tests, 496
|
||||
total). Tokenizer already had `lazy` as a keyword. parse-prefix now
|
||||
emits `(:lazy EXPR)`; eval creates a one-element cell with state
|
||||
`("Thunk" expr env)`. Host primitive `_lazy_force` flips the cell to
|
||||
`("Forced" v)` on first call and returns the cached value on
|
||||
subsequent calls. Memoization confirmed by tracking a side-effect
|
||||
counter through two forces (counter increments only once).
|
||||
- 2026-05-09 Phase 6 — Hashtbl.iter / Hashtbl.fold (+2 tests, 494
|
||||
total). New host primitive `_hashtbl_to_list` returns the entries
|
||||
as a list of OCaml tuples (`("tuple" k v)` form, matching the AST
|
||||
|
||||
Reference in New Issue
Block a user