nk 1.0.0
C++ Numerical Computing Runtime
Loading...
Searching...
No Matches
debugger.hpp
1// include/debugger.hpp
2//
3// Debugger infrastructure for M interpreter.
4//
5// Design principles:
6// - Zero overhead when no observer is attached
7// - Unified interface for both VM and TreeWalker backends
8// - Read-only DebugContext (no mutation through debug API)
9// - No coupling to UI — observer is abstract
10// - No heap allocation for debug frames
11// - Shared step logic — no duplication between backends
12//
13#pragma once
14
15#include <nk/value/value.hpp>
16#include <cstdint>
17#include <string>
18#include <unordered_map>
19#include <vector>
20
21namespace nk {
22
23class Environment;
24struct BytecodeChunk;
25
26// ============================================================
27// DebugAction — returned by observer to control execution
28// ============================================================
29enum class DebugAction : uint8_t {
30 Continue, // run until next breakpoint
31 StepOver, // next line in current scope (skip function calls)
32 StepInto, // step into function call (break on very next line)
33 StepOut, // run until current function returns
34 Stop // abort execution
35};
36
37// ============================================================
38// StackFrame — one entry in the call stack
39//
40// All data inline — no heap allocation, no void*.
41// VM sets chunk + registers, TW sets env. Unused fields are null.
42// ============================================================
44{
45 std::string functionName; // "<script>" for top-level
46 uint16_t line = 0;
47 uint16_t col = 0;
48
49 // VM backend: variable access via registers
50 const BytecodeChunk *chunk = nullptr;
51 const Value *registers = nullptr;
52
53 // TW backend: variable access via Environment
54 Environment *env = nullptr;
55
56 // Variable inspection (works for both backends)
57 struct VarEntry
58 {
59 std::string name;
60 const Value *value;
61 };
62 std::vector<VarEntry> variables() const;
63};
64
65// ============================================================
66// DebugContext — read-only view of execution state
67//
68// Passed to observer callbacks. callStack back() = current frame.
69// ============================================================
71{
72 uint16_t line = 0;
73 uint16_t col = 0;
74 const std::string *functionName = nullptr;
75
76 // Source code text for display. Available in VM backend (from BytecodeChunk).
77 // Null in TreeWalker backend (TW operates on AST, not compiled chunks).
78 const std::string *sourceCode = nullptr;
79
80 // Call stack: front() = top-level, back() = current frame
81 const std::vector<StackFrame> *callStack = nullptr;
82
83 const StackFrame *currentFrame() const
84 {
85 return (callStack && !callStack->empty()) ? &callStack->back() : nullptr;
86 }
87
88 const Value *getVariable(const std::string &name) const;
89 std::vector<std::string> variableNames() const;
90};
91
92// ============================================================
93// DebugObserver — abstract interface implemented by UI/CLI
94// ============================================================
96{
97public:
98 virtual ~DebugObserver() = default;
99 virtual DebugAction onLine(const DebugContext &ctx) = 0;
100 virtual DebugAction onBreakpoint(const DebugContext &ctx) = 0;
101 virtual void onError(const DebugContext &ctx, const std::string &msg) = 0;
102 virtual void onFunctionEntry(const DebugContext &ctx) = 0;
103 virtual void onFunctionExit(const DebugContext &ctx) = 0;
104};
105
106// ============================================================
107// BreakpointManager — tracks line breakpoints
108// ============================================================
110{
111public:
113 {
114 int id = 0;
115 uint16_t line = 0;
116 bool enabled = true;
117 std::string condition; // empty = unconditional; else a boolean expr
118 };
119
120 int addBreakpoint(uint16_t line);
121 int addBreakpoint(uint16_t line, const std::string &condition);
122 void removeBreakpoint(int id);
123 void enableBreakpoint(int id, bool enabled);
124 void clearAll();
125 bool shouldBreak(uint16_t line) const;
126 // Condition text of the enabled breakpoint at `line` (empty if none or
127 // unconditional). The manager only stores it; the debugger evaluates it.
128 std::string conditionForLine(uint16_t line) const;
129 const std::vector<Breakpoint> &breakpoints() const { return breakpoints_; }
130
131private:
132 std::vector<Breakpoint> breakpoints_;
133 int nextId_ = 1;
134 mutable std::unordered_map<uint16_t, size_t> lineIndex_;
135 mutable bool indexDirty_ = true;
136 void rebuildIndex() const;
137};
138
139// ============================================================
140// DebugController — shared step/breakpoint logic
141//
142// Both VM and TW call checkLine() on line change.
143// Owns the call stack and step state.
144// No backend-specific code.
145//
146// Lifetime: observer and bpm must outlive the controller.
147// Do not call Engine::setDebugObserver() during execution.
148// ============================================================
149class DebugController
150{
151public:
152 explicit DebugController(DebugObserver *observer, BreakpointManager *bpm)
153 : observer_(observer)
154 , bpm_(bpm)
155 {}
156
157 // Reset for new execution.
158 // Default StepInto: observer's onLine() fires on the first source line,
159 // giving it a chance to choose the execution mode (Continue, StepOver, etc).
160 // Pass Continue to skip the initial stop (breakpoints still work).
161 void reset(DebugAction initialAction = DebugAction::StepInto);
162
163 // Called by backend on source line change.
164 // Returns false if Stop requested.
165 bool checkLine(uint16_t line, uint16_t col, int callDepth);
166
167 // Call stack management
168 void pushFrame(StackFrame frame);
169 void popFrame();
170
171 // RAII guard: pushes frame on construction, pops on destruction (even on exception).
172 // Movable (for std::optional), not copyable.
173 class FrameGuard
174 {
175 public:
176 FrameGuard(DebugController &ctl, StackFrame frame)
177 : ctl_(&ctl)
178 {
179 ctl_->pushFrame(std::move(frame));
180 }
181 ~FrameGuard()
182 {
183 if (ctl_)
184 ctl_->popFrame();
185 }
186 FrameGuard(FrameGuard &&o) noexcept
187 : ctl_(o.ctl_)
188 {
189 o.ctl_ = nullptr;
190 }
191 FrameGuard &operator=(FrameGuard &&) = delete;
192 FrameGuard(const FrameGuard &) = delete;
193 FrameGuard &operator=(const FrameGuard &) = delete;
194
195 private:
196 DebugController *ctl_;
197 };
198
199 std::vector<StackFrame> &callStack() { return callStack_; }
200 const std::vector<StackFrame> &callStack() const { return callStack_; }
201 StackFrame *currentFrame() { return callStack_.empty() ? nullptr : &callStack_.back(); }
202
203 DebugContext makeContext(uint16_t line, uint16_t col) const;
204 DebugContext makeContext() const;
205
206 // Set action for resume after pause (called before re-entering dispatch loop)
207 void setResumeAction(DebugAction action, int callDepth);
208
209 // Restore lastLine_ (used after debug eval to avoid re-triggering on the same line)
210 void setLastLine(uint16_t line) { lastLine_ = line; }
211
212private:
213 DebugObserver *observer_;
214 BreakpointManager *bpm_;
215 std::vector<StackFrame> callStack_;
216 DebugAction stepAction_ = DebugAction::Continue;
217 int stepDepth_ = 0;
218 uint16_t lastLine_ = 0;
219
220 void applyAction(DebugAction action, int callDepth);
221};
222
223// ============================================================
224// DebugStopException
225// ============================================================
226class DebugStopException : public std::runtime_error
227{
228public:
229 DebugStopException()
230 : std::runtime_error("Execution stopped by debugger")
231 {}
232};
233
234} // namespace nk
Core MATLAB-compatible multidimensional array and scalar value type.
Definition value.hpp:61
Definition debugger.hpp:58