From 3f75daf8ba87d784bca50d4fffb57b29f40c1506 Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 17 Feb 2026 10:29:55 -0700 Subject: [PATCH] Step 556: add deterministic debug step engine --- editor/CMakeLists.txt | 9 ++ editor/src/StepEngine.h | 135 +++++++++++++++++++++++++++++ editor/tests/step556_test.cpp | 154 ++++++++++++++++++++++++++++++++++ progress.md | 35 ++++++++ 4 files changed, 333 insertions(+) create mode 100644 editor/src/StepEngine.h create mode 100644 editor/tests/step556_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 6280893..857d41d 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -3883,4 +3883,13 @@ target_link_libraries(step555_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step556_test tests/step556_test.cpp) +target_include_directories(step556_test PRIVATE src) +target_link_libraries(step556_test PRIVATE + nlohmann_json::nlohmann_json + unofficial::tree-sitter::tree-sitter + tree_sitter_python tree_sitter_cpp tree_sitter_elisp + tree_sitter_javascript tree_sitter_typescript + tree_sitter_java tree_sitter_rust tree_sitter_go) + # Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies) diff --git a/editor/src/StepEngine.h b/editor/src/StepEngine.h new file mode 100644 index 0000000..c2612cb --- /dev/null +++ b/editor/src/StepEngine.h @@ -0,0 +1,135 @@ +#pragma once +// Step 556: Step Engine + +#include +#include + +enum class DebugRunState { + Running, + Paused, + Completed +}; + +struct StepEngineState { + DebugRunState runState = DebugRunState::Paused; + int stackDepth = 1; + int instructionPointer = 0; + std::string lastCommand; +}; + +struct StepResult { + bool ok = false; + StepEngineState state; + std::vector diagnostics; +}; + +class StepEngine { +public: + StepEngine() = default; + + const StepEngineState& state() const { return state_; } + + StepResult execute(const std::string& command) { + StepResult out; + out.state = state_; + + if (state_.runState == DebugRunState::Completed) { + out.ok = false; + out.diagnostics.push_back("session_completed"); + return out; + } + + if (command == "continue") { + return onContinue(); + } + if (command == "step_over") { + return onStepOver(); + } + if (command == "step_into") { + return onStepInto(); + } + if (command == "step_out") { + return onStepOut(); + } + + out.ok = false; + out.diagnostics.push_back("unknown_command"); + return out; + } + + bool pause() { + if (state_.runState == DebugRunState::Completed) return false; + state_.runState = DebugRunState::Paused; + return true; + } + +private: + StepEngineState state_; + + StepResult onContinue() { + StepResult out; + state_.runState = DebugRunState::Running; + state_.instructionPointer += 5; + state_.lastCommand = "continue"; + // Deterministic stop point after "running" burst. + state_.runState = DebugRunState::Paused; + out.ok = true; + out.state = state_; + return out; + } + + StepResult onStepOver() { + StepResult out; + if (state_.runState != DebugRunState::Paused) { + out.ok = false; + out.diagnostics.push_back("must_be_paused"); + out.state = state_; + return out; + } + state_.instructionPointer += 1; + state_.lastCommand = "step_over"; + out.ok = true; + out.state = state_; + return out; + } + + StepResult onStepInto() { + StepResult out; + if (state_.runState != DebugRunState::Paused) { + out.ok = false; + out.diagnostics.push_back("must_be_paused"); + out.state = state_; + return out; + } + state_.instructionPointer += 1; + state_.stackDepth += 1; + state_.lastCommand = "step_into"; + out.ok = true; + out.state = state_; + return out; + } + + StepResult onStepOut() { + StepResult out; + if (state_.runState != DebugRunState::Paused) { + out.ok = false; + out.diagnostics.push_back("must_be_paused"); + out.state = state_; + return out; + } + if (state_.stackDepth <= 1) { + state_.runState = DebugRunState::Completed; + state_.lastCommand = "step_out"; + out.ok = true; + out.state = state_; + out.diagnostics.push_back("session_completed"); + return out; + } + state_.stackDepth -= 1; + state_.instructionPointer += 1; + state_.lastCommand = "step_out"; + out.ok = true; + out.state = state_; + return out; + } +}; diff --git a/editor/tests/step556_test.cpp b/editor/tests/step556_test.cpp new file mode 100644 index 0000000..52f61ee --- /dev/null +++ b/editor/tests/step556_test.cpp @@ -0,0 +1,154 @@ +// Step 556: Step Engine (12 tests) + +#include "StepEngine.h" + +#include + +static int passed = 0, failed = 0; +#define TEST(name) { std::cout << " " << #name << "... "; } +#define PASS() { std::cout << "PASS\n"; ++passed; } +#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; } +#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {} + +static bool hasDiag(const StepResult& r, const std::string& d) { + for (const auto& x : r.diagnostics) if (x == d) return true; + return false; +} + +void test_step_over_advances_instruction_pointer() { + TEST(step_over_advances_instruction_pointer); + StepEngine e; + auto before = e.state().instructionPointer; + auto r = e.execute("step_over"); + CHECK(r.ok, "step_over should succeed"); + CHECK(r.state.instructionPointer == before + 1, "ip should advance by one"); + PASS(); +} + +void test_step_into_advances_ip_and_stack_depth() { + TEST(step_into_advances_ip_and_stack_depth); + StepEngine e; + auto r = e.execute("step_into"); + CHECK(r.ok, "step_into should succeed"); + CHECK(r.state.instructionPointer == 1, "ip should advance"); + CHECK(r.state.stackDepth == 2, "stack depth should increase"); + PASS(); +} + +void test_step_out_reduces_stack_depth_when_nested() { + TEST(step_out_reduces_stack_depth_when_nested); + StepEngine e; + CHECK(e.execute("step_into").ok, "step_into should nest"); + auto r = e.execute("step_out"); + CHECK(r.ok, "step_out should succeed"); + CHECK(r.state.stackDepth == 1, "stack should reduce"); + PASS(); +} + +void test_step_out_from_root_completes_session() { + TEST(step_out_from_root_completes_session); + StepEngine e; + auto r = e.execute("step_out"); + CHECK(r.ok, "step_out root should succeed"); + CHECK(r.state.runState == DebugRunState::Completed, "session should complete"); + CHECK(hasDiag(r, "session_completed"), "completion diag expected"); + PASS(); +} + +void test_continue_runs_and_pauses_deterministically() { + TEST(continue_runs_and_pauses_deterministically); + StepEngine e; + auto before = e.state().instructionPointer; + auto r = e.execute("continue"); + CHECK(r.ok, "continue should succeed"); + CHECK(r.state.runState == DebugRunState::Paused, "engine should return to paused state"); + CHECK(r.state.instructionPointer == before + 5, "continue should advance by deterministic burst"); + PASS(); +} + +void test_unknown_command_fails() { + TEST(unknown_command_fails); + StepEngine e; + auto r = e.execute("teleport"); + CHECK(!r.ok, "unknown command should fail"); + CHECK(hasDiag(r, "unknown_command"), "unknown command diag expected"); + PASS(); +} + +void test_pause_keeps_engine_paused() { + TEST(pause_keeps_engine_paused); + StepEngine e; + CHECK(e.pause(), "pause should succeed"); + CHECK(e.state().runState == DebugRunState::Paused, "engine should remain paused"); + PASS(); +} + +void test_commands_fail_after_completion() { + TEST(commands_fail_after_completion); + StepEngine e; + CHECK(e.execute("step_out").ok, "root step_out completes session"); + auto r = e.execute("step_over"); + CHECK(!r.ok, "commands should fail after completion"); + CHECK(hasDiag(r, "session_completed"), "completion diag expected"); + PASS(); +} + +void test_last_command_tracks_step_actions() { + TEST(last_command_tracks_step_actions); + StepEngine e; + CHECK(e.execute("step_into").ok, "step_into should succeed"); + CHECK(e.state().lastCommand == "step_into", "last command mismatch"); + CHECK(e.execute("step_over").ok, "step_over should succeed"); + CHECK(e.state().lastCommand == "step_over", "last command mismatch"); + PASS(); +} + +void test_continue_updates_last_command() { + TEST(continue_updates_last_command); + StepEngine e; + CHECK(e.execute("continue").ok, "continue should succeed"); + CHECK(e.state().lastCommand == "continue", "last command should be continue"); + PASS(); +} + +void test_multiple_step_into_accumulates_stack_depth() { + TEST(multiple_step_into_accumulates_stack_depth); + StepEngine e; + CHECK(e.execute("step_into").ok, "step_into #1"); + CHECK(e.execute("step_into").ok, "step_into #2"); + CHECK(e.state().stackDepth == 3, "stack depth should accumulate"); + PASS(); +} + +void test_instruction_pointer_progression_is_monotonic() { + TEST(instruction_pointer_progression_is_monotonic); + StepEngine e; + CHECK(e.execute("step_over").ok, "step_over"); + int a = e.state().instructionPointer; + CHECK(e.execute("step_into").ok, "step_into"); + int b = e.state().instructionPointer; + CHECK(e.execute("continue").ok, "continue"); + int c = e.state().instructionPointer; + CHECK(a < b && b < c, "ip progression should be monotonic"); + PASS(); +} + +int main() { + std::cout << "Step 556: Step Engine\n"; + + test_step_over_advances_instruction_pointer(); // 1 + test_step_into_advances_ip_and_stack_depth(); // 2 + test_step_out_reduces_stack_depth_when_nested(); // 3 + test_step_out_from_root_completes_session(); // 4 + test_continue_runs_and_pauses_deterministically(); // 5 + test_unknown_command_fails(); // 6 + test_pause_keeps_engine_paused(); // 7 + test_commands_fail_after_completion(); // 8 + test_last_command_tracks_step_actions(); // 9 + test_continue_updates_last_command(); // 10 + test_multiple_step_into_accumulates_stack_depth(); // 11 + test_instruction_pointer_progression_is_monotonic(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index b665ea8..c897f9f 100644 --- a/progress.md +++ b/progress.md @@ -9836,3 +9836,38 @@ file edits. - `editor/src/BreakpointSystem.h` within header-size limit (`118` <= `600`) - `editor/tests/step555_test.cpp` within test-file size guidance (`150` lines) - Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md` + +### Step 556: Step Engine +**Status:** PASS (12/12 tests) + +Implements deterministic stepping semantics for debug control with `step_into`, +`step_over`, `step_out`, and `continue` command mapping and explicit runtime +state transitions. + +**Files added:** +- `editor/src/StepEngine.h` - stepping control-plane module: + - deterministic command execution mapping + - state transitions across paused/running/completed states + - stack depth and instruction pointer progression semantics + - command validity guards for unknown/invalid lifecycle states +- `editor/tests/step556_test.cpp` - 12 tests covering: + - per-command transition behavior + - root/ nested `step_out` behavior + - deterministic continue behavior + - unknown command behavior + - completion-state command guard behavior + - command tracking and progression monotonicity + +**Files modified:** +- `editor/CMakeLists.txt` - `step556_test` target + +**Verification run:** +- `cmake -S editor -B editor/build-native` - PASS +- `cmake --build editor/build-native --target step556_test step555_test` - PASS +- `./editor/build-native/step556_test` - PASS (12/12) +- `./editor/build-native/step555_test` - PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/StepEngine.h` within header-size limit (`135` <= `600`) +- `editor/tests/step556_test.cpp` within test-file size guidance (`154` lines) +- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`