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)))))
|
(fn (t) (fn (k) (has-key? (nth t 0) (str k)))))
|
||||||
(list "_hashtbl_length"
|
(list "_hashtbl_length"
|
||||||
(fn (t) (len (keys (nth t 0)))))
|
(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.
|
;; _hashtbl_to_list: returns [(k, v); ...] as a list of pairs.
|
||||||
;; Keys are returned as the stringified form used internally.
|
;; Keys are returned as the stringified form used internally.
|
||||||
(list "_hashtbl_to_list"
|
(list "_hashtbl_to_list"
|
||||||
@@ -451,6 +465,11 @@
|
|||||||
(cond
|
(cond
|
||||||
((= v false) (error "Assert_failure"))
|
((= v false) (error "Assert_failure"))
|
||||||
(else nil))))
|
(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")
|
((= tag "deref")
|
||||||
(let ((cell (ocaml-eval (nth ast 1) env)))
|
(let ((cell (ocaml-eval (nth ast 1) env)))
|
||||||
(nth cell 0)))
|
(nth cell 0)))
|
||||||
|
|||||||
@@ -624,6 +624,8 @@
|
|||||||
(begin (advance-tok!) (list :not (parse-prefix))))
|
(begin (advance-tok!) (list :not (parse-prefix))))
|
||||||
((at-kw? "assert")
|
((at-kw? "assert")
|
||||||
(begin (advance-tok!) (list :assert (parse-prefix))))
|
(begin (advance-tok!) (list :assert (parse-prefix))))
|
||||||
|
((at-kw? "lazy")
|
||||||
|
(begin (advance-tok!) (list :lazy (parse-prefix))))
|
||||||
(else (parse-app)))))
|
(else (parse-app)))))
|
||||||
(set!
|
(set!
|
||||||
parse-binop-rhs
|
parse-binop-rhs
|
||||||
|
|||||||
@@ -455,6 +455,10 @@
|
|||||||
let printf fmt = sprintf fmt
|
let printf fmt = sprintf fmt
|
||||||
end ;;
|
end ;;
|
||||||
|
|
||||||
|
module Lazy = struct
|
||||||
|
let force lz = _lazy_force lz
|
||||||
|
end ;;
|
||||||
|
|
||||||
module Stack = struct
|
module Stack = struct
|
||||||
let create () = ref []
|
let create () = ref []
|
||||||
let push x s = s := x :: !s
|
let push x s = s := x :: !s
|
||||||
|
|||||||
@@ -1228,6 +1228,12 @@ cat > "$TMPFILE" << 'EPOCHS'
|
|||||||
(epoch 4951)
|
(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\")")
|
(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
|
EPOCHS
|
||||||
|
|
||||||
OUTPUT=$(timeout 360 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
|
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 4950 "Hashtbl.fold sum 1+2+3" '6'
|
||||||
check 4951 "Hashtbl.iter ref accum 10+20" '30'
|
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))
|
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"
|
||||||
|
|||||||
@@ -407,6 +407,13 @@ _Newest first._
|
|||||||
binary search tree (`type 'a tree = Leaf | Node of 'a * 'a tree *
|
binary search tree (`type 'a tree = Leaf | Node of 'a * 'a tree *
|
||||||
'a tree`) with insert + in-order traversal. Tests parametric ADT,
|
'a tree`) with insert + in-order traversal. Tests parametric ADT,
|
||||||
recursive match, List.append, List.fold_left.
|
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
|
- 2026-05-09 Phase 6 — Hashtbl.iter / Hashtbl.fold (+2 tests, 494
|
||||||
total). New host primitive `_hashtbl_to_list` returns the entries
|
total). New host primitive `_hashtbl_to_list` returns the entries
|
||||||
as a list of OCaml tuples (`("tuple" k v)` form, matching the AST
|
as a list of OCaml tuples (`("tuple" k v)` form, matching the AST
|
||||||
|
|||||||
Reference in New Issue
Block a user