7#include <simplesquirrel/simplesquirrel.hpp>
18std::string truthyString(
bool v) {
return v ?
"true" :
"false"; }
21 const SQObjectType t = sq_gettype(
vm,
idx);
27 sq_getinteger(
vm,
idx, &
v);
28 return std::to_string(
static_cast<long long>(
v));
33 return std::to_string(
static_cast<double>(
v));
38 return v ?
"true" :
"false";
41 const SQChar*
s =
nullptr;
43 return s ? std::string(
"\"") +
s +
"\"" :
"\"\"";
53 case OT_NATIVECLOSURE:
67 switch (sq_gettype(
vm,
idx)) {
86 case OT_NATIVECLOSURE:
104 const SQObjectType t = sq_gettype(
vm,
idx);
105 info.type = typeName(
vm,
idx);
112 sq_getinteger(
vm,
idx, &
v);
113 info.value = std::to_string(
static_cast<long long>(
v));
119 info.value = std::to_string(
static_cast<double>(
v));
125 info.value =
v ?
"true" :
"false";
129 const SQChar*
s =
nullptr;
130 sq_getstring(
vm,
idx, &
s);
131 info.value = std::string(
"\"") + (
s ?
s :
"") +
"\"";
136 const SQInteger
n = sq_getsize(
vm,
idx);
137 info.value = std::string(t == OT_TABLE ?
"<table (" :
"<array (") +
138 std::to_string(static_cast<long long>(
n)) +
")>";
139 info.expandable =
true;
140 info.childCount =
static_cast<int>(
n);
144 info.value =
"<class>";
145 info.expandable =
true;
148 info.value =
"<instance>";
149 info.expandable =
true;
152 info.value =
"<userdata>";
155 info.value =
"<closure>";
156 info.expandable =
true;
158 case OT_NATIVECLOSURE:
159 info.value =
"<native>";
162 info.value =
"<thread>";
165 info.value =
"<generator>";
168 info.value =
"<other>";
176 std::vector<VariableInfo> out;
177 const SQObjectType t = sq_gettype(
vm,
idx);
178 const SQInteger top = sq_gettop(
vm);
179 const int absIdx = idx < 0 ? top + static_cast<int>(
idx) + 1 :
static_cast<int>(
idx);
182 const SQInteger size = sq_getsize(
vm,
idx);
183 for (SQInteger i = 0; i < size; ++i) {
184 const SQInteger before = sq_gettop(
vm);
185 sq_pushinteger(
vm, i);
186 if (SQ_FAILED(sq_get(
vm, absIdx))) {
187 sq_settop(
vm, before);
190 VariableInfo info = describeAt(
vm, -1);
191 info.name = std::to_string(
static_cast<long long>(i));
192 sq_settop(
vm, before);
193 out.push_back(std::move(info));
198 if (t == OT_TABLE || t == OT_CLASS) {
199 sq_pushinteger(
vm, 0);
200 while (SQ_SUCCEEDED(sq_next(
vm, absIdx))) {
201 VariableInfo info = describeAt(
vm, -1);
202 switch (sq_gettype(
vm, -2)) {
204 const SQChar*
s =
nullptr;
205 sq_getstring(
vm, -2, &
s);
206 info.name =
s ?
s :
"";
211 sq_getinteger(
vm, -2, &k);
212 info.name = std::to_string(
static_cast<long long>(k));
217 sq_getfloat(
vm, -2, &k);
218 info.name = std::to_string(
static_cast<double>(k));
223 sq_getbool(
vm, -2, &k);
224 info.name = truthyString(k != SQFalse);
232 out.push_back(std::move(info));
238 if (t == OT_INSTANCE) {
239 sq_getclass(
vm, absIdx);
240 const int clsAbs = sq_gettop(
vm);
241 sq_pushinteger(
vm, 0);
242 while (SQ_SUCCEEDED(sq_next(
vm, clsAbs))) {
243 const SQChar*
s =
nullptr;
244 if (sq_gettype(
vm, -2) == OT_STRING) sq_getstring(
vm, -2, &
s);
245 const std::string
name =
s ?
s :
"";
246 const SQInteger before = sq_gettop(
vm);
248 sq_pushstring(
vm,
name.c_str(), -1);
250 if (SQ_SUCCEEDED(sq_get(
vm, -2))) {
251 info = describeAt(
vm, -1);
254 info.value =
"not found";
257 sq_settop(
vm, before);
259 out.push_back(std::move(info));
265 if (t == OT_CLOSURE) {
266 SQInteger nparams = 0, nfree = 0;
267 if (SQ_SUCCEEDED(sq_getclosureinfo(
vm,
idx, &nparams, &nfree))) {
268 for (SQUnsignedInteger i = 0; i < static_cast<SQUnsignedInteger>(nfree); ++i) {
269 const SQChar*
n = sq_getfreevariable(
vm, absIdx, i);
270 VariableInfo info = describeAt(
vm, -1);
271 info.name =
n ?
n : (
"upvalue_" + std::to_string(
static_cast<long long>(i)));
273 out.push_back(std::move(info));
281bool valueTruthy(
const VariableInfo& info) {
282 if (info.type ==
"error")
return false;
283 if (info.type ==
"null")
return false;
284 if (info.type ==
"bool")
return info.value ==
"true";
285 if (info.type ==
"integer") {
287 return std::stoll(info.value) != 0;
292 if (info.type ==
"float") {
294 return std::stod(info.value) != 0.0;
310 if (source.empty())
return source;
312 if (source.rfind(
"file://", 0) == 0) {
313 source = source.substr(7);
315 if (source.rfind(
"localhost/", 0) == 0) source = source.substr(9);
318 for (
char&
c : source) {
319 if (
c ==
'\\')
c =
'/';
322 while (source.size() >= 2 && source[0] ==
'.' && source[1] ==
'/') source = source.substr(2);
328 const auto slash = norm.find_last_of(
'/');
329 return (slash == std::string::npos) ? norm : norm.substr(slash + 1);
335 if (na.empty() || nb.empty())
return false;
336 if (na == nb)
return true;
339 if (!ba.empty() && ba == bb)
return true;
341 const std::string& longer = na.size() >= nb.size() ? na : nb;
342 const std::string& shorter = na.size() >= nb.size() ? nb : na;
343 if (longer.size() > shorter.size() &&
344 longer.compare(longer.size() - shorter.size(), shorter.size(), shorter) == 0) {
345 const auto idx = longer.size() - shorter.size();
346 return idx == 0 || longer[
idx - 1] ==
'/';
352 std::lock_guard<std::mutex> lock(mu_);
357 stepFrameArmed_ =
false;
361 std::lock_guard<std::mutex> lock(mu_);
365 stepFrameArmed_ =
false;
369 reason_.store(reason);
379 stepFrameArmed_ =
false;
388 stepFrameArmed_ =
true;
395 for (
int level = 0;; ++level) {
397 if (SQ_FAILED(sq_stackinfos(
vm, level, &si)))
break;
403void Debugger::beginScriptStep(
RunMode stepMode) {
408 stepSkipLoc_ = pauseLoc_;
411 if (stepStartDepth_ <= 0) {
416 mode_.store(stepMode);
435 if (!pauseLoc_.
empty() &&
455 stepFrameArmed_ =
false;
463bool Debugger::matchBreakpoint(
const std::string& source,
int line)
const {
464 if (!bpsEnabled_.load())
return false;
465 for (
const auto& bp : bps_) {
466 if (!bp.enabled || bp.line !=
line)
continue;
472bool Debugger::matchBreakpointAny(
const std::string& source,
int line)
const {
473 for (
const auto& bp : bps_) {
474 if (bp.line !=
line)
continue;
480bool Debugger::conditionHolds(
const Breakpoint& bp)
const {
481 if (bp.condition.empty())
return true;
483 const VariableInfo r =
evaluate(bp.condition, 0);
484 if (r.type ==
"error")
return true;
485 return valueTruthy(r);
496 std::vector<int> verifiedNow;
498 std::lock_guard<std::mutex> lock(mu_);
499 if (!bps_.empty() && matchBreakpointAny(loc.
source, loc.
line)) {
500 for (
auto& bp : bps_) {
501 if (bp.verified || bp.line != loc.
line ||
505 verifiedNow.push_back(bp.id);
509 for (
const int id : verifiedNow) {
510 if (bpEventFn_) bpEventFn_(
id, loc.
source, loc.
line,
true);
513 const bool stepping =
518 if (stepping && !stepSkipLoc_.
empty() && stepSkipLoc_.
line == loc.
line &&
527 std::lock_guard<std::mutex> lock(mu_);
528 if (bpsEnabled_.load()) {
529 for (
const auto& bp : bps_) {
530 if (!bp.enabled || bp.line != loc.
line ||
540 if (!conditionHolds(matched))
return false;
556 stop =
depth <= stepStartDepth_;
558 stop =
depth < stepStartDepth_;
575 else if (pump_) pump_();
576 std::this_thread::sleep_for(std::chrono::milliseconds(5));
582 std::string condition) {
584 if (source.empty() ||
line <= 0)
return 0;
585 std::lock_guard<std::mutex> lock(mu_);
586 for (
auto& bp : bps_) {
589 if (!condition.empty()) bp.condition = std::move(condition);
594 bp.
source = std::move(source);
604 std::lock_guard<std::mutex> lock(mu_);
605 const auto before = bps_.size();
606 bps_.erase(std::remove_if(bps_.begin(), bps_.end(),
608 return bp.line == line && sourcesMatch(source, bp.source);
611 return bps_.size() != before;
615 std::lock_guard<std::mutex> lock(mu_);
616 if (source.empty()) {
620 bps_.erase(std::remove_if(bps_.begin(), bps_.end(),
621 [&](
const Breakpoint& bp) { return sourcesMatch(source, bp.source); }),
626 std::lock_guard<std::mutex> lock(mu_);
631 std::lock_guard<std::mutex> lock(mu_);
632 return matchBreakpoint(source,
line);
636 if (expression.empty())
return;
637 std::lock_guard<std::mutex> lock(mu_);
638 for (
const auto& e : watchExprs_) {
639 if (e == expression)
return;
641 watchExprs_.push_back(std::move(expression));
645 std::lock_guard<std::mutex> lock(mu_);
646 const auto before = watchExprs_.size();
647 watchExprs_.erase(std::remove(watchExprs_.begin(), watchExprs_.end(), expression),
649 return watchExprs_.size() != before;
653 std::lock_guard<std::mutex> lock(mu_);
659 std::lock_guard<std::mutex> lock(mu_);
664 std::vector<std::string> exprs;
666 std::lock_guard<std::mutex> lock(mu_);
669 std::vector<WatchEntry> cache;
670 cache.reserve(exprs.size());
671 for (
const auto& e : exprs) {
675 w.ok = !info.type.empty() && info.type !=
"error";
676 w.value = info.value;
677 cache.push_back(std::move(
w));
679 std::lock_guard<std::mutex> lock(mu_);
680 watchCache_ = std::move(cache);
688 info.
value =
"no vm";
691 for (SQUnsignedInteger
n = 0;; ++
n) {
692 const SQInteger top = sq_gettop(
vm);
693 const SQChar* lname = sq_getlocal(
vm, level,
n);
699 info.
value = describeSqValue(
vm, -1);
700 info.
type = typeName(
vm, -1);
707 info.
value =
"not found";
716 info.value =
"no vm";
719 const SQInteger top = sq_gettop(
vm);
720 sq_pushroottable(
vm);
721 sq_pushstring(
vm,
name.c_str(), -1);
722 if (SQ_SUCCEEDED(sq_get(
vm, -2))) {
723 info.value = describeSqValue(
vm, -1);
724 info.type = typeName(
vm, -1);
727 info.value =
"not found";
733VariableInfo Debugger::evaluateLegacy(
const std::string& expression)
const {
735 if (!
vm || expression.empty()) {
737 info.name = expression;
739 info.value =
"unavailable";
744 auto local = readLocal(
vm, 0, expression);
745 if (local.type !=
"error")
return local;
748 if (expression.find(
'.') != std::string::npos) {
750 info.name = expression;
751 const SQInteger top = sq_gettop(
vm);
752 sq_pushroottable(
vm);
755 while (start < expression.size()) {
756 size_t dot = expression.find(
'.', start);
757 if (dot == std::string::npos) dot = expression.size();
758 const std::string part = expression.substr(start, dot - start);
759 sq_pushstring(
vm, part.c_str(), -1);
760 if (SQ_FAILED(sq_get(
vm, -2))) {
769 info.value = describeSqValue(
vm, -1);
770 info.type = typeName(
vm, -1);
773 info.value =
"not found";
778 return readRoot(
vm, expression);
782 std::vector<VariableInfo> out;
785 const SQUnsignedInteger level =
static_cast<SQUnsignedInteger
>(stackLevel < 0 ? 0 : stackLevel);
786 for (SQUnsignedInteger
n = 0;; ++
n) {
787 const SQInteger top = sq_gettop(
vm);
788 const SQChar*
name = sq_getlocal(
vm, level,
n);
796 out.push_back(std::move(info));
802 std::vector<VariableInfo> out;
805 const SQInteger top = sq_gettop(
vm);
806 sq_pushroottable(
vm);
807 out = enumerateAt(
vm, -1);
812bool Debugger::pushLocalValue(
HSQUIRRELVM vm,
unsigned level,
const std::string&
name)
const {
813 for (SQUnsignedInteger
n = 0;; ++
n) {
814 const SQInteger top = sq_gettop(
vm);
815 const SQChar* lname = sq_getlocal(
vm, level,
n);
820 if (
name == lname)
return true;
826 const std::vector<std::string>& path)
const {
827 if (!
vm)
return false;
828 const SQInteger top = sq_gettop(
vm);
830 sq_pushroottable(
vm);
832 if (path.empty())
return false;
833 if (!pushLocalValue(
vm,
static_cast<unsigned>(frame < 0 ? 0 : frame), path[0]))
840 for (
size_t i = start; i < path.size(); ++i) {
841 const SQInteger before = sq_gettop(
vm);
842 const int curAbs = before;
843 sq_pushstring(
vm, path[i].c_str(), -1);
844 if (SQ_FAILED(sq_get(
vm, curAbs))) {
854 const std::vector<std::string>& path)
const {
862 const SQInteger top = sq_gettop(
vm);
863 if (!pushPathValue(
vm,
kind, frame, path)) {
867 std::vector<VariableInfo> out = enumerateAt(
vm, -1);
874 if (!
vm || expression.empty()) {
876 info.
name = expression;
878 info.
value =
"unavailable";
881 const SQInteger top = sq_gettop(
vm);
882 const int level = frameLevel < 0 ? 0 : frameLevel;
887 for (SQUnsignedInteger
n = 0;; ++
n) {
888 const SQInteger before = sq_gettop(
vm);
889 const SQChar* lname = sq_getlocal(
vm,
static_cast<SQUnsignedInteger
>(level),
n);
891 sq_settop(
vm, before);
895 sq_pushstring(
vm, lname, -1);
898 sq_newslot(
vm, -3, SQFalse);
900 sq_pushroottable(
vm);
901 if (SQ_FAILED(sq_setdelegate(
vm, -2))) {
903 return evaluateLegacy(expression);
906 const std::string src =
"return (" + expression +
");";
907 if (SQ_FAILED(sq_compilebuffer(
vm, src.c_str(),
static_cast<SQInteger
>(src.size()),
908 _SC(
"eval"), SQTrue))) {
912 info.
name = expression;
919 sq_setclosureroot(
vm, -2);
920 sq_pushroottable(
vm);
922 info.
name = expression;
923 if (SQ_SUCCEEDED(sq_call(
vm, 1, SQTrue, SQFalse))) {
924 info = describeAt(
vm, -1);
935 std::vector<StackFrameInfo> out;
938 if (maxFrames <= 0) maxFrames = 32;
939 for (
int level = 0; level <= maxFrames; ++level) {
941 if (SQ_FAILED(sq_stackinfos(
vm, level, &si)))
break;
944 if (si.source)
f.loc.source = si.source;
945 f.loc.line =
static_cast<int>(si.line);
947 f.loc.function = si.funcname;
948 f.name = si.funcname;
957 if (
f.loc.source ==
"NATIVE" || (
f.loc.source.empty() &&
f.loc.line <= 0))
continue;
958 out.push_back(std::move(
f));
struct SQVM * HSQUIRRELVM
Script + frame debugger: pause/step, breakpoints, watches.
bool hasBreakpoint(const std::string &source, int line) const
void waitWhilePaused(const std::function< void()> &pump={})
Block until resume/step/detach (processes external poll callbacks).
static std::string sourceBasename(const std::string &source)
Basename of a normalized path (empty-safe).
std::vector< VariableInfo > containerChildren(VarKind kind, int frame, const std::vector< std::string > &path) const
Children of a container variable. path is the key chain that locates the container from its root (loc...
void stepInto()
Enter calls: stop on the next script line at any depth.
void refreshWatches()
Re-evaluate all watches against current VM (paused preferred).
void notifyFrameDone()
After a frame when StepFrame was active → return to Paused.
void stepOut()
Finish current function: stop when stack depth drops.
bool onScriptLine(const SourceLoc &loc)
Called from Squirrel line debug hook. Returns true if execution should block (breakpoint / step).
std::vector< WatchEntry > watches() const
static std::string normalizeSource(std::string source)
Normalize source paths for breakpoint matching (basename fallback).
std::vector< VariableInfo > globals() const
Root-table slots (globals).
bool removeWatch(const std::string &expression)
std::vector< VariableInfo > locals(int stackLevel=0) const
Locals of the given call-stack level (0 = current script frame).
void stepOver()
Skip calls: stop on the next line at ≤ current stack depth.
void addWatch(std::string expression)
int scriptStackDepth() const
Current Squirrel call depth (1 = topmost script frame). 0 if none.
bool shouldRunUpdate()
Frame loop: true ⇒ call eve_update this frame. Consumes StepFrame.
void pause(PauseReason reason=PauseReason::PauseKey)
bool clearBreakpoint(std::string source, int line)
int setBreakpoint(std::string source, int line, bool enabled=true, std::string condition={})
static Debugger & instance()
std::vector< Breakpoint > breakpoints() const
void step()
Convenience: script stepOver when mid-hook; otherwise one game frame. Prefer stepInto/stepOver/stepOu...
static bool sourcesMatch(const std::string &a, const std::string &b)
True when two source paths refer to the same script file. Matches exact path, basename,...
void attach(HSQUIRRELVM vm)
std::vector< StackFrameInfo > stackTrace(int maxFrames=32) const
VariableInfo evaluate(const std::string &expression, int frameLevel=0) const
Evaluate an expression in the given frame's scope. Understands plain names, a.b paths,...
void clearBreakpoints(const std::string &source={})
void notifyPending(const std::string &reason, const std::string &source, int line)
Record that a breakpoint / critical site wants a vision dump.
static RenderVision & instance()
VarKind
Where a variable tree node is rooted (used by containerChildren/resolvePath).
ScriptErrorContext captureCompileError(HSQUIRRELVM vm)
Captures the last compilation error recorded by the VM.
std::string formatScriptError(const ScriptErrorContext &ctx)
Formats a context into a human-readable multi-line report.
ScriptErrorContext takeLastScriptError(HSQUIRRELVM vm)
Consumes and clears the last recorded error for a VM.
Source location in a Squirrel (or synthetic) script.
Structured snapshot of a script error: message, throw site and stack.
bool empty() const noexcept
True when no error payload was captured.