24#include <imgui_stdlib.h>
30using namespace std::literals;
36 , symbolManager(manager.getReactor().getSymbolManager())
52 if (!motherBoard || !
show)
return;
54 ImGui::SetNextWindowSize(
gl::vec2{25, 14} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
59 paintTab<BreakPoint>(cpuInterface);
62 paintTab<WatchPoint>(cpuInterface);
65 paintTab<DebugCondition>(cpuInterface);
71template<
typename T>
static std::string_view getCheckCmd();
80 auto end = cond.size();
81 std::optional<int> o_ps;
82 std::optional<int> o_ss;
83 std::optional<int> o_seg;
85 auto next = [&](std::string_view s) {
86 if (cond.substr(pos).starts_with(s)) {
93 bool stop = cond.substr(pos) ==
"]";
94 if (stop || (next(
"] && (") && cond.ends_with(
')'))) {
98 rest = cond.substr(pos, cond.size() - pos - 1);
100 if (o_ps) {
hasPs =
true;
ps = *o_ps; }
101 if (o_ss) {
hasSs =
true;
ss = *o_ss; }
102 if (o_seg) {
hasSeg =
true;
seg = narrow<uint8_t>(*o_seg); }
107 auto isDigit = [](
char c) {
return (
'0' <= c) && (c <=
'9'); };
108 auto getInt = [&](
unsigned max) -> std::optional<int> {
110 if ((pos ==
end) || !isDigit(cond[pos]))
return {};
111 while ((pos !=
end) && isDigit(cond[pos])) {
112 i = 10 * i + (cond[pos] -
'0');
115 if (i >= max)
return {};
119 if (!next(
tmpStrCat(
'[', checkCmd,
' ')))
return;
123 if (!next(
" "))
return;
129 if (!next(
" "))
return;
139 std::string result =
strCat(
'[', checkCmd,
' ',
ps);
158 std::string result =
strCat(
"Slot:",
ps,
'-');
179template<
typename Item>
struct HasAddress : std::true_type {};
189static std::vector<std::shared_ptr<WatchPoint>>& getItems(WatchPoint*, MSXCPUInterface& cpuInterface)
191 return cpuInterface.getWatchPoints();
193static std::vector<DebugCondition>& getItems(DebugCondition*, MSXCPUInterface&)
198template<
typename T>
static void createNew(MSXCPUInterface& cpuInterface, Interpreter& interp, std::optional<uint16_t> addr);
216template<
typename T>
static void remove(MSXCPUInterface& cpuInterface,
unsigned id);
227[[nodiscard]]
static unsigned getId(
const BreakPoint& bp) {
return bp.getId(); }
228[[nodiscard]]
static unsigned getId(
const std::shared_ptr<WatchPoint>& wp) {
return wp->getId(); }
229[[nodiscard]]
static unsigned getId(
const DebugCondition& cond) {
return cond.getId(); }
231[[nodiscard]]
static bool getEnabled(
const BreakPoint& bp) {
return bp.isEnabled(); }
232[[nodiscard]]
static bool getEnabled(
const std::shared_ptr<WatchPoint>& wp) {
return wp->isEnabled(); }
233[[nodiscard]]
static bool getEnabled(
const DebugCondition& cond) {
return cond.isEnabled(); }
234static void setEnabled(BreakPoint& bp,
bool e) { bp.setEnabled(e); }
235static void setEnabled(std::shared_ptr<WatchPoint>& wp,
bool e) { wp->setEnabled(e); }
236static void setEnabled(DebugCondition& cond,
bool e) { cond.setEnabled(e); }
238[[nodiscard]]
static std::optional<uint16_t> getAddress(
const BreakPoint& bp) {
return bp.getAddress(); }
239[[nodiscard]]
static std::optional<uint16_t> getAddress(
const std::shared_ptr<WatchPoint>& wp) {
return wp->getBeginAddress(); }
240[[nodiscard]]
static std::optional<uint16_t> getAddress(
const DebugCondition&) {
return {}; }
242[[nodiscard]]
static TclObject getAddressString(
const BreakPoint& bp) {
return bp.getAddressString(); }
243[[nodiscard]]
static TclObject getAddressString(
const std::shared_ptr<WatchPoint>& wp) {
return wp->getBeginAddressString(); }
244[[nodiscard]]
static TclObject getAddressString(
const DebugCondition&) {
return {}; }
246[[nodiscard]]
static TclObject getEndAddressString(
const BreakPoint&) {
return {}; }
247[[nodiscard]]
static TclObject getEndAddressString(
const std::shared_ptr<WatchPoint>& wp) {
return wp->getEndAddressString(); }
249[[nodiscard]]
static TclObject getCondition(
const BreakPoint& bp) {
return bp.getCondition(); }
250[[nodiscard]]
static TclObject getCondition(
const std::shared_ptr<WatchPoint>& wp) {
return wp->getCondition(); }
251[[nodiscard]]
static TclObject getCondition(
const DebugCondition& cond) {
return cond.getCondition(); }
252static void setCondition(BreakPoint& bp,
const TclObject& c) { bp.setCondition(c); }
253static void setCondition(std::shared_ptr<WatchPoint>& wp,
const TclObject& c) { wp->setCondition(c); }
254static void setCondition(DebugCondition& cond,
const TclObject& c) { cond.setCondition(c); }
256[[nodiscard]]
static TclObject getCommand(
const BreakPoint& bp) {
return bp.getCommand(); }
257[[nodiscard]]
static TclObject getCommand(
const std::shared_ptr<WatchPoint>& wp) {
return wp->getCommand(); }
258[[nodiscard]]
static TclObject getCommand(
const DebugCondition& cond) {
return cond.getCommand(); }
259static void setCommand(BreakPoint& bp,
const TclObject& c) { bp.setCommand(c); }
260static void setCommand(std::shared_ptr<WatchPoint>& wp,
const TclObject& c) { wp->setCommand(c); }
261static void setCommand(DebugCondition& cond,
const TclObject& c) { cond.setCommand(c); }
263[[nodiscard]]
static bool getOnce(
const BreakPoint& bp) {
return bp.onlyOnce(); }
264[[nodiscard]]
static bool getOnce(
const std::shared_ptr<WatchPoint>& wp) {
return wp->onlyOnce(); }
265[[nodiscard]]
static bool getOnce(
const DebugCondition& cond) {
return cond.onlyOnce(); }
266static void setOnce(BreakPoint& bp,
bool o) { bp.setOnce(o); }
267static void setOnce(std::shared_ptr<WatchPoint>& wp,
bool o) { wp->setOnce(o); }
268static void setOnce(DebugCondition& cond,
bool o) { cond.setOnce(o); }
272[[nodiscard]]
static auto getScopedChange(std::shared_ptr<WatchPoint>& wp, MSXCPUInterface& cpuInterface) {
273 return cpuInterface.getScopedChangeWatchpoint(wp);
275[[nodiscard]]
static DummyScopedChange getScopedChange(DebugCondition&, MSXCPUInterface&) {
return {}; }
277template<
typename Item>
278static void checkSort(std::vector<Item>& items)
280 using Type = BaseBpType_t<Item>;
281 constexpr bool isWatchPoint = std::is_same_v<Type, WatchPoint>;
282 constexpr bool hasAddress = HasAddress<Type>{};
284 auto* sortSpecs = ImGui::TableGetSortSpecs();
285 if (!sortSpecs->SpecsDirty)
return;
287 sortSpecs->SpecsDirty =
false;
288 assert(sortSpecs->SpecsCount == 1);
289 assert(sortSpecs->Specs);
290 assert(sortSpecs->Specs->SortOrder == 0);
292 switch (sortSpecs->Specs->ColumnIndex) {
294 sortUpDown_T(items, sortSpecs, [](
const auto& item) {
return getEnabled(item); });
297 if constexpr (isWatchPoint) {
302 if constexpr (hasAddress) {
304 return std::tuple(getAddressString (item).getString(),
305 getEndAddressString(item).getString());
320template<
typename Type>
321[[nodiscard]]
static std::string isValidCond(std::string_view cond, Interpreter& interp)
324 return AllowEmptyCond<Type>{} ? std::string{}
325 : std::string(
"cannot be empty");
327 return interp.parseExpressionError(cond);
330[[nodiscard]]
static std::string isValidCmd(std::string_view cmd, Interpreter& interp)
332 if (cmd.empty())
return {};
333 return interp.parseCommandError(cmd);
336std::string ImGuiBreakPoints::displayAddr(
const TclObject& addr)
const
338 auto str = addr.getString();
339 if (
auto symbol = [&]() -> std::optional<std::string_view> {
340 if (str.ends_with(
')')) {
341 if (str.starts_with(
"$sym(")) return str.substr(5, str.size() - 6);
342 if (str.starts_with(
"$::sym(")) return str.substr(7, str.size() - 8);
347 return std::string(*symbol);
350 return std::string(str);
353std::string ImGuiBreakPoints::parseDisplayAddress(std::string_view str)
const
356 return strCat(
"$sym(", str,
')');
358 return std::string(str);
361template<
typename Item>
362void ImGuiBreakPoints::drawRow(MSXCPUInterface& cpuInterface,
int row, Item& item)
364 using Type = BaseBpType_t<Item>;
365 constexpr bool isWatchPoint = std::is_same_v<Type, WatchPoint>;
366 constexpr bool isBreakPoint = std::is_same_v<Type, BreakPoint>;
369 const auto& style = ImGui::GetStyle();
370 float rowHeight = 2.0f * style.FramePadding.y + ImGui::GetTextLineHeight();
372 auto setRedBg = [](
bool valid) {
377 if (ImGui::TableNextColumn()) {
378 auto pos = ImGui::GetCursorPos();
379 if (ImGui::Selectable(
"##selection", selectedRow == row,
380 ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap,
381 {0.0f, rowHeight})) {
384 ImGui::SetCursorPos(pos);
386 bool enabled = getEnabled(item);
387 if (ImGui::Checkbox(
"##enabled", &enabled)) {
388 auto sc = getScopedChange(item, cpuInterface); (void)sc;
389 setEnabled(item, enabled);
391 if (ImGui::IsItemActive()) selectedRow = row;
393 if (ImGui::TableNextColumn()) {
394 if constexpr (isWatchPoint) {
395 ImGui::SetNextItemWidth(-FLT_MIN);
396 int wpType =
static_cast<int>(item->getType());
397 if (ImGui::Combo(
"##type", &wpType,
"read IO\000write IO\000read memory\000write memory\000")) {
398 auto sc = getScopedChange(item, cpuInterface); (void)sc;
401 if (ImGui::IsItemActive()) selectedRow = row;
404 if (ImGui::TableNextColumn()) {
405 std::string addrStr = displayAddr(getAddressString(item));
406 ImGui::SetNextItemWidth(-FLT_MIN);
407 if constexpr (isWatchPoint) {
408 std::string parseError = item->parseAddressError(interp);
409 setRedBg(parseError.empty());
411 auto pos = ImGui::GetCursorPos();
412 std::string endAddrStr = displayAddr(item->getEndAddressString());
413 std::string displayAddr = addrStr;
414 if (!endAddrStr.empty()) {
415 strAppend(displayAddr,
"...", endAddrStr);
418 ImGui::TextUnformatted(displayAddr);
420 ImGui::SetCursorPos(pos);
421 if (ImGui::InvisibleButton(
"##range-button", {-FLT_MIN, rowHeight})) {
422 ImGui::OpenPopup(
"range-popup");
425 auto addr = item->getBeginAddress();
426 if (parseError.empty() && addr) {
428 auto tip =
strCat(
"0x", hex_string<4>(*addr));
429 if (
auto endAddr = item->getEndAddress(); endAddr && *endAddr != *addr) {
430 strAppend(tip,
"...0x", hex_string<4>(*endAddr));
438 if (ImGui::IsItemActive()) selectedRow = row;
440 editRange(cpuInterface, item, addrStr, endAddrStr);
442 }
else if constexpr (isBreakPoint) {
443 std::string parseError = item.parseAddressError(interp);
444 setRedBg(parseError.empty());
447 if (ImGui::InputText(
"##addr", &addrStr)) {
448 auto sc = getScopedChange(item, cpuInterface); (void)sc;
449 item.setAddress(interp, TclObject(parseDisplayAddress(addrStr)));
453 auto addr = item.getAddress();
454 if (parseError.empty() && addr) {
461 if (ImGui::MenuItem(
"Show in Disassembly",
nullptr,
nullptr, addr.has_value())) {
462 manager.debugger->setGotoTarget(*addr);
466 if (ImGui::IsItemActive()) selectedRow = row;
469 if (ImGui::TableNextColumn()) {
470 std::string cond{getCondition(item).
getString()};
471 std::string parseError = isValidCond<Type>(cond, interp);
472 setRedBg(parseError.empty());
473 auto checkCmd = getCheckCmd<Type>();
474 ParsedSlotCond slot(checkCmd, cond);
475 auto pos = ImGui::GetCursorPos();
477 ImGui::TextUnformatted(slot.toDisplayString());
479 ImGui::SetCursorPos(pos);
480 if (ImGui::InvisibleButton(
"##cond-button", {-FLT_MIN, rowHeight})) {
481 ImGui::OpenPopup(
"cond-popup");
483 if (ImGui::IsItemActive()) selectedRow = row;
486 if (editCondition(slot)) {
487 cond = slot.toTclExpression(checkCmd);
488 setCondition(item, TclObject(cond));
492 if (ImGui::TableNextColumn()) {
493 std::string cmd{getCommand(item).
getString()};
494 std::string parseError = isValidCmd(cmd, interp);
495 setRedBg(parseError.empty());
497 ImGui::SetNextItemWidth(-FLT_MIN);
498 if (ImGui::InputText(
"##cmd", &cmd)) {
499 setCommand(item, TclObject(cmd));
501 if (ImGui::IsItemActive()) selectedRow = row;
505 if (ImGui::TableNextColumn()) {
506 bool once = getOnce(item);
507 if (ImGui::Checkbox(
"##once", &once)) {
510 if (ImGui::IsItemActive()) selectedRow = row;
516 paintTab<BreakPoint>(cpuInterface, addr);
519template<
typename Type>
520void ImGuiBreakPoints::paintTab(
MSXCPUInterface& cpuInterface, std::optional<uint16_t> addr)
522 constexpr bool isWatchPoint = std::is_same_v<Type, WatchPoint>;
523 constexpr bool isCondition = std::is_same_v<Type, DebugCondition>;
526 auto& items = getItems(tag, cpuInterface);
528 int flags = ImGuiTableFlags_RowBg |
529 ImGuiTableFlags_BordersV |
530 ImGuiTableFlags_BordersOuter |
531 ImGuiTableFlags_Resizable |
532 ImGuiTableFlags_Sortable |
533 ImGuiTableFlags_Hideable |
534 ImGuiTableFlags_Reorderable |
535 ImGuiTableFlags_ContextMenuInBody |
536 ImGuiTableFlags_SizingStretchProp;
538 flags |= ImGuiTableFlags_ScrollY
539 | ImGuiTableFlags_ScrollX;
541 const auto& style = ImGui::GetStyle();
542 auto width = style.ItemSpacing.x + 2.0f * style.FramePadding.x +
ImGui::CalcTextSize(
"Remove").x;
543 bool disableRemove =
true;
545 int lastDrawnRow = -1;
546 im::Table(
"items", 6, flags, {-width, 0}, [&]{
547 ImGui::TableSetupScrollFreeze(0, 1);
548 ImGui::TableSetupColumn(
"Enable", ImGuiTableColumnFlags_WidthFixed);
549 int typeFlags = isWatchPoint ? ImGuiTableColumnFlags_NoHide : ImGuiTableColumnFlags_Disabled;
550 ImGui::TableSetupColumn(
"Type", typeFlags);
551 int addressFlags = hasAddress ? ImGuiTableColumnFlags_NoHide | ImGuiTableColumnFlags_DefaultSort
552 : ImGuiTableColumnFlags_Disabled;
553 ImGui::TableSetupColumn(
"Address", addressFlags);
554 ImGui::TableSetupColumn(
"Condition", isCondition ? ImGuiTableColumnFlags_NoHide : 0);
555 ImGui::TableSetupColumn(
"Action", addr ? 0 : ImGuiTableColumnFlags_DefaultHide);
556 ImGui::TableSetupColumn(
"Once", ImGuiTableColumnFlags_DefaultHide);
557 ImGui::TableHeadersRow();
562 if (!addr || (getAddress(items[row]) == addr)) {
563 ++count; lastDrawnRow = row;
564 if (row == selectedRow) disableRemove = false;
565 drawRow(cpuInterface, row, items[row]);
569 if (count == 1) disableRemove =
false;
572 if (ImGui::Button(
"Add")) {
573 createNew<Type>(cpuInterface, manager.getInterpreter(), addr);
577 if (ImGui::Button(
"Remove")) {
578 int removeRow = (
count == 1) ? lastDrawnRow : selectedRow;
579 auto it = items.begin() + removeRow;
580 remove<Type>(cpuInterface, getId(*it));
587 if (ImGui::Button(
"Clear")) {
588 while (!items.empty()) {
589 remove<Type>(cpuInterface, getId(items.back()));
597void ImGuiBreakPoints::editRange(MSXCPUInterface& cpuInterface, std::shared_ptr<WatchPoint>& wp, std::string&
begin, std::string&
end)
601 const auto& style = ImGui::GetStyle();
602 auto pos = ImGui::GetCursorPos().x +
ImGui::CalcTextSize(
"end: (?)").x + 2.0f * style.ItemSpacing.x;
604 ImGui::AlignTextToFramePadding();
606 ImGui::SameLine(pos);
608 if (ImGui::InputText(
"##begin", &begin)) {
609 auto& interp = manager.getInterpreter();
610 auto sc = getScopedChange(wp, cpuInterface); (void)sc;
611 wp->setBeginAddressString(interp, TclObject(parseDisplayAddress(begin)));
615 ImGui::AlignTextToFramePadding();
617 HelpMarker(
"End address is included in the range.\n"
618 "Leave empty for a single address.");
619 ImGui::SameLine(pos);
621 if (ImGui::InputText(
"##end", &end)) {
622 auto& interp = manager.getInterpreter();
623 auto sc = getScopedChange(wp, cpuInterface); (void)sc;
624 wp->setEndAddressString(interp, TclObject(parseDisplayAddress(end)));
630bool ImGuiBreakPoints::editCondition(ParsedSlotCond& slot)
632 bool changed =
false;
635 const auto& style = ImGui::GetStyle();
636 auto pos = ImGui::GetCursorPos().x + ImGui::GetFrameHeight() +
639 changed |= ImGui::Checkbox(
"primary", &slot.hasPs);
640 ImGui::SameLine(pos);
642 changed |= ImGui::Combo(
"##ps", &slot.ps,
"0\0001\0002\0003\000");
644 changed |= ImGui::Checkbox(
"secondary", &slot.hasSs);
645 ImGui::SameLine(pos);
646 im::Disabled(!slot.hasSs, [&]{
647 changed |= ImGui::Combo(
"##ss", &slot.ss,
"0\0001\0002\0003\000");
650 changed |= ImGui::Checkbox(
"segment", &slot.hasSeg);
651 ImGui::SameLine(pos);
654 changed |= ImGui::InputScalar(
"##seg", ImGuiDataType_U8, &slot.seg, &one);
661 ImGui::SetNextItemWidth(-FLT_MIN);
662 changed |= ImGui::InputText(
"##cond", &slot.rest);
668void ImGuiBreakPoints::refreshSymbols()
670 auto& interp = manager.getInterpreter();
671 for (
auto& bp : MSXCPUInterface::getBreakPoints()) {
672 bp.evaluateAddress(interp);
674 if (
auto* motherBoard = manager.getReactor().getMotherBoard()) {
675 auto& cpuInterface = motherBoard->getCPUInterface();
676 for (
auto& wp : cpuInterface.getWatchPoints()) {
677 auto sc = getScopedChange(wp, cpuInterface);
678 wp->evaluateAddress(interp);
void setAddress(Interpreter &interp, const TclObject &addr)
General debugger condition Like breakpoints, but not tied to a specific address.
ImGuiBreakPoints(ImGuiManager &manager)
void save(ImGuiTextBuffer &buf) override
void loadLine(std::string_view name, zstring_view value) override
void paint(MSXMotherBoard *motherBoard) override
Interpreter & getInterpreter()
void setWatchPoint(const std::shared_ptr< WatchPoint > &watchPoint)
void insertBreakPoint(BreakPoint bp)
void removeCondition(const DebugCondition &cond)
static BreakPoints & getBreakPoints()
void removeWatchPoint(std::shared_ptr< WatchPoint > watchPoint)
void setCondition(DebugCondition cond)
void removeBreakPoint(const BreakPoint &bp)
static Conditions & getConditions()
MSXCPUInterface & getCPUInterface()
std::optional< uint16_t > lookupSymbol(std::string_view s) const
zstring_view getString() const
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
auto CalcTextSize(std::string_view str)
void TextUnformatted(const std::string &str)
void Table(const char *str_id, int column, ImGuiTableFlags flags, const ImVec2 &outer_size, float inner_width, std::invocable<> auto next)
void Window(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
void PopupContextItem(const char *str_id, ImGuiPopupFlags popup_flags, std::invocable<> auto next)
void TabBar(const char *str_id, ImGuiTabBarFlags flags, std::invocable<> auto next)
void Disabled(bool b, std::invocable<> auto next)
void Group(std::invocable<> auto next)
void Font(ImFont *font, std::invocable<> auto next)
void TabItem(const char *label, bool *p_open, ImGuiTabItemFlags flags, std::invocable<> auto next)
void Indent(float indent_w, std::invocable<> auto next)
void Popup(const char *str_id, ImGuiWindowFlags flags, std::invocable<> auto next)
void ID_for_range(std::integral auto count, std::invocable< int > auto next)
This file implemented 3 utility functions:
void remove< BreakPoint >(MSXCPUInterface &cpuInterface, unsigned id)
void createNew< BreakPoint >(MSXCPUInterface &cpuInterface, Interpreter &interp, std::optional< uint16_t > addr)
bool loadOnePersistent(std::string_view name, zstring_view value, C &c, const std::tuple< Elements... > &tup)
std::string_view getCheckCmd< BreakPoint >()
void sortUpDown_String(Range &range, const ImGuiTableSortSpecs *sortSpecs, Projection proj)
void simpleToolTip(std::string_view desc)
void createNew< DebugCondition >(MSXCPUInterface &cpuInterface, Interpreter &, std::optional< uint16_t >)
void createNew< WatchPoint >(MSXCPUInterface &cpuInterface, Interpreter &, std::optional< uint16_t >)
void savePersistent(ImGuiTextBuffer &buf, C &c, const std::tuple< Elements... > &tup)
std::string_view getCheckCmd< WatchPoint >()
void remove< WatchPoint >(MSXCPUInterface &cpuInterface, unsigned id)
void sortUpDown_T(Range &range, const ImGuiTableSortSpecs *sortSpecs, Projection proj)
void HelpMarker(std::string_view desc)
ImU32 getColor(imColor col)
std::string_view getCheckCmd< DebugCondition >()
typename BaseBpType< T >::type BaseBpType_t
void remove< DebugCondition >(MSXCPUInterface &cpuInterface, unsigned id)
auto count(InputRange &&range, const T &value)
TemporaryString tmpStrCat(Ts &&... ts)
void strAppend(std::string &result, Ts &&...ts)
std::string toDisplayString() const
std::string toTclExpression(std::string_view checkCmd) const
ParsedSlotCond(std::string_view checkCmd, std::string_view cond)
constexpr auto begin(const zstring_view &x)
constexpr auto end(const zstring_view &x)