From 07e862906708efd0bbd4e8ba6a682e3aea774416 Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 17 Feb 2026 10:35:12 -0700 Subject: [PATCH] Step 559: add call stack frame inspector model --- editor/CMakeLists.txt | 9 ++ editor/src/CallStackFrameInspector.h | 58 +++++++++++ editor/tests/step559_test.cpp | 146 +++++++++++++++++++++++++++ progress.md | 33 ++++++ 4 files changed, 246 insertions(+) create mode 100644 editor/src/CallStackFrameInspector.h create mode 100644 editor/tests/step559_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 4c4f2d4..639f942 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -3910,4 +3910,13 @@ target_link_libraries(step558_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step559_test tests/step559_test.cpp) +target_include_directories(step559_test PRIVATE src) +target_link_libraries(step559_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/CallStackFrameInspector.h b/editor/src/CallStackFrameInspector.h new file mode 100644 index 0000000..7ac7ccc --- /dev/null +++ b/editor/src/CallStackFrameInspector.h @@ -0,0 +1,58 @@ +#pragma once +// Step 559: Call Stack and Frame Inspector + +#include +#include +#include + +struct FrameScope { + int frameIndex = 0; + std::string functionName; + std::string filePath; + int line = 0; + std::map variables; +}; + +struct FrameInspectorResult { + bool ok = false; + int selectedIndex = -1; + FrameScope selected; + std::vector errors; +}; + +class CallStackFrameInspector { +public: + static FrameInspectorResult inspect(const std::vector& frames, + int requestedIndex) { + FrameInspectorResult out; + if (frames.empty()) { + out.errors.push_back("empty_stack"); + return out; + } + if (requestedIndex < 0 || requestedIndex >= (int)frames.size()) { + out.errors.push_back("invalid_frame_index"); + return out; + } + if (!isFrameValid(frames[requestedIndex])) { + out.errors.push_back("invalid_frame_payload"); + return out; + } + + out.ok = true; + out.selectedIndex = requestedIndex; + out.selected = frames[requestedIndex]; + return out; + } + + static std::vector variableNames(const FrameScope& frame) { + std::vector names; + for (const auto& kv : frame.variables) names.push_back(kv.first); + return names; + } + +private: + static bool isFrameValid(const FrameScope& f) { + return f.frameIndex >= 0 && !f.functionName.empty() && + !f.filePath.empty() && f.line > 0; + } +}; diff --git a/editor/tests/step559_test.cpp b/editor/tests/step559_test.cpp new file mode 100644 index 0000000..a276c5d --- /dev/null +++ b/editor/tests/step559_test.cpp @@ -0,0 +1,146 @@ +// Step 559: Call Stack and Frame Inspector (12 tests) + +#include "CallStackFrameInspector.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 hasError(const FrameInspectorResult& r, const std::string& e) { + for (const auto& x : r.errors) if (x == e) return true; + return false; +} + +static std::vector sampleFrames() { + return { + {0, "main", "main.cpp", 10, {{"argc", "2"}, {"argv", "ptr"}}}, + {1, "run", "runtime.cpp", 25, {{"cursor", "12"}, {"state", "ok"}}}, + {2, "dispatch", "runtime.cpp", 42, {{"event", "tick"}}} + }; +} + +void test_inspect_top_frame() { + TEST(inspect_top_frame); + auto r = CallStackFrameInspector::inspect(sampleFrames(), 0); + CHECK(r.ok, "inspect should succeed"); + CHECK(r.selected.functionName == "main", "function mismatch"); + PASS(); +} + +void test_inspect_middle_frame() { + TEST(inspect_middle_frame); + auto r = CallStackFrameInspector::inspect(sampleFrames(), 1); + CHECK(r.ok, "inspect should succeed"); + CHECK(r.selected.functionName == "run", "function mismatch"); + PASS(); +} + +void test_inspect_bottom_frame() { + TEST(inspect_bottom_frame); + auto r = CallStackFrameInspector::inspect(sampleFrames(), 2); + CHECK(r.ok, "inspect should succeed"); + CHECK(r.selected.functionName == "dispatch", "function mismatch"); + PASS(); +} + +void test_empty_stack_fails() { + TEST(empty_stack_fails); + auto r = CallStackFrameInspector::inspect({}, 0); + CHECK(!r.ok, "empty stack should fail"); + CHECK(hasError(r, "empty_stack"), "empty_stack expected"); + PASS(); +} + +void test_negative_index_fails() { + TEST(negative_index_fails); + auto r = CallStackFrameInspector::inspect(sampleFrames(), -1); + CHECK(!r.ok, "negative index should fail"); + CHECK(hasError(r, "invalid_frame_index"), "invalid_frame_index expected"); + PASS(); +} + +void test_index_out_of_range_fails() { + TEST(index_out_of_range_fails); + auto r = CallStackFrameInspector::inspect(sampleFrames(), 5); + CHECK(!r.ok, "out-of-range index should fail"); + CHECK(hasError(r, "invalid_frame_index"), "invalid_frame_index expected"); + PASS(); +} + +void test_invalid_frame_payload_function_fails() { + TEST(invalid_frame_payload_function_fails); + auto frames = sampleFrames(); + frames[1].functionName.clear(); + auto r = CallStackFrameInspector::inspect(frames, 1); + CHECK(!r.ok, "invalid frame payload should fail"); + CHECK(hasError(r, "invalid_frame_payload"), "invalid payload expected"); + PASS(); +} + +void test_invalid_frame_payload_file_fails() { + TEST(invalid_frame_payload_file_fails); + auto frames = sampleFrames(); + frames[1].filePath.clear(); + auto r = CallStackFrameInspector::inspect(frames, 1); + CHECK(!r.ok, "invalid frame payload should fail"); + CHECK(hasError(r, "invalid_frame_payload"), "invalid payload expected"); + PASS(); +} + +void test_invalid_frame_payload_line_fails() { + TEST(invalid_frame_payload_line_fails); + auto frames = sampleFrames(); + frames[1].line = 0; + auto r = CallStackFrameInspector::inspect(frames, 1); + CHECK(!r.ok, "invalid frame payload should fail"); + CHECK(hasError(r, "invalid_frame_payload"), "invalid payload expected"); + PASS(); +} + +void test_variable_scope_mapping_preserved() { + TEST(variable_scope_mapping_preserved); + auto r = CallStackFrameInspector::inspect(sampleFrames(), 1); + CHECK(r.ok, "inspect should succeed"); + CHECK(r.selected.variables.at("cursor") == "12", "variable mapping mismatch"); + PASS(); +} + +void test_variable_names_exposed() { + TEST(variable_names_exposed); + auto r = CallStackFrameInspector::inspect(sampleFrames(), 0); + auto names = CallStackFrameInspector::variableNames(r.selected); + CHECK(names.size() == 2, "two names expected"); + PASS(); +} + +void test_selected_index_reported() { + TEST(selected_index_reported); + auto r = CallStackFrameInspector::inspect(sampleFrames(), 2); + CHECK(r.ok, "inspect should succeed"); + CHECK(r.selectedIndex == 2, "selected index mismatch"); + PASS(); +} + +int main() { + std::cout << "Step 559: Call Stack and Frame Inspector\n"; + + test_inspect_top_frame(); // 1 + test_inspect_middle_frame(); // 2 + test_inspect_bottom_frame(); // 3 + test_empty_stack_fails(); // 4 + test_negative_index_fails(); // 5 + test_index_out_of_range_fails(); // 6 + test_invalid_frame_payload_function_fails(); // 7 + test_invalid_frame_payload_file_fails(); // 8 + test_invalid_frame_payload_line_fails(); // 9 + test_variable_scope_mapping_preserved(); // 10 + test_variable_names_exposed(); // 11 + test_selected_index_reported(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index d26058b..b363ec3 100644 --- a/progress.md +++ b/progress.md @@ -9943,3 +9943,36 @@ capture into a complete Phase 30a debug control-plane cycle. - **Steps completed:** 5 - **New tests in phase plan:** 56/56 passing - **Debug control-plane integration:** PASS + +### Step 559: Call Stack and Frame Inspector +**Status:** PASS (12/12 tests) + +Implements call stack frame inspection with frame navigation and variable scope +mapping for runtime inspection surfaces. + +**Files added:** +- `editor/src/CallStackFrameInspector.h` - frame inspector module: + - frame selection by index + - payload validation for frame navigation metadata + - selected-frame scope extraction + - variable name listing helper for panel rendering +- `editor/tests/step559_test.cpp` - 12 tests covering: + - top/middle/bottom frame navigation + - invalid/empty stack guard behavior + - invalid frame payload guard behavior + - variable scope mapping and exposure behavior + - selected-index reporting behavior + +**Files modified:** +- `editor/CMakeLists.txt` - `step559_test` target + +**Verification run:** +- `cmake -S editor -B editor/build-native` - PASS +- `cmake --build editor/build-native --target step559_test step558_test` - PASS +- `./editor/build-native/step559_test` - PASS (12/12) +- `./editor/build-native/step558_test` - PASS (8/8) regression coverage + +**Architecture gate check:** +- `editor/src/CallStackFrameInspector.h` within header-size limit (`58` <= `600`) +- `editor/tests/step559_test.cpp` within test-file size guidance (`146` lines) +- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`