#if !defined(_REPL_H_) #define _REPL_H_ 1 #include "sim.h" /* * repl.h - interactive simulation REPL (scheduler-core spec, Req 5 AC 22). * New file per design.md's File Structure Plan ("repl.c -- NEW (Phase 3): * interactive REPL command loop (Req 5 AC 22)"), Phase 3, sibling to * sim.h/sim.c/sim_loop.c. Task 12.1 built execution control (step/continue/ * quit, pre-replay pause); task 12.2 (this revision) adds breakpoints * (event/BEGIN_ROUND/DECISION, with key=value filters) and the `info` * state-inspection command family. * * This header declares repl.c's driver-facing entry points, plus `static * inline` wrappers that fold the `sim->interactive` gate INTO the header * itself. That inline-wrapper indirection is a deliberate, judgment-based * deviation from this file family's usual "extern declared where consumed, * no dedicated header" convention (sim_verify.c/sim_record.c/ * recording_session.c's entry points are all extern-declared directly * inside sim_loop.c -- see that file's own header comment): sim_loop.c sits * right at its 800-line cap (CLAUDE.md), leaving no room for even one bare * `if (sim->interactive) { ... }` block at any of the five call sites * design.md's REPL integration pseudocode requires (pre-replay pause, * post-session pause, per-event/per-BEGIN_ROUND/per-decision breakpoint * checks). Moving the gate into these inline wrappers keeps sim_loop.c's * own diff to five single-line, brace-free call expressions. The real * functions below still carry the actual logic; a `static inline` here * costs nothing at link time (each including TU gets its own copy, * trivially small, no ODR risk -- sim_loop.c is the only production * consumer). * * Boundary (Req 5 AC 17, "Simulation isolation", same as sim.h): this header * only depends on sim.h (itself core.h + stdbool/stdint/stdio) -- no * scheduler.h, no client.h, no capnp types. xce_core_event_t (core.h) and * xce_decision_t (decision.h, included transitively via core.h) are both * already capnp-free, so the three new task 12.2 hooks below add no new * dependency. repl.c's own file header comment documents the same boundary * for its implementation. */ /* * xce_repl_start - the pre-replay pause (Req 5 AC 22's interaction model: * "blocks on stdin at startup"). Prints the load banner ("[sim] loaded: ... * snapshot @ ts=...", "[sim] ready. type 'help' for commands.") then enters * the REPL command loop, blocking on stdin until the operator issues a * command that hands control back (`step [N]`/`continue`) or terminates the * process (`quit`, which never returns). Called once, by * sched_sim_driver_run() (sim_loop.c), strictly before its event loop's * `for(;;)` begins -- see xce_repl_pre_replay_pause() below for the actual * production call site. */ extern void xce_repl_start(xce_sched_sim_driver_t* sim); /* * xce_repl_post_session_check - the `step N` pause-decision point (Req 5 AC * 22's interaction model: a `step` budget reaching zero). Called once per * completed ReplaySession, immediately after xce_core_end_session() * (sim_loop.c), when sim->interactive is set. This is a SEPARATE pause path * from the breakpoint checks below -- it never inspects the breakpoint * list (that would be too coarse: a breakpoint must interrupt mid-batch, at * the exact matching event/decision, not merely once per completed * session). See xce_repl_check_begin_round()'s own doc comment for why * task 12.2 gave breakpoints their own, separately-triggered hooks instead * of folding them into this function. */ extern void xce_repl_post_session_check(xce_sched_sim_driver_t* sim); /* * xce_repl_on_event / xce_repl_on_begin_round / xce_repl_on_decision (task * 12.2, Req 5 AC 22 "Breakpoints" + "REPL integration with the simulation * loop" pseudocode) - the three breakpoint-check hooks, one per granularity * the pseudocode's `check_breakpoints(...)` calls require: per decoded * ReplayEvent (before it is applied to core state), per ReplaySession * boundary (BEGIN_ROUND, before the accumulated event batch is pushed), and * per drained decision (DECISION, after verify/output-write, matching * sim_loop.c's existing per-decision loop body). Each also does lightweight * position bookkeeping (event_index/session_count/last-session decision * tally) that `info session` reports -- folded in here rather than given * separate hook call sites, since these are the only three places in * sim_loop.c's loop where "one event/session/decision was just processed" * is already known, and sim_loop.c has no line budget for a sixth call * site. Implemented in repl.c; each checks sim->repl_state's breakpoint * list for a match and, if found, prints a "[sim] hit breakpoint ..." line * and re-enters the same blocking command loop xce_repl_start() uses. A * NULL sim/ev/d, or a not-yet-allocated repl_state, is a safe no-op (same * "never load-bearing, degrade to unattended" discipline * repl_state_ensure()'s own doc comment establishes). */ extern void xce_repl_on_event(xce_sched_sim_driver_t* sim, xce_core_event_t* ev); extern void xce_repl_on_begin_round(xce_sched_sim_driver_t* sim, uint64_t begin_round); extern void xce_repl_on_decision(xce_sched_sim_driver_t* sim, xce_decision_t* d); /* * xce_repl_on_replay_complete (task 12.5, Req 5 AC 22 amendment, bug-1594) - * called once, by sched_sim_driver_run() (sim_loop.c), immediately after the * replay loop reaches EOF or the AC 20 end-of-stream marker -- the ONE point * in the whole REPL integration that requirements.md's original pseudocode * let fall through to process exit with no re-entry into the command loop. * Prints the existing "[sim] replay complete" banner, marks * sim->repl_state->replay_complete, and re-enters repl_command_loop() so the * operator can inspect final state; the only remaining exit paths at that * point are an explicit `quit` or stdin EOF (both already funnel through * repl_cmd_quit()). Implemented in repl.c. */ extern void xce_repl_on_replay_complete(xce_sched_sim_driver_t* sim); static inline void xce_repl_pre_replay_pause(xce_sched_sim_driver_t* sim) { if (sim->interactive) { xce_repl_start(sim); } } static inline void xce_repl_post_session_pause(xce_sched_sim_driver_t* sim) { if (sim->interactive) { xce_repl_post_session_check(sim); } } static inline void xce_repl_check_event(xce_sched_sim_driver_t* sim, xce_core_event_t* ev) { if (sim->interactive) { xce_repl_on_event(sim, ev); } } static inline void xce_repl_check_begin_round(xce_sched_sim_driver_t* sim, uint64_t begin_round) { if (sim->interactive) { xce_repl_on_begin_round(sim, begin_round); } } static inline void xce_repl_check_decision(xce_sched_sim_driver_t* sim, xce_decision_t* d) { if (sim->interactive) { xce_repl_on_decision(sim, d); } } static inline void xce_repl_check_replay_complete(xce_sched_sim_driver_t* sim) { if (sim->interactive) { xce_repl_on_replay_complete(sim); } } #endif