diff --git a/libs/imgui-texteditor-fork/TextEditor.cpp b/libs/imgui-texteditor-fork/TextEditor.cpp index c3e794e..82704c9 100644 --- a/libs/imgui-texteditor-fork/TextEditor.cpp +++ b/libs/imgui-texteditor-fork/TextEditor.cpp @@ -1085,11 +1085,21 @@ void TextEditor::Render() // Render inline errors if (true) { for (auto &&inlineError: inlineErrors) { + if (inlineError.line < lineStart || inlineError.line > lineMax) continue; + ImVec2 lineStartScreenPos = ImVec2(cursorScreenPos.x, cursorScreenPos.y + inlineError.line * mCharAdvance.y); auto start = ImVec2(lineStartScreenPos.x + mTextStart + scrollX + (mCharAdvance.x * inlineError.charPos), lineStartScreenPos.y + mCharAdvance.y); auto end = ImVec2(lineStartScreenPos.x + mTextStart + scrollX + (mCharAdvance.x * inlineError.charEnd), lineStartScreenPos.y + mCharAdvance.y); drawList->AddLine(start, end, mPalette[(int) PaletteIndex::ErrorMarker], 2); } + + for (auto &&highlight: highlights) { + if (highlight.line < lineStart || highlight.line > lineMax) continue; + ImVec2 lineStartScreenPos = ImVec2(cursorScreenPos.x, cursorScreenPos.y + highlight.line * mCharAdvance.y); + auto start = ImVec2(lineStartScreenPos.x + mTextStart + scrollX + (mCharAdvance.x * highlight.charPos), lineStartScreenPos.y); + auto end = ImVec2(lineStartScreenPos.x + mTextStart + scrollX + (mCharAdvance.x * highlight.charEnd), lineStartScreenPos.y + mCharAdvance.y); + drawList->AddRectFilled(start, end, mPalette[(int)PaletteIndex::Selection]); + } } // Draw a tooltip on known identifiers/preprocessor symbols diff --git a/libs/imgui-texteditor-fork/TextEditor.h b/libs/imgui-texteditor-fork/TextEditor.h index 65b1593..6b63afe 100644 --- a/libs/imgui-texteditor-fork/TextEditor.h +++ b/libs/imgui-texteditor-fork/TextEditor.h @@ -155,6 +155,12 @@ class TextEditor void *data = nullptr; }; + struct Highlight { + int line; + int charPos; + int charEnd; + }; + struct LanguageDefinition { typedef std::pair TokenRegexString; @@ -274,6 +280,7 @@ class TextEditor static const Palette& GetRetroBluePalette(); std::vector inlineErrors; + std::vector highlights; std::function inlineErrorHover; private: typedef std::vector> RegexList; diff --git a/src/checker/checks.h b/src/checker/checks.h index c0fc1fd..af9a6b5 100644 --- a/src/checker/checks.h +++ b/src/checker/checks.h @@ -64,7 +64,8 @@ namespace ts::vm { } DiagnosticMessage errorMessage() { - auto [left, right] = stack.back(); +// auto [left, right] = stack.back(); + auto [left, right] = stack.front(); auto message = fmt::format("Type '{}' is not assignable to type '{}'", stringify(left), stringify(right)); return DiagnosticMessage(message, left->ip); @@ -187,6 +188,11 @@ namespace ts::vm { if (left->kind == TypeKind::Literal) return to(left)->type == TypeLiteralType::Number ? stack.valid() : stack.failed(); return stack.failed(); } + case TypeKind::Boolean: { + if (left->kind == TypeKind::Boolean) return stack.valid(); + if (left->kind == TypeKind::Literal) return to(left)->type == TypeLiteralType::Boolean ? stack.valid() : stack.failed(); + return stack.failed(); + } case TypeKind::Literal: { if (left->kind == TypeKind::Literal) { return to(left)->type == to(right)->type && to(left)->text() == to(right)->text() ? stack.valid() : stack.failed(); diff --git a/src/checker/compiler.h b/src/checker/compiler.h index ad5df61..cff6837 100644 --- a/src/checker/compiler.h +++ b/src/checker/compiler.h @@ -206,8 +206,10 @@ namespace ts::checker { } void pushError(ErrorCode code, const shared &node) { - pushOp(OP::Error, node); - pushUint16((unsigned int)code); + //errors need to be part of main + sourceMap.push(0, node->pos, node->end); + ops.push_back(OP::Error); + writeUint16(ops, ops.size(), (unsigned int)code); } void pushSymbolAddress(Symbol &symbol) { @@ -241,9 +243,9 @@ namespace ts::checker { ops.push_back(op); } - void pushOp(OP op, const shared &node) { + void pushOp(OP op, const sharedOpt &node) { auto &ops = getOPs(); - pushSourceMap(node); + if (node) pushSourceMap(node); ops.push_back(op); } @@ -431,6 +433,8 @@ namespace ts::checker { } break; } + case types::SyntaxKind::NeverKeyword: program.pushOp(OP::Never, node); + break; case types::SyntaxKind::BooleanKeyword: program.pushOp(OP::Boolean, node); break; case types::SyntaxKind::StringKeyword: program.pushOp(OP::String, node); @@ -514,7 +518,7 @@ namespace ts::checker { const auto name = to(n->typeName)->escapedText; auto symbol = program.findSymbol(name); if (!symbol) { - program.pushOp(OP::Never); + program.pushOp(OP::Never, n->typeName); program.pushError(ErrorCode::CannotFind, n->typeName); } else { if (symbol->type == SymbolType::TypeVariable) { @@ -675,7 +679,7 @@ namespace ts::checker { const auto n = to(node); auto symbol = program.findSymbol(n->escapedText); if (!symbol) { - program.pushOp(OP::Never); + program.pushOp(OP::Never, n); program.pushError(ErrorCode::CannotFind, n); } else { if (symbol->type == SymbolType::TypeVariable) { @@ -863,12 +867,12 @@ namespace ts::checker { //in the subroutine of the conditional type we place a new type variable, which acts as input. //the `Distribute` OP makes then sure that the current stack entry is used as input program.pushSymbol(distributiveOverIdentifier->escapedText, SymbolType::TypeVariable, distributiveOverIdentifier); - program.pushOp(instructions::TypeArgument); + program.pushOp(instructions::TypeArgument, distributiveOverIdentifier); } handle(n->checkType, program); handle(n->extendsType, program); - program.pushOp(instructions::Extends); + program.pushOp(instructions::Extends, n); auto trueProgram = program.pushSubroutineNameLess(n->trueType); handle(n->trueType, program); @@ -950,7 +954,7 @@ namespace ts::checker { const auto name = to(n->left)->escapedText; auto symbol = program.findSymbol(name); if (!symbol) { - program.pushOp(OP::Never); + program.pushOp(OP::Never, n->left); program.pushError(ErrorCode::CannotFind, n->left); } else { if (!symbol->routine) throw runtime_error("Symbol has no routine"); diff --git a/src/checker/debug.h b/src/checker/debug.h index 81f431a..88ade49 100644 --- a/src/checker/debug.h +++ b/src/checker/debug.h @@ -14,10 +14,15 @@ namespace ts::checker { return ops.substr(address + 2, size); } + struct PrintSubroutineOp { + string text; + unsigned int address; + }; + struct PrintSubroutine { string name; unsigned int address; - vector operations; + vector operations; }; struct DebugSourceMapEntry { @@ -176,16 +181,18 @@ namespace ts::checker { string text; if (params.empty()) { - text = fmt::format("[{}]{} ", startI, op); + text = fmt::format("{}", op); } else { - text = fmt::format("([{}]{}{}) ", startI, op, params); + text = fmt::format("{}{}", op, params); } if (result.activeSubroutine) { - result.activeSubroutine->operations.push_back(text); + result.activeSubroutine->operations.push_back({.text = text, .address = startI}); } else { result.operations.push_back(text); } - if (print) std::cout << text; + if (print) { + std::cout << "[" << startI << "] (" << text << ") "; + } } if (print) fmt::print("\n"); return result; diff --git a/src/checker/types.h b/src/checker/types.h index 15265af..f786fda 100644 --- a/src/checker/types.h +++ b/src/checker/types.h @@ -108,6 +108,10 @@ namespace ts::vm { struct FoundSourceMap { unsigned int pos; unsigned int end; + + bool found() { + return pos != 0 && end != 0; + } }; struct FoundSourceLineCharacter { @@ -144,6 +148,7 @@ namespace ts::vm { void clear() { errors.clear(); + subroutines.clear(); } ModuleSubroutine *getSubroutine(unsigned int index) { @@ -156,7 +161,7 @@ namespace ts::vm { string findIdentifier(unsigned int ip) { auto map = findNormalizedMap(ip); - if (map.end == 0) return ""; + if (!map.found()) return ""; return code.substr(map.pos, map.end - map.pos); } @@ -164,8 +169,12 @@ namespace ts::vm { unsigned int found = 0; for (unsigned int i = sourceMapAddress; i < sourceMapAddressEnd; i += 3 * 4) { auto mapIp = readUint32(bin, i); - if (mapIp > ip) break; - found = i; + if (mapIp == ip) { + found = i; + break; + } +// if (mapIp > ip) break; +// found = i; } if (found) { @@ -176,7 +185,7 @@ namespace ts::vm { FoundSourceMap findNormalizedMap(unsigned int ip) { auto map = findMap(ip); - omitWhitespace(code, map); + if (map.found()) omitWhitespace(code, map); return map; } @@ -205,18 +214,20 @@ namespace ts::vm { if (e.ip) { auto map = findNormalizedMap(e.ip); - std::size_t lineStart = code.rfind('\n', map.pos); - lineStart = lineStart == std::string::npos ? 0 : lineStart + 1; - - std::size_t lineEnd = code.find('\n', map.end); - if (lineEnd == std::string::npos) lineEnd = code.size(); - std::cout << cyan << fileName << ":" << yellow << map.pos << ":" << map.end << reset << " - " << red << "error" << reset << " TS0000: " << e.message << "\n\n"; - std::cout << code.substr(lineStart, lineEnd - lineStart - 1) << "\n"; - auto space = map.pos - lineStart; - std::cout << std::string(space, ' ') << red << "^" << reset << "\n\n"; - } else { - std::cout << " " << e.message << "\n"; + if (map.found()) { + std::size_t lineStart = code.rfind('\n', map.pos); + lineStart = lineStart == std::string::npos ? 0 : lineStart + 1; + + std::size_t lineEnd = code.find('\n', map.end); + if (lineEnd == std::string::npos) lineEnd = code.size(); + std::cout << cyan << fileName << ":" << yellow << map.pos << ":" << map.end << reset << " - " << red << "error" << reset << " TS0000: " << e.message << "\n\n"; + std::cout << code.substr(lineStart, lineEnd - lineStart - 1) << "\n"; + auto space = map.pos - lineStart; + std::cout << std::string(space, ' ') << red << "^" << reset << "\n\n"; + continue; + } } + std::cout << " " << e.message << "\n"; } std::cout << "Found " << errors.size() << " errors in " << fileName << "\n"; } diff --git a/src/checker/vm.h b/src/checker/vm.h index 4f04313..e1c5b04 100644 --- a/src/checker/vm.h +++ b/src/checker/vm.h @@ -246,26 +246,6 @@ namespace ts::vm { } }; - struct Frame { - unsigned int sp = 0; //stack pointer - unsigned int initialSp = 0; //initial stack pointer - unsigned int variables = 0; //the amount of registered variable slots on the stack. will be subtracted when doing popFrame() - - sharedOpt loop = nullptr; - Frame *previous = nullptr; - - unsigned int size() { - return sp - initialSp; - } - - explicit Frame(Frame *previous = nullptr): previous(previous) { - if (previous) { - sp = previous->sp; - initialSp = previous->sp; - } - } - }; - /** * For each active subroutine this object is created. */ @@ -298,6 +278,28 @@ namespace ts::vm { } }; + struct Frame { + unsigned int sp = 0; //stack pointer + unsigned int initialSp = 0; //initial stack pointer + unsigned int variables = 0; //the amount of registered variable slots on the stack. will be subtracted when doing popFrame() + vector variableIPs; //only used when stepper is active + sharedOpt loop = nullptr; + Frame *previous = nullptr; + ModuleSubroutine *subroutine; + + unsigned int size() { + return sp - initialSp; + } + + explicit Frame(Frame *previous = nullptr): previous(previous) { + if (previous) { + sp = previous->sp; + initialSp = previous->sp; + subroutine = previous->subroutine; + } + } + }; + static unsigned char emptyTuple[] = { OP::Frame, OP::Tuple, OP::Return }; @@ -343,7 +345,7 @@ namespace ts::vm { } case OP::Main: { module->mainAddress = readUint32(bin, i + 1); - module->subroutines.push_back(ModuleSubroutine("name", module->mainAddress)); + module->subroutines.push_back(ModuleSubroutine("main", module->mainAddress)); return; } } @@ -363,7 +365,9 @@ namespace ts::vm { vector> stack; //when a OP is processes, its instruction pointer is stores here, and used in push() to set the ip to the new type generated by this OP. //diagnostics/debugger can use this information to map the type to the sourcecode. - unsigned int ip; + unsigned int ip{}; + + bool stepper = false; vector> modules; @@ -391,7 +395,7 @@ namespace ts::vm { return errors; } - void run(shared module) { + void prepare(shared module) { parseHeader(module); modules.push_back(module); @@ -403,22 +407,17 @@ namespace ts::vm { next->previous = subroutine; subroutine = next; frame = new Frame(frame); + frame->subroutine = subroutine->subroutine; + } + void run(shared module) { + prepare(module); process(); } void call(shared &module, unsigned int index = 0, unsigned int arguments = 0) { - //todo: check if address was already calculated using unordered_map? const auto loopRunning = !!subroutine; -// auto found = std::equal(bin.begin() + address, bin.begin() + address + sizeof(emptyTuple), emptyTuple); -// if (found) { -//// debug("found! {}", address); -// push(make_shared()); -// return; -// } -// } - auto routine = module->getSubroutine(index); if (routine->narrowed) { push(routine->narrowed); @@ -437,6 +436,7 @@ namespace ts::vm { next->depth = subroutine ? subroutine->depth + 1 : 0; frame = new Frame(frame); + frame->subroutine = next->subroutine; if (arguments) { //we move x arguments from the old stack to the new one @@ -449,20 +449,8 @@ namespace ts::vm { if (loopRunning) subroutine->ip--; //`subroutine` is set to something new, so for() increments its ip, which we don't want if (!loopRunning) process(); - -// print(); -// return next->resultType; } -// span> popFrame() { -// span> sub{stack.data() + frame->initialSp, frame->sp - frame->initialSp}; -//// auto sub = slice(stack, frame->initialSp, frame->sp); -// stack.resize(frame->initialSp); -// frame = frame->previous; -// if (!frame) throw runtime_error("Invalid pop frame"); -// return sub; -// } - /** * Read frame types without popping frame. */ @@ -486,19 +474,10 @@ namespace ts::vm { unsigned int frameSize() { return frame->initialSp - frame->sp; } -// -// void popFrame(function)> callback) { -// auto frameSize = frame->initialSp - frame->sp; -// while (frameSize-- ) { -// auto &item = stack.top(); -// callback(item); -// stack.pop(); -// } -// frame = frame->previous; -// } void pushFrame() { frame = new Frame(frame); + frame->subroutine = subroutine->subroutine; } shared &last() { @@ -1001,7 +980,7 @@ namespace ts::vm { break; } case OP::Error: { - const auto code = (instructions::ErrorCode)subroutine->parseUint16(); + const auto code = (instructions::ErrorCode) subroutine->parseUint16(); switch (code) { case ErrorCode::CannotFind: { report(DiagnosticMessage(fmt::format("Cannot find name '{}'", subroutine->module->findIdentifier(ip)), ip)); @@ -1017,6 +996,20 @@ namespace ts::vm { throw runtime_error(fmt::format("unhandled op {}", op)); } } + + if (stepper) { + if (op == instructions::TypeArgument) { + frame->variableIPs.push_back(ip); + } + subroutine->ip++; +// debug("Routine {} (ended={})", subroutine->depth, subroutine->ip == subroutine->end); + if (subroutine->ip == subroutine->end) { + //subroutine is done + subroutine = subroutine->previous; + } + //process() needs to be executed for each step + return; + } } subroutine = subroutine->previous; diff --git a/src/gui/debugger_main.cpp b/src/gui/debugger_main.cpp index 20122a7..61d503e 100644 --- a/src/gui/debugger_main.cpp +++ b/src/gui/debugger_main.cpp @@ -1,4 +1,5 @@ #include +#include #include "./app.h" #include "../parser2.h" @@ -8,6 +9,7 @@ #include "TextEditor.h" using std::string; +using std::span; using namespace ts; using namespace ts::gui; @@ -22,24 +24,38 @@ struct ExecutionData { }; int main() { + guiApp.title = "Typhoon"; guiAppInit(); string fileName = "app.ts"; TextEditor editor; auto lang = TextEditor::LanguageDefinition::CPlusPlus(); - lang.mKeywords.insert("type"); + + lang.mKeywords.insert({"type", "extends", "string", "number", "boolean", "bigint"}); + editor.SetLanguageDefinition(lang); TextEditor::ErrorMarkers markers; // markers.insert(std::make_pair(1, "Example error here:\nInclude file not found: \"TextEditor.h\"")); // markers.insert(std::make_pair(41, "Another example error")); editor.SetErrorMarkers(markers); + editor.SetShowWhitespaces(false); + + editor.SetText(R"( +// Here you can see in real-time what branch the conditional type takes +type isNumber = T extends number ? df : "no"; +const v2: isNumber = "yes"; - editor.SetText("type a = T;const v2: a = \"as\";\n\n"); +// Here you can see that distributive conditional types +// are executed for each union member +type NoNumber = T extends number ? never : T; +type Primitive = string | number | boolean; +const v3: NoNumber = 34; +)"); auto fontDefault = ImGui::GetIO().Fonts->AddFontFromFileTTF("/System/Library/Fonts/SFNS.ttf", 32.0); - auto fontMono = ImGui::GetIO().Fonts->AddFontFromFileTTF("/System/Library/Fonts/SFNSMono.ttf", 32.0); + auto fontMono = ImGui::GetIO().Fonts->AddFontFromFileTTF("/System/Library/Fonts/SFNSMono.ttf", 30.0); auto fontMonoSmall = ImGui::GetIO().Fonts->AddFontFromFileTTF("/System/Library/Fonts/SFNSMono.ttf", 26.0); checker::DebugBinResult debugBinResult; @@ -47,6 +63,26 @@ int main() { ExecutionData lastExecution; + auto extractErrors = [&] { + editor.inlineErrors.clear(); + for (auto &&e: module->errors) { + auto map = e.module->findNormalizedMap(e.ip); + auto lineChar = e.module->mapToLineCharacter(map); + editor.inlineErrors.push_back({.data = &e, .line = (int) lineChar.line, .charPos = (int) lineChar.pos, .charEnd = (int) lineChar.end}); + } + }; + + { + checker::Compiler compiler; + Parser parser; + auto result = parser.parseSourceFile(fileName, editor.GetText(), ts::types::ScriptTarget::Latest, false, ScriptKind::TS, {}); + auto program = compiler.compileSourceFile(result); + auto bin = program.build(); + module = make_shared(std::move(bin), fileName, editor.GetText()); + debugBinResult = checker::parseBin(module->bin); + extractErrors(); + } + auto runProgram = [&] { checker::Compiler compiler; Parser parser; @@ -87,13 +123,8 @@ int main() { lastExecution.checkTime = (std::chrono::high_resolution_clock::now() - start) / iterations; // vm.printErrors(); - - editor.inlineErrors.clear(); - for (auto &&e: module->errors) { - auto map = e.module->findNormalizedMap(e.ip); - auto lineChar = e.module->mapToLineCharacter(map); - editor.inlineErrors.push_back({.data = &e, .line = (int) lineChar.line, .charPos = (int) lineChar.pos, .charEnd = (int) lineChar.end}); - } + editor.highlights.clear(); + extractErrors(); }; editor.inlineErrorHover = [](ImVec2 &start, ImVec2 &end, TextEditor::InlineError &inlineError) { @@ -103,6 +134,10 @@ int main() { ImGui::EndTooltip(); }; + auto debugActive = false; + auto debugEnded = false; + vm::VM debugVM; + runProgram(); guiAppRender([&] { ImGui::PushFont(fontDefault); @@ -127,45 +162,47 @@ int main() { ImVec4 grey{0.5, 0.5, 0.5, 1}; ImVec4 green{0.5, 0.9, 0.5, 1}; + ImVec4 yellow{1, 1, 0, 1}; { ImGui::SetNextWindowSize(ImVec2(300, 300), ImGuiCond_FirstUseEver); if (ImGui::Begin("Run", nullptr)) { if (ImGui::Button("Execute")) { runProgram(); + debugActive = false; + debugEnded = false; } auto totalCompiler = lastExecution.parseTime.count() + lastExecution.compileTime.count() + lastExecution.binaryTime.count(); auto total = totalCompiler + lastExecution.checkTime.count(); ImGui::PushFont(fontMonoSmall); - ImGui::TextColored(green, fmt::format("Warm: {}ms", lastExecution.checkTime.count()).c_str()); + ImGui::TextColored(green, fmt::format("Warm: {:.6f}ms", lastExecution.checkTime.count()).c_str()); ImGui::SameLine(); ImGui::TextColored(grey, "Bytecode was cached on disk/memory"); - ImGui::TextColored(green, fmt::format("Cold: {}ms", total).c_str()); + ImGui::TextColored(green, fmt::format("Cold: {:.6f}ms", total).c_str()); ImGui::SameLine(); ImGui::TextColored(grey, "No bytecode cache"); - ImGui::TextColored(grey, "Details:"); ImGui::BeginGroup(); - ImGui::Text(fmt::format("Compile\n{}ms", totalCompiler).c_str()); + ImGui::Text(fmt::format("Compile bytecode\n{:.6f}ms", totalCompiler).c_str()); ImGui::BeginGroup(); - ImGui::TextColored(grey, fmt::format("Parse\n{}ms", lastExecution.parseTime.count()).c_str()); + ImGui::TextColored(grey, fmt::format("Parse\n{:.6f}ms", lastExecution.parseTime.count()).c_str()); ImGui::SameLine(); - ImGui::TextColored(grey, fmt::format("Compile\n{}ms", lastExecution.compileTime.count()).c_str()); + ImGui::TextColored(grey, fmt::format("Compile\n{:.6f}ms", lastExecution.compileTime.count()).c_str()); ImGui::SameLine(); - ImGui::TextColored(grey, fmt::format("Packaging\n{}ms", lastExecution.binaryTime.count()).c_str()); + ImGui::TextColored(grey, fmt::format("Packaging\n{:.6f}ms", lastExecution.binaryTime.count()).c_str()); ImGui::EndGroup(); ImGui::EndGroup(); ImGui::SameLine(); ImGui::BeginGroup(); - ImGui::Text(fmt::format("Checking\n{}ms", lastExecution.checkTime.count()).c_str()); + ImGui::Text(fmt::format("Checking\n{:.6f}ms", lastExecution.checkTime.count()).c_str()); ImGui::EndGroup(); ImGui::PopFont(); @@ -214,18 +251,159 @@ int main() { ImGui::End(); } + { + ImGui::SetNextWindowSize(ImVec2(500, 300), ImGuiCond_FirstUseEver); + if (ImGui::Begin("Virtual Machine", nullptr)) { + if ((!debugActive || debugEnded) && ImGui::Button("Debug")) { + if (!debugActive || debugEnded) { + debugVM = vm::VM(); + module->clear(); + debugActive = true; + debugEnded = false; + editor.SetReadOnly(true); + debugVM.stepper = true; + debugVM.prepare(module); + } + } + + static vm::Frame *selectedFrame = nullptr; + + if (debugActive) { + if (debugEnded) { + ImGui::Text("Program exited"); + } else { + if (ImGui::Button("Next")) { + static vm::FoundSourceMap lastMap; + selectedFrame = nullptr; + while (true) { + debugVM.process(); + if (!debugVM.subroutine) { + debugEnded = true; + editor.SetReadOnly(false); + editor.highlights.clear(); + break; + } else { + auto map = module->findNormalizedMap(debugVM.subroutine->ip); + if (!map.found()) continue; //another step please + if (map.pos == lastMap.pos && map.end == lastMap.end) continue; //another step please + lastMap = map; + auto lineChar = module->mapToLineCharacter(map); + editor.highlights.clear(); + editor.highlights.push_back({.line = (int) lineChar.line, .charPos = (int) lineChar.pos, .charEnd = (int) lineChar.end}); + break; + } + } + } + if (!debugEnded) { + ImGui::SameLine(); + if (ImGui::Button("Stop")) { + editor.SetReadOnly(false); + debugActive = false; + debugEnded = true; + debugVM = vm::VM(); + runProgram(); + } + + ImGui::SameLine(); + ImGui::TextColored(green, "*Active*"); + } + } + + if (debugVM.subroutine) { + + auto current = debugVM.frame; + vector frames; + while (current) { + frames.push_back(current); + current = current->previous; + } + ImGui::Text("Stack (%d/%d)", frames.size(), debugVM.stack.size()); + + static auto showNonVariables = false; + + if (!selectedFrame) selectedFrame = frames.front(); + + ImGui::Checkbox("Show all stack entries", &showNonVariables); + + ImGui::PushItemWidth(120); + if (ImGui::BeginListBox("###listbox")) { + auto i = 0; + string_view lastName = "main"; + for (auto it = frames.rbegin(); it != frames.rend(); it++) { + auto frame = (*it); + ImGui::PushID(i); + + if (!frame->subroutine->name.empty()) { + lastName = frame->subroutine->name; + } + if (ImGui::Selectable((string(lastName)).c_str(), selectedFrame == frame)) { + selectedFrame = frame; + } + + ImGui::SameLine(); + ImGui::TextColored(grey, to_string(showNonVariables ? frame->size() : frame->variables).c_str()); + + // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) + if (selectedFrame == frame) ImGui::SetItemDefaultFocus(); + i++; + ImGui::PopID(); + } + ImGui::EndListBox(); + } + + ImGui::PopItemWidth(); + + if (selectedFrame) { + ImGui::SameLine(); + ImGui::BeginGroup(); + auto start = selectedFrame->initialSp; // + frame->variables; + span> frameStack{debugVM.stack.data() + start, selectedFrame->sp - start}; + + for (unsigned int i = 0; i < frameStack.size(); i++) { + auto type = frameStack[i]; + if (i >= selectedFrame->variables && !showNonVariables) continue; + + ImGui::Text(" "); + ImGui::SameLine(); + if (i < selectedFrame->variables) { + auto ip = selectedFrame->variableIPs[i]; + auto identifier = module->findIdentifier(ip); + ImGui::Text(identifier.c_str()); + ImGui::SameLine(); + } + auto stype = vm::stringify(type); + if (stype.size() > 20) stype = stype.substr(0, 20) + "..."; + ImGui::TextColored(grey, stype.c_str()); + } + ImGui::EndGroup(); + } + } + } + } + + ImGui::End(); + } + { ImGui::SetNextWindowSize(ImVec2(500, 300), ImGuiCond_FirstUseEver); if (ImGui::Begin("Bytecode", nullptr)) { //show storage - string size = "Size: " + to_string(module->bin.size()) + "bytes"; - ImGui::TextWrapped(size.c_str()); - string storage = "Storage: "; + ImGui::Text("Size: "); + ImGui::SameLine(); + ImGui::PushFont(fontMono); + ImGui::TextWrapped((to_string(module->bin.size()) + "bytes").c_str()); + ImGui::PopFont(); + + ImGui::Text("Storage: "); + ImGui::SameLine(); + string storage = ""; for (auto &&s: debugBinResult.storages) { - storage += s + " "; + storage += "\"" + s + "\" "; } + ImGui::PushFont(fontMono); ImGui::TextWrapped(storage.c_str()); - ImGui::Spacing(); + ImGui::PopFont(); +// ImGui::Spacing(); //show subroutines + ops ImGui::Text("Subroutines"); @@ -237,6 +415,7 @@ int main() { ImGui::TableSetupColumn("OPs", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoSort); ImGui::TableHeadersRow(); + ImGui::PushFont(fontMonoSmall); auto i = 0; for (auto &&s: debugBinResult.subroutines) { ImGui::TableNextRow(); @@ -251,15 +430,18 @@ int main() { ImGui::Text(s.name.c_str()); ImGui::TableNextColumn(); - string ops = ""; for (auto &&op: s.operations) { - ops += op + " "; + ImGui::TextColored(grey, to_string(op.address).c_str()); + ImGui::SameLine(); + if (debugVM.subroutine && debugVM.subroutine->ip == op.address) { + ImGui::TextColored(yellow, op.text.c_str()); + } else { + ImGui::Text(op.text.c_str()); + } } - ImGui::PushFont(fontMonoSmall); - ImGui::TextWrapped(ops.c_str()); - ImGui::PopFont(); i++; } + ImGui::PopFont(); ImGui::EndTable();