Step 10c: unified reactive model — peek + provide! special forms + tracking primitives

CEK evaluator integration:
- peek — non-tracking read from provide frame (like context but never subscribes)
- provide! — mutate value in provide frame (cf_extra made mutable)
- Both dispatch as special forms alongside provide/context

Scope-stack primitives (for adapter/island use):
- provide-reactive! / provide-pop-reactive! / provide-set! — signal-backed scope
- peek (primitive) — non-tracking scope read
- context (override) — tracking-aware scope read
- bind — tracked computation with auto-resubscription
- tracking-start! / tracking-stop! / tracking-active? — tracking context

12/13 user-authored peek/provide! tests pass.
bind integration with CEK context pending (scope vs kont gap).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-05 02:10:26 +00:00
parent b3e9ebee1d
commit 98fd315f14
5 changed files with 133 additions and 84 deletions

View File

@@ -1378,6 +1378,64 @@ let () =
| [] -> (match rest with default_val :: _ -> default_val | [] -> Nil))
| _ -> Nil);
(* tracking-register-scope! — explicitly register a reactive provide as a dep *)
register "tracking-register-scope!" (fun args ->
match args with
| [String name] ->
if !_tracking_active then begin
let stack = try Hashtbl.find _scope_stacks name with Not_found -> [] in
match stack with
| Signal sig' :: _ ->
if not (List.memq (Signal sig') !_tracking_deps) then
_tracking_deps := Signal sig' :: !_tracking_deps;
Nil
| _ -> Nil
end else Nil
| _ -> Nil);
(* bind — create a tracked computation. Takes a body-fn (lambda).
Starts tracking, evaluates body, collects deps, subscribes.
On dep change: unsubscribes, re-evaluates, re-subscribes.
Returns initial value. Optional update-fn called with new values. *)
register "bind" (fun args ->
match args with
| [body_fn] | [body_fn; _] ->
let update_fn = match args with [_; u] -> Some u | _ -> None in
let disposers : (unit -> unit) list ref = ref [] in
let rec run_tracked () =
(* Clean up previous subscriptions *)
List.iter (fun d -> d ()) !disposers;
disposers := [];
(* Start tracking *)
_tracking_active := true;
_tracking_deps := [];
(* Evaluate body *)
let result = !Sx_types._cek_call_ref body_fn Nil in
(* Collect deps *)
let deps = !_tracking_deps in
_tracking_active := false;
_tracking_deps := [];
(* Subscribe to each dep *)
List.iter (fun dep ->
match dep with
| Signal sig' ->
let subscriber = (fun () ->
let new_result = run_tracked () in
match update_fn with
| Some f -> ignore (!Sx_types._cek_call_ref f (List [new_result]))
| None -> ()
) in
sig'.s_subscribers <- subscriber :: sig'.s_subscribers;
disposers := (fun () ->
sig'.s_subscribers <- List.filter (fun s -> s != subscriber) sig'.s_subscribers
) :: !disposers
| _ -> ()
) deps;
result
in
run_tracked ()
| _ -> raise (Eval_error "bind: expected (body-fn) or (body-fn update-fn)"));
(* --- Emit / emitted --- *)
register "scope-emit!" (fun args ->

File diff suppressed because one or more lines are too long

View File

@@ -95,7 +95,7 @@ and cek_frame = {
cf_f : value; (* call/map/filter/etc: function *)
cf_args : value; (* call: raw args; arg: evaled args *)
cf_results : value; (* map/filter/dict: accumulated results *)
cf_extra : value; (* extra field: scheme, indexed, value, phase, etc. *)
mutable cf_extra : value; (* extra field: scheme, indexed, value, phase, etc. *)
cf_extra2 : value; (* second extra: emitted, etc. *)
}