diff --git a/.gitmodules b/.gitmodules index 0a4c3dc..daba944 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "libs/fmt"] path = libs/fmt url = git@github.com:fmtlib/fmt.git +[submodule "libs/imgui"] + path = libs/imgui + url = git@github.com:ocornut/imgui.git diff --git a/libs/imgui b/libs/imgui new file mode 160000 index 0000000..9aae45e --- /dev/null +++ b/libs/imgui @@ -0,0 +1 @@ +Subproject commit 9aae45eb4a05a5a1f96be1ef37eb503a12ceb889 diff --git a/libs/imgui-texteditor-fork/TextEditor.cpp b/libs/imgui-texteditor-fork/TextEditor.cpp index ab47a4b..c3e794e 100644 --- a/libs/imgui-texteditor-fork/TextEditor.cpp +++ b/libs/imgui-texteditor-fork/TextEditor.cpp @@ -52,6 +52,7 @@ TextEditor::TextEditor() SetPalette(GetDarkPalette()); SetLanguageDefinition(LanguageDefinition::HLSL()); mLines.push_back(Line()); + inlineErrorHover = [](auto ...) {}; } TextEditor::~TextEditor() @@ -882,6 +883,7 @@ void TextEditor::Render() auto scrollY = ImGui::GetScrollY(); auto lineNo = (int)floor(scrollY / mCharAdvance.y); + auto lineStart = (int)floor(scrollY / mCharAdvance.y); auto globalLineMax = (int)mLines.size(); auto lineMax = std::max(0, std::min((int)mLines.size() - 1, lineNo + (int)floor((scrollY + contentSize.y) / mCharAdvance.y))); @@ -1009,7 +1011,6 @@ void TextEditor::Render() } } - // Render colorized text auto prevColor = line.empty() ? mPalette[(int)PaletteIndex::Default] : GetGlyphColor(line[0]); ImVec2 bufferOffset; @@ -1081,9 +1082,31 @@ void TextEditor::Render() ++lineNo; } - // Draw a tooltip on known identifiers/preprocessor symbols + // Render inline errors + if (true) { + for (auto &&inlineError: inlineErrors) { + 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); + } + } + + // Draw a tooltip on known identifiers/preprocessor symbols if (ImGui::IsMousePosValid()) { + auto mousePos = ImGui::GetMousePos(); + 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); + auto end = ImVec2(lineStartScreenPos.x + mTextStart + scrollX + (mCharAdvance.x * inlineError.charEnd), lineStartScreenPos.y + mCharAdvance.y); + if (ImGui::IsMouseHoveringRect(start, end)) { + inlineErrorHover(start, end, inlineError); + } + } + auto id = GetWordAt(ScreenPosToCoordinates(ImGui::GetMousePos())); if (!id.empty()) { @@ -2008,7 +2031,7 @@ const TextEditor::Palette & TextEditor::GetDarkPalette() { const static Palette p = { { 0xff7f7f7f, // Default - 0xffd69c56, // Keyword + 0xffd69c56, // Keyword 0xff00ff00, // Number 0xff7070e0, // String 0xff70a0e0, // Char literal @@ -2036,7 +2059,7 @@ const TextEditor::Palette & TextEditor::GetLightPalette() { const static Palette p = { { 0xff7f7f7f, // None - 0xffff0c06, // Keyword + 0xffff0c06, // Keyword 0xff008000, // Number 0xff2020a0, // String 0xff304070, // Char literal @@ -2064,7 +2087,7 @@ const TextEditor::Palette & TextEditor::GetRetroBluePalette() { const static Palette p = { { 0xff00ffff, // None - 0xffffff00, // Keyword + 0xffffff00, // Keyword 0xff00ff00, // Number 0xff808000, // String 0xff808000, // Char literal diff --git a/libs/imgui-texteditor-fork/TextEditor.h b/libs/imgui-texteditor-fork/TextEditor.h index bd52e13..65b1593 100644 --- a/libs/imgui-texteditor-fork/TextEditor.h +++ b/libs/imgui-texteditor-fork/TextEditor.h @@ -148,6 +148,13 @@ class TextEditor typedef std::vector Line; typedef std::vector Lines; + struct InlineError { + int line; + int charPos; + int charEnd; + void *data = nullptr; + }; + struct LanguageDefinition { typedef std::pair TokenRegexString; @@ -266,6 +273,8 @@ class TextEditor static const Palette& GetLightPalette(); static const Palette& GetRetroBluePalette(); + std::vector inlineErrors; + std::function inlineErrorHover; private: typedef std::vector> RegexList; diff --git a/main.cpp b/main.cpp index 30d59a0..ba9831d 100644 --- a/main.cpp +++ b/main.cpp @@ -32,6 +32,26 @@ bool exists(const string &file) return infile.good(); } +void run(const string &bytecode, const string &code, const string &fileName) { + vm::VM vm; + vm.run(bytecode, code, fileName); + vm.printErrors(); +} + +void compileAndRun(const string &code, const string &file, const string &fileName) { + auto bytecode = file + ".tsbytecode"; + auto buffer = readFile(file); + checker::Compiler compiler; + Parser parser; + auto result = parser.parseSourceFile(file, buffer, ts::types::ScriptTarget::Latest, false, ScriptKind::TS, {}); + auto program = compiler.compileSourceFile(result); + auto bin = program.build(); + writeFile(bytecode, bin); + vm::VM vm; + vm.run(bin, code, fileName); + vm.printErrors(); +} + int main(int argc, char *argv[]) { std::string file = "/Users/marc/bude/typescript-cpp/tests/basicError1.ts"; auto cwd = std::filesystem::current_path(); @@ -41,25 +61,13 @@ int main(int argc, char *argv[]) { } auto code = readFile(file); + auto bytecode = file + ".tsb"; auto relative = std::filesystem::relative(file, cwd); - auto bytecode = file + ".tsbytecode"; if (exists(bytecode)) { - auto buffer = readFile(bytecode); - vm::VM vm; - vm.run(buffer, code, relative.string()); - vm.printErrors(); + run(readFile(bytecode), code, relative.string()); } else { - auto buffer = readFile(file); - checker::Compiler compiler; - Parser parser; - auto result = parser.parseSourceFile(file, buffer, ts::types::ScriptTarget::Latest, false, ScriptKind::TS, {}); - auto program = compiler.compileSourceFile(result); - auto bin = program.build(); - writeFile(bytecode, bin); - vm::VM vm; - vm.run(bin, code, relative.string()); - vm.printErrors(); + compileAndRun(code, file, relative.string()); } return 0; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 38d474f..bad3cea 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,7 +11,9 @@ add_subdirectory(tests) add_library(typescript utf.h utf.cpp core.h core.cpp utilities.h utilities.cpp node_test.h node_test.cpp syntax_cursor.h syntax_cursor.cpp parser2.h parser2.cpp types.h types.cpp path.h path.cpp factory.h factory.cpp parenthesizer.h parenthesizer.cpp scanner.h scanner.cpp - checker/vm.h checker/vm.cpp checker/instructions.h checker/compiler.h checker/type_objects.h checker/utils.h checker/checks.h checker/debug.h) + checker/vm.h checker/vm.cpp checker/instructions.h checker/compiler.h checker/types.h checker/utils.h checker/checks.h checker/debug.h) # ${CMAKE_CURRENT_SOURCE_DIR}/../libs/tracy/TracyClient.cpp -target_link_libraries(typescript fmt) \ No newline at end of file +target_link_libraries(typescript fmt) + +add_subdirectory(gui) \ No newline at end of file diff --git a/src/checker/checks.h b/src/checker/checks.h index bb67080..c0fc1fd 100644 --- a/src/checker/checks.h +++ b/src/checker/checks.h @@ -1,6 +1,6 @@ #pragma once -#include "./type_objects.h" +#include "./types.h" #include "../core.h" namespace ts::vm { @@ -17,10 +17,14 @@ namespace ts::vm { sharedOpt findMember(const vector> &members, const string_view &name) { for (auto &&member: members) { switch (member->kind) { - case TypeKind::MethodSignature: if (to(member)->name == name) return member; break; - case TypeKind::Method: if (to(member)->name == name) return member; break; - case TypeKind::PropertySignature: if (to(member)->name == name) return member; break; - case TypeKind::Property: if (to(member)->name == name) return member; break; + case TypeKind::MethodSignature: if (to(member)->name == name) return member; + break; + case TypeKind::Method: if (to(member)->name == name) return member; + break; + case TypeKind::PropertySignature: if (to(member)->name == name) return member; + break; + case TypeKind::Property: if (to(member)->name == name) return member; + break; } } return nullptr; @@ -32,8 +36,8 @@ namespace ts::vm { } struct StackEntry { - shared left; - shared right; + shared left; + shared right; }; struct ExtendableStack { @@ -184,7 +188,34 @@ namespace ts::vm { return stack.failed(); } case TypeKind::Literal: { - return to(left)->type == to(right)->type && to(left)->text() == to(right)->text() ? stack.valid() : stack.failed(); + if (left->kind == TypeKind::Literal) { + return to(left)->type == to(right)->type && to(left)->text() == to(right)->text() ? stack.valid() : stack.failed(); + } + return stack.failed(); + } + case TypeKind::Union: { + if (left->kind != TypeKind::Union) { + for (auto &&l: to(right)->types) { + if (isExtendable(left, l, stack)) return stack.valid(); + } + return false; + } else { + //e.g.: string|number = string|boolean + auto rightTypes = to(right)->types; + auto leftTypes = to(left)->types; + + for (auto &&r: leftTypes) { + bool valid = false; + for (auto &&l: rightTypes) { + if (isExtendable(l, r, stack)) { + valid = true; + break; + }; + } + if (!valid) return stack.failed(); + } + return stack.valid(); + } } } @@ -240,5 +271,4 @@ namespace ts::vm { ExtendableStack stack; return isExtendable(left, right, stack); } - } \ No newline at end of file diff --git a/src/checker/compiler.h b/src/checker/compiler.h index a2d242f..ad5df61 100644 --- a/src/checker/compiler.h +++ b/src/checker/compiler.h @@ -13,6 +13,7 @@ namespace ts::checker { using std::string; using std::function; using instructions::OP; + using instructions::ErrorCode; enum class SymbolType { Variable, //const x = true; @@ -159,20 +160,20 @@ namespace ts::checker { return subroutine; } - Symbol &findSymbol(const string_view &identifier) { + Symbol *findSymbol(const string_view &identifier) { Frame *current = frame.get(); while (true) { for (auto &&s: current->symbols) { if (s.name == identifier) { - return s; + return &s; } } if (!current->previous) break; current = current->previous.get(); }; - throw runtime_error(fmt::format("No symbol for {} found", identifier)); + return nullptr; } /** @@ -204,6 +205,11 @@ namespace ts::checker { writeUint16(ops, ops.size(), v); } + void pushError(ErrorCode code, const shared &node) { + pushOp(OP::Error, node); + pushUint16((unsigned int)code); + } + void pushSymbolAddress(Symbol &symbol) { auto &ops = getOPs(); unsigned int frameOffset = 0; @@ -237,8 +243,8 @@ namespace ts::checker { void pushOp(OP op, const shared &node) { auto &ops = getOPs(); - ops.push_back(op); pushSourceMap(node); + ops.push_back(op); } //needed for variables @@ -343,10 +349,6 @@ namespace ts::checker { //set initial jump position to right after the storage data writeUint32(bin, 1, address); - - address += subroutines.size() * (1 + 4 + 4); //OP::Subroutine + uint32 name address + uin32 routine address - address += 1 + 4; //OP::Main + uint32 address - //push all storage data to the binary for (auto &&item: storage) { writeUint16(bin, bin.size(), item.size()); @@ -363,16 +365,19 @@ namespace ts::checker { //write sourcemap bin.push_back(OP::SourceMap); writeUint32(bin, bin.size(), sourceMapSize); - address += 1 + 4 + sourceMapSize; + address += 1 + 4 + sourceMapSize; //OP::SourceMap + uint32 size unsigned int bytecodePosOffset = address; + bytecodePosOffset += subroutines.size() * (1 + 4 + 4); //OP::Subroutine + uint32 name address + uint32 routine address + bytecodePosOffset += 1 + 4; //OP::Main + uint32 address + for (auto &&routine: subroutines) { for (auto &&map: routine->sourceMap.map) { writeUint32(bin, bin.size(), bytecodePosOffset + map.bytecodePos); writeUint32(bin, bin.size(), map.sourcePos); writeUint32(bin, bin.size(), map.sourceEnd); - bytecodePosOffset += routine->ops.size(); } + bytecodePosOffset += routine->ops.size(); } for (auto &&map: sourceMap.map) { @@ -381,6 +386,9 @@ namespace ts::checker { writeUint32(bin, bin.size(), map.sourceEnd); } + address += 1 + 4; //OP::Main + uint32 address + address += subroutines.size() * (1 + 4 + 4); //OP::Subroutine + uint32 name address + uint32 routine address + //after the storage data follows the subroutine meta-data. for (auto &&routine: subroutines) { bin.push_back(OP::Subroutine); @@ -504,25 +512,30 @@ namespace ts::checker { // program.pushOp(OP::Number); const auto n = to(node); const auto name = to(n->typeName)->escapedText; - auto &symbol = program.findSymbol(name); - if (symbol.type == SymbolType::TypeVariable) { - program.pushOp(OP::Loads, n->typeName); - program.pushSymbolAddress(symbol); + auto symbol = program.findSymbol(name); + if (!symbol) { + program.pushOp(OP::Never); + program.pushError(ErrorCode::CannotFind, n->typeName); } else { - if (n->typeArguments) { - for (auto &&p: n->typeArguments->list) { - handle(p, program); - } - } - program.pushOp(OP::Call, n->typeName); - if (!symbol.routine) { - throw runtime_error("Reference is not a reference to a existing routine."); - } - program.pushAddress(symbol.routine->index); - if (n->typeArguments) { - program.pushUint16(n->typeArguments->length()); + if (symbol->type == SymbolType::TypeVariable) { + program.pushOp(OP::Loads, n->typeName); + program.pushSymbolAddress(*symbol); } else { - program.pushUint16(0); + if (n->typeArguments) { + for (auto &&p: n->typeArguments->list) { + handle(p, program); + } + } + program.pushOp(OP::Call, n->typeName); + if (!symbol->routine) { + throw runtime_error("Reference is not a reference to a existing routine."); + } + program.pushAddress(symbol->routine->index); + if (n->typeArguments) { + program.pushUint16(n->typeArguments->length()); + } else { + program.pushUint16(0); + } } } break; @@ -571,7 +584,7 @@ namespace ts::checker { case types::SyntaxKind::TypeParameter: { const auto n = to(node); auto &symbol = program.pushSymbol(n->name->escapedText, SymbolType::TypeVariable, n); - program.pushOp(instructions::TypeArgument, node); + program.pushOp(instructions::TypeArgument, n->name); if (n->defaultType) { program.pushSubroutineNameLess(n->defaultType); handle(n->defaultType, program); @@ -661,24 +674,29 @@ namespace ts::checker { case types::SyntaxKind::Identifier: { const auto n = to(node); auto symbol = program.findSymbol(n->escapedText); - if (symbol.type == SymbolType::TypeVariable) { - program.pushOp(OP::Loads, node); - program.pushSymbolAddress(symbol); + if (!symbol) { + program.pushOp(OP::Never); + program.pushError(ErrorCode::CannotFind, n); } else { - if (n->typeArguments) { - for (auto &&p: n->typeArguments->list) { - handle(p, program); - } - } - program.pushOp(OP::Call, node); - if (!symbol.routine) { - throw runtime_error("Reference is not a reference to a existing routine."); - } - program.pushAddress(symbol.routine->index); - if (n->typeArguments) { - program.pushUint16(n->typeArguments->length()); + if (symbol->type == SymbolType::TypeVariable) { + program.pushOp(OP::Loads, node); + program.pushSymbolAddress(*symbol); } else { - program.pushUint16(0); + if (n->typeArguments) { + for (auto &&p: n->typeArguments->list) { + handle(p, program); + } + } + program.pushOp(OP::Call, node); + if (!symbol->routine) { + throw runtime_error("Reference is not a reference to a existing routine."); + } + program.pushAddress(symbol->routine->index); + if (n->typeArguments) { + program.pushUint16(n->typeArguments->length()); + } else { + program.pushUint16(0); + } } } break; @@ -930,13 +948,17 @@ namespace ts::checker { if (n->left->kind == types::SyntaxKind::Identifier) { const auto name = to(n->left)->escapedText; - //todo: handle when symbol is not defined/known (there are other places that need to be adjusted, too) - auto &symbol = program.findSymbol(name); - if (!symbol.routine) throw runtime_error("Symbol has no routine"); + auto symbol = program.findSymbol(name); + if (!symbol) { + program.pushOp(OP::Never); + program.pushError(ErrorCode::CannotFind, n->left); + } else { + if (!symbol->routine) throw runtime_error("Symbol has no routine"); - handle(n->right, program); - program.pushOp(OP::Set, n->operatorToken); - program.pushAddress(symbol.routine->index); + handle(n->right, program); + program.pushOp(OP::Set, n->operatorToken); + program.pushAddress(symbol->routine->index); + } } else { throw runtime_error("BinaryExpression left only Identifier implemented"); } @@ -962,7 +984,7 @@ namespace ts::checker { } else { if (n->type) { const auto subroutineIndex = program.pushSubroutine(id->escapedText); - program.pushSourceMap(id); +// program.pushSourceMap(id); handle(n->type, program); program.popSubroutine(); if (n->initializer) { diff --git a/src/checker/debug.h b/src/checker/debug.h index ae7ff98..81f431a 100644 --- a/src/checker/debug.h +++ b/src/checker/debug.h @@ -15,23 +15,40 @@ namespace ts::checker { } struct PrintSubroutine { - string_view name; + string name; unsigned int address; + vector operations; }; - inline void printBin(string_view bin) { + struct DebugSourceMapEntry { + OP op; + unsigned int bytecodePos; + unsigned int sourcePos; + unsigned int sourceEnd; + }; + + struct DebugBinResult { + vector operations; + vector storages; + vector subroutines; + vector sourceMap; + PrintSubroutine *activeSubroutine = nullptr; + }; + + inline DebugBinResult parseBin(string_view bin, bool print = false) { const auto end = bin.size(); unsigned int storageEnd = 0; bool newSubRoutine = false; - vector subroutines; - fmt::print("Bin {} bytes: ", bin.size()); + DebugBinResult result; + if (print) fmt::print("Bin {} bytes: ", bin.size()); for (unsigned int i = 0; i < end; i++) { if (storageEnd) { while (i < storageEnd) { auto size = readUint16(bin, i); auto data = bin.substr(i + 2, size); - fmt::print("(Storage ({})\"{}\") ", size, data); + if (print) fmt::print("(Storage ({})\"{}\") ", size, data); + result.storages.push_back(string(data)); i += 2 + size; } debug(""); @@ -41,20 +58,22 @@ namespace ts::checker { if (newSubRoutine) { auto found = false; unsigned int j = 0; - for (auto &&r: subroutines) { + for (auto &&r: result.subroutines) { if (r.address == i) { - fmt::print("\n&{} {}(): ", j, r.name); + if (print) fmt::print("\n&{} {}(): ", j, r.name); + result.activeSubroutine = &r; found = true; break; } j++; } if (!found) { - fmt::print("\nunknown!(): "); + if (print) fmt::print("\nunknown!(): "); } newSubRoutine = false; } std::string params = ""; + auto startI = i; auto op = (OP) bin[i]; switch (op) { @@ -69,18 +88,25 @@ namespace ts::checker { i += 4 + size; params += fmt::format(" {}->{} ({})", start, i, size / (4 * 3)); //each entry has 3x 4bytes (uint32) -// for (unsigned int j = start + 4; j < i; j += 4 * 3) { -// debug("Map {}({}) to {}:{}", readUint32(bin, j), (OP)(bin[readUint32(bin, j)]), readUint32(bin, j + 4), readUint32(bin, j + 8)); -// } + for (unsigned int j = start + 4; j < i; j += 4 * 3) { + DebugSourceMapEntry sourceMapEntry{ + .op = (OP)(bin[readUint32(bin, j)]), + .bytecodePos = readUint32(bin, j), + .sourcePos = readUint32(bin, j + 4), + .sourceEnd = readUint32(bin, j + 8), + }; + result.sourceMap.push_back(sourceMapEntry); + if (print) debug("Map [{}]{} to {}:{}", sourceMapEntry.bytecodePos, sourceMapEntry.op, sourceMapEntry.sourcePos, sourceMapEntry.sourceEnd); + } break; } case OP::Subroutine: { auto nameAddress = readUint32(bin, i + 1); auto address = readUint32(bin, i + 5); - string_view name = nameAddress ? readStorage(bin, nameAddress) : ""; + string name = nameAddress ? string(readStorage(bin, nameAddress)) : ""; params += fmt::format(" {}[{}]", name, address); i += 8; - subroutines.push_back({.name = name, .address = address}); + result.subroutines.push_back({.name = name, .address = address}); break; } case OP::Main: @@ -91,7 +117,7 @@ namespace ts::checker { if (op == OP::Jump) { storageEnd = address; } else { - subroutines.push_back({.name = "main", .address = address}); + result.subroutines.push_back({.name = "main", .address = address}); newSubRoutine = true; } break; @@ -122,6 +148,11 @@ namespace ts::checker { i += 2; break; } + case OP::Error: { + params += fmt::format(" {}", (ErrorCode)readUint16(bin, i + 1)); + i += 2; + break; + } case OP::CallExpression: { params += fmt::format(" &{}", readUint16(bin, i + 1)); i += 2; @@ -143,12 +174,24 @@ namespace ts::checker { } } + string text; if (params.empty()) { - fmt::print("{} ", op); + text = fmt::format("[{}]{} ", startI, op); + } else { + text = fmt::format("([{}]{}{}) ", startI, op, params); + } + if (result.activeSubroutine) { + result.activeSubroutine->operations.push_back(text); } else { - fmt::print("({}{}) ", op, params); + result.operations.push_back(text); } + if (print) std::cout << text; } - fmt::print("\n"); + if (print) fmt::print("\n"); + return result; + } + + inline void printBin(string_view bin) { + parseBin(bin, true); } } \ No newline at end of file diff --git a/src/checker/instructions.h b/src/checker/instructions.h index 91900e1..8957c6f 100644 --- a/src/checker/instructions.h +++ b/src/checker/instructions.h @@ -102,6 +102,7 @@ namespace ts::instructions { Assign, Dup, //Duplicates the current stack end Set, //narrows/Sets a new value for a subroutine (variables) + Error, Frame, //creates a new stack frame Return, @@ -114,6 +115,10 @@ namespace ts::instructions { Distribute, //calls a subroutine for each union member. one parameter (address to subroutine) Call //call a subroutine and push the result on the stack }; + + enum class ErrorCode { + CannotFind, //e.g. Cannot find name 'abc' + }; } template<> @@ -123,3 +128,11 @@ struct fmt::formatter: formatter { return formatter::format(magic_enum::enum_name(p), ctx); } }; + +template<> +struct fmt::formatter: formatter { + template + auto format(ts::instructions::ErrorCode p, FormatContext &ctx) { + return formatter::format(magic_enum::enum_name(p), ctx); + } +}; diff --git a/src/checker/type_objects.h b/src/checker/types.h similarity index 84% rename from src/checker/type_objects.h rename to src/checker/types.h index ad8b3e0..15265af 100644 --- a/src/checker/type_objects.h +++ b/src/checker/types.h @@ -2,6 +2,7 @@ #include "../core.h" #include "./utils.h" +#include "../utf.h" #include #include #include @@ -13,6 +14,9 @@ #include "magic_enum.hpp" +namespace ts::checker { +} + namespace ts::vm { using std::string_view; using std::array; @@ -94,9 +98,9 @@ namespace ts::vm { struct Module; struct DiagnosticMessage { - const string message; + string message; unsigned int ip; //ip of the node/OP - Module *module = nullptr; + shared module = nullptr; DiagnosticMessage() {} explicit DiagnosticMessage(const string &message, unsigned int &ip): message(message), ip(ip) {} }; @@ -106,9 +110,26 @@ namespace ts::vm { unsigned int end; }; + struct FoundSourceLineCharacter { + unsigned int line; + unsigned int pos; + unsigned int end; + }; + + /** + * const v2: a = "as" + * ^-----^ this is the identifier source map. + * + * This function makes sure map.pos starts at `v`, basically eating whitespace. + */ + inline void omitWhitespace(const string &code, FoundSourceMap &map) { + map.pos = eatWhitespace(code, map.pos); + } + struct Module { - const string_view &bin; + const string bin; string fileName = "index.ts"; + const string code = ""; //for diagnostic messages only vector subroutines; unsigned int mainAddress; @@ -116,9 +137,13 @@ namespace ts::vm { unsigned int sourceMapAddressEnd; vector errors; - string_view code = ""; //for diagnostic messages only + Module() {} - Module(const string_view &bin): bin(bin) { + Module(const string_view &bin, const string &fileName, const string &code): bin(bin), fileName(fileName), code(code) { + } + + void clear() { + errors.clear(); } ModuleSubroutine *getSubroutine(unsigned int index) { @@ -129,12 +154,18 @@ namespace ts::vm { return &subroutines.back(); } + string findIdentifier(unsigned int ip) { + auto map = findNormalizedMap(ip); + if (map.end == 0) return ""; + return code.substr(map.pos, map.end - map.pos); + } + FoundSourceMap findMap(unsigned int ip) { unsigned int found = 0; for (unsigned int i = sourceMapAddress; i < sourceMapAddressEnd; i += 3 * 4) { auto mapIp = readUint32(bin, i); - found = i; if (mapIp > ip) break; + found = i; } if (found) { @@ -143,23 +174,46 @@ namespace ts::vm { return {0, 0}; } + FoundSourceMap findNormalizedMap(unsigned int ip) { + auto map = findMap(ip); + omitWhitespace(code, map); + return map; + } + + /** + * Converts FindSourceMap{x,y} to + */ + FoundSourceLineCharacter mapToLineCharacter(FoundSourceMap map) { + unsigned int pos = 0; + unsigned int line = 0; + while (pos < map.pos) { + std::size_t lineStart = code.find('\n', pos); + if (lineStart == std::string::npos) { + return {.line = line, .pos = map.pos - pos, .end = map.end - pos}; + } else if (lineStart > map.pos) { + //dont overshot + break; + } + pos = lineStart + 1; + line++; + } + return {.line = line, .pos = map.pos - pos, .end = map.end - pos}; + } + void printErrors() { for (auto &&e: errors) { if (e.ip) { - auto map = findMap(e.ip); + auto map = findNormalizedMap(e.ip); - //todo, map pos/end via sourcemap to source positions std::size_t lineStart = code.rfind('\n', map.pos); - if (lineStart != std::string::npos) { - 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 + 1, lineEnd - lineStart - 1) << "\n"; - auto space = map.pos - lineStart; - std::cout << std::string(space, ' ') << red << "^" << reset << "\n\n"; - } else { - std::cout << " " << e.message << "\n"; - } + 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"; } diff --git a/src/checker/vm.h b/src/checker/vm.h index 1edf43c..4f04313 100644 --- a/src/checker/vm.h +++ b/src/checker/vm.h @@ -1,6 +1,6 @@ #pragma once -#include "./type_objects.h" +#include "./types.h" #include "../core.h" #include "./instructions.h" #include "./checks.h" @@ -22,6 +22,7 @@ namespace ts::vm { using std::vector; using std::runtime_error; using instructions::OP; + using instructions::ErrorCode; inline string_view readStorage(const string_view &bin, const uint32_t offset) { const auto size = readUint16(bin, offset); @@ -269,7 +270,7 @@ namespace ts::vm { * For each active subroutine this object is created. */ struct ProgressingSubroutine { - Module *module; + shared module; ModuleSubroutine *subroutine; unsigned int ip = 0; //instruction pointer @@ -282,7 +283,7 @@ namespace ts::vm { sharedOpt previous = nullptr; - explicit ProgressingSubroutine(Module *module, ModuleSubroutine *subroutine): module(module), subroutine(subroutine) {} + explicit ProgressingSubroutine(shared module, ModuleSubroutine *subroutine): module(module), subroutine(subroutine) {} uint32_t parseUint32() { auto val = readUint32(module->bin, ip + 1); @@ -316,9 +317,8 @@ namespace ts::vm { shared stringToNum(VM &vm); - inline Module *parseHeader(const string_view &bin) { - Module *module = new Module(bin); - + inline void parseHeader(shared &module) { + auto &bin = module->bin; for (unsigned int i = 0; i < bin.size(); i++) { const auto op = (OP) bin[i]; switch (op) { @@ -344,7 +344,7 @@ namespace ts::vm { case OP::Main: { module->mainAddress = readUint32(bin, i + 1); module->subroutines.push_back(ModuleSubroutine("name", module->mainAddress)); - return module; + return; } } } @@ -365,7 +365,7 @@ namespace ts::vm { //diagnostics/debugger can use this information to map the type to the sourcecode. unsigned int ip; - vector modules; + vector> modules; unordered_map(VM &)>> optimised; @@ -375,7 +375,6 @@ namespace ts::vm { ~VM() { delete frame; - for (auto &&module: modules) delete module; } void printErrors() { @@ -392,13 +391,8 @@ namespace ts::vm { return errors; } - void run(const string_view &bin, const string_view &code, string fileName = "") { - for (auto &&module: modules) delete module; - modules.clear(); - - auto module = parseHeader(bin); - module->code = code; - module->fileName = fileName; + void run(shared module) { + parseHeader(module); modules.push_back(module); if (subroutine) throw runtime_error("Subroutine already running"); @@ -413,7 +407,7 @@ namespace ts::vm { process(); } - void call(Module *module, unsigned int index = 0, unsigned int arguments = 0) { + 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; @@ -1006,6 +1000,19 @@ namespace ts::vm { push(make_shared(text, TypeLiteralType::String)); break; } + case OP::Error: { + const auto code = (instructions::ErrorCode)subroutine->parseUint16(); + switch (code) { + case ErrorCode::CannotFind: { + report(DiagnosticMessage(fmt::format("Cannot find name '{}'", subroutine->module->findIdentifier(ip)), ip)); + break; + } + default: { + report(DiagnosticMessage(fmt::format("{}", code), ip)); + } + } + break; + } default: { throw runtime_error(fmt::format("unhandled op {}", op)); } diff --git a/src/diagnostic_messages.h b/src/diagnostic_messages.h index ac905b4..0aa58ba 100644 --- a/src/diagnostic_messages.h +++ b/src/diagnostic_messages.h @@ -13,8 +13,7 @@ namespace ts { return std::make_shared(code, category, key, message, reportsUnnecessary, elidedInCompatabilityPyramid, reportsDeprecated); } - class Diagnostics { - public: + namespace Diagnostics { static shared Unterminated_string_literal(){ return diag(1002, DiagnosticCategory::Error, "Unterminated_string_literal_1002", "Unterminated string literal."); }; static shared Identifier_expected(){ return diag(1003, DiagnosticCategory::Error, "Identifier_expected_1003", "Identifier expected."); }; static shared _0_expected(){ return diag(1005, DiagnosticCategory::Error, "_0_expected_1005", "'{0}' expected."); }; diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt new file mode 100644 index 0000000..f63b6e8 --- /dev/null +++ b/src/gui/CMakeLists.txt @@ -0,0 +1,52 @@ +cmake_minimum_required(VERSION 3.22) +project(typescript) + +add_executable(typescript_debugger + debugger_main.cpp + app.h + ../../libs/imgui/imgui.cpp + ../../libs/imgui/imgui_demo.cpp + ../../libs/imgui/imgui_draw.cpp + ../../libs/imgui/imgui_widgets.cpp + ../../libs/imgui/imgui_tables.cpp + ../../libs/imgui/misc/cpp/imgui_stdlib.cpp + ../../libs/imgui/backends/imgui_impl_sdl.cpp + ../../libs/imgui/backends/imgui_impl_opengl3.cpp + ../../libs/imgui-texteditor-fork/TextEditor.cpp +) + +target_include_directories( + typescript_debugger + PUBLIC ../../libs/imgui + PUBLIC ../../libs/imgui-texteditor-fork + PUBLIC /usr/local/include/ + PUBLIC ../../libs/imgui/backends + PUBLIC ../../libs/imgui/examples/libs/gl3w/ +) + +#if (TRACY_ENABLE) +# target_sources( +# typescript_debugger +# PUBLIC ../../libs/imgui/examples/libs/gl3w/GL/gl3w.c +# ) +#endif() + +target_link_libraries(typescript_debugger typescript) + +find_package(OpenGL REQUIRED) +find_package(SDL2 REQUIRED) + +target_include_directories( + typescript_debugger + PUBLIC ${SDL2_INCLUDE_DIRS} +) + +if (APPLE) + target_link_libraries(typescript_debugger "-framework CoreFoundation") +endif () + +target_link_libraries( + typescript_debugger + ${SDL2_LIBRARIES} + ${OPENGL_LIBRARIES} +) \ No newline at end of file diff --git a/src/gui/app.h b/src/gui/app.h new file mode 100644 index 0000000..e17d596 --- /dev/null +++ b/src/gui/app.h @@ -0,0 +1,161 @@ +#pragma once + +#include "imgui.h" +#include "imgui_impl_sdl.h" +#include "imgui_impl_opengl3.h" +#include +#include +#if defined(IMGUI_IMPL_OPENGL_ES2) +#include +#else +#include +#endif + +using namespace std; + +namespace ts::gui { + class App { + public: + int displayWidth = 20; + int displayHeight = 20; + string title = "App"; + }; + + SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + SDL_Window* window = nullptr; + SDL_GLContext gl_context; + + bool running = true; + App guiApp; + function renderer; + ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + ImFontConfig cfg; + + void mainLoop(void *arg) { + // Poll and handle events (inputs, window resize, etc.) + // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. + // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. + // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. + // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. + SDL_Event event; + while (SDL_PollEvent(&event)) + { + ImGui_ImplSDL2_ProcessEvent(&event); + if (event.type == SDL_QUIT) + running = false; + if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) + running = false; + } + + ImGuiIO &io = ImGui::GetIO(); + guiApp.displayWidth = (int) io.DisplaySize.x; + guiApp.displayHeight = (int) io.DisplaySize.y; + // Start the Dear ImGui frame + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + ImGui::NewFrame(); + + // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). + //ImGui::ShowDemoWindow(&show_demo_window); + renderer(); + + // Rendering + ImGui::Render(); + glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + SDL_GL_SwapWindow(window); + } + + void guiAppInit() { + // Setup SDL + // (Some versions of SDL before <2.0.10 appears to have performance/stalling issues on a minority of Windows systems, + // depending on whether SDL_INIT_GAMECONTROLLER is enabled or disabled.. updating to latest version of SDL is recommended!) + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) + { + printf("Error: %s\n", SDL_GetError()); + return; + } + + // Decide GL+GLSL versions +#if defined(IMGUI_IMPL_OPENGL_ES2) + // GL ES 2.0 + GLSL 100 + const char* glsl_version = "#version 100"; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); +#elif defined(__APPLE__) + // GL 3.2 Core + GLSL 150 + const char* glsl_version = "#version 150"; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); +#else + // GL 3.0 + GLSL 130 + const char* glsl_version = "#version 130"; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); +#endif + + // Create window with graphics context + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + window = SDL_CreateWindow(guiApp.title.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags); + gl_context = SDL_GL_CreateContext(window); + SDL_GL_MakeCurrent(window, gl_context); + SDL_GL_SetSwapInterval(1); // Enable vsync + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + (void)io; + //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + + auto scale = 2; + cfg.SizePixels = 13.0f * scale; + io.Fonts->AddFontDefault(&cfg); + io.FontGlobalScale = 1.0f / scale; + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + //ImGui::StyleColorsClassic(); + + // Setup Platform/Renderer backends + ImGui_ImplSDL2_InitForOpenGL(window, gl_context); + ImGui_ImplOpenGL3_Init(glsl_version); + + // Load Fonts + // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. + // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. + // - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). + // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. + // - Read 'docs/FONTS.md' for more instructions and details. + // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //io.Fonts->AddFontDefault(); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese()); + //IM_ASSERT(font != NULL); + } + + void guiAppRender(function r) { + renderer = r; +#if defined(__EMSCRIPTEN__) + emscripten_set_main_loop_arg(mainLoop, nullptr, 0, true); +#else + while (running) { + mainLoop(nullptr); + } +#endif + } +} \ No newline at end of file diff --git a/src/gui/debugger_main.cpp b/src/gui/debugger_main.cpp new file mode 100644 index 0000000..20122a7 --- /dev/null +++ b/src/gui/debugger_main.cpp @@ -0,0 +1,297 @@ +#include + +#include "./app.h" +#include "../parser2.h" +#include "../checker/compiler.h" +#include "../checker/vm.h" +#include "../checker/debug.h" +#include "TextEditor.h" + +using std::string; + +using namespace ts; +using namespace ts::gui; + +typedef std::chrono::duration took; + +struct ExecutionData { + took parseTime; + took compileTime; + took binaryTime; + took checkTime; +}; + +int main() { + guiAppInit(); + + string fileName = "app.ts"; + + TextEditor editor; + auto lang = TextEditor::LanguageDefinition::CPlusPlus(); + lang.mKeywords.insert("type"); + 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.SetText("type a = T;const v2: a = \"as\";\n\n"); + + 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 fontMonoSmall = ImGui::GetIO().Fonts->AddFontFromFileTTF("/System/Library/Fonts/SFNSMono.ttf", 26.0); + + checker::DebugBinResult debugBinResult; + auto module = make_shared(); + + ExecutionData lastExecution; + + auto runProgram = [&] { + checker::Compiler compiler; + Parser parser; + auto iterations = 1000; + + auto start = std::chrono::high_resolution_clock::now(); + shared result; + for (auto i = 0; i < iterations; i++) { + result = parser.parseSourceFile(fileName, editor.GetText(), ts::types::ScriptTarget::Latest, false, ScriptKind::TS, {}); + } + lastExecution.parseTime = (std::chrono::high_resolution_clock::now() - start) / iterations; + + start = std::chrono::high_resolution_clock::now(); + + checker::Program program; + for (auto i = 0; i < iterations; i++) { + program = compiler.compileSourceFile(result); + } + lastExecution.compileTime = (std::chrono::high_resolution_clock::now() - start) / iterations; + + start = std::chrono::high_resolution_clock::now(); + string bin; + for (auto i = 0; i < iterations; i++) { + bin = program.build(); + } + lastExecution.binaryTime = (std::chrono::high_resolution_clock::now() - start) / iterations; + + module = make_shared(std::move(bin), fileName, editor.GetText()); + debugBinResult = checker::parseBin(module->bin); + + start = std::chrono::high_resolution_clock::now(); + + for (auto i = 0; i < iterations; i++) { + module->clear(); + vm::VM vm; //we need to keep Modules alive + vm.run(module); + } + 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.inlineErrorHover = [](ImVec2 &start, ImVec2 &end, TextEditor::InlineError &inlineError) { + ImGui::BeginTooltip(); + vm::DiagnosticMessage *message = (vm::DiagnosticMessage *) inlineError.data; + ImGui::TextUnformatted(message->message.c_str()); + ImGui::EndTooltip(); + }; + + runProgram(); + guiAppRender([&] { + ImGui::PushFont(fontDefault); + + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(ImVec2(guiApp.displayWidth, guiApp.displayHeight)); + ImGui::Begin("TypeScript Debugger", nullptr, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoBringToFrontOnFocus | + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoMove); + + ImGui::SetWindowFontScale(1.2); +// ImGui::Text("%.1f FPS", ImGui::GetIO().Framerate); + + if (editor.IsTextChanged()) { + runProgram(); + } + + ImGui::PushFont(fontMono); + editor.Render("TextEditor"); + ImGui::PopFont(); + + ImVec4 grey{0.5, 0.5, 0.5, 1}; + ImVec4 green{0.5, 0.9, 0.5, 1}; + + { + ImGui::SetNextWindowSize(ImVec2(300, 300), ImGuiCond_FirstUseEver); + if (ImGui::Begin("Run", nullptr)) { + if (ImGui::Button("Execute")) { + runProgram(); + } + + 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::SameLine(); + ImGui::TextColored(grey, "Bytecode was cached on disk/memory"); + + ImGui::TextColored(green, fmt::format("Cold: {}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::BeginGroup(); + ImGui::TextColored(grey, fmt::format("Parse\n{}ms", lastExecution.parseTime.count()).c_str()); + ImGui::SameLine(); + ImGui::TextColored(grey, fmt::format("Compile\n{}ms", lastExecution.compileTime.count()).c_str()); + ImGui::SameLine(); + ImGui::TextColored(grey, fmt::format("Packaging\n{}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::EndGroup(); + + ImGui::PopFont(); +// +// ImGui::SameLine(); +// ImGui::BeginGroup(); +// ImGui::Text("Checking:"); +// ImGui::SameLine(); +// ImGui::PushFont(fontMono); +// ImGui::Text("%fms", lastExecution.checkTime.count()); +// ImGui::PopFont(); +// ImGui::EndGroup(); + } + ImGui::End(); + } + + { + ImGui::SetNextWindowSize(ImVec2(500, 300), ImGuiCond_FirstUseEver); + if (ImGui::Begin("Diagnostics", nullptr)) { + ImGui::BeginTable("diagnostics", 4, ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders); + + ImGui::TableSetupColumn("file", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoSort, 60); + ImGui::TableSetupColumn("bytepos", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoSort, 60); + ImGui::TableSetupColumn("pos", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoSort, 60); + ImGui::TableSetupColumn("message", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoSort); + ImGui::TableHeadersRow(); + + for (auto &&e: module->errors) { + ImGui::TableNextRow(); + + ImGui::TableNextColumn(); + ImGui::Text(e.module->fileName.c_str()); + + ImGui::TableNextColumn(); + ImGui::Text("%d", e.ip); + + ImGui::TableNextColumn(); + auto map = e.module->findMap(e.ip); + ImGui::Text("%d:%d", map.pos, map.end); + + ImGui::TableNextColumn(); + ImGui::TextWrapped(e.message.c_str()); + } + ImGui::EndTable(); + } + 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: "; + for (auto &&s: debugBinResult.storages) { + storage += s + " "; + } + ImGui::TextWrapped(storage.c_str()); + ImGui::Spacing(); + + //show subroutines + ops + ImGui::Text("Subroutines"); + ImGui::BeginTable("subroutines2", 4, ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders); + + ImGui::TableSetupColumn("idx", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoSort, 30); + ImGui::TableSetupColumn("bytepos", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoSort, 40); + ImGui::TableSetupColumn("name", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoSort, 70); + ImGui::TableSetupColumn("OPs", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoSort); + ImGui::TableHeadersRow(); + + auto i = 0; + for (auto &&s: debugBinResult.subroutines) { + ImGui::TableNextRow(); + + ImGui::TableNextColumn(); + ImGui::Text(to_string(i).c_str()); + + ImGui::TableNextColumn(); + ImGui::Text(to_string(s.address).c_str()); + + ImGui::TableNextColumn(); + ImGui::Text(s.name.c_str()); + + ImGui::TableNextColumn(); + string ops = ""; + for (auto &&op: s.operations) { + ops += op + " "; + } + ImGui::PushFont(fontMonoSmall); + ImGui::TextWrapped(ops.c_str()); + ImGui::PopFont(); + i++; + } + ImGui::EndTable(); + + + //show subroutines + ops + ImGui::Text("Sourcemap"); + ImGui::BeginTable("sourcemap", 3, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders); + + ImGui::TableSetupColumn("bytepos", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoSort, 30); + ImGui::TableSetupColumn("OP", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoSort, 120); + ImGui::TableSetupColumn("source", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoSort, 60); + ImGui::TableHeadersRow(); + + ImGui::PushFont(fontMonoSmall); + for (auto &&m: debugBinResult.sourceMap) { + ImGui::TableNextRow(); + + ImGui::TableNextColumn(); + ImGui::Text("%d", m.bytecodePos); + + ImGui::TableNextColumn(); + ImGui::Text(string(magic_enum::enum_name(m.op)).c_str()); + + ImGui::TableNextColumn(); + ImGui::Text((to_string(m.sourcePos) + ":" + to_string(m.sourceEnd)).c_str()); + } + ImGui::PopFont(); + ImGui::EndTable(); + } + ImGui::End(); + } + + ImGui::End(); + ImGui::PopFont(); + }); +} \ No newline at end of file diff --git a/src/scanner.cpp b/src/scanner.cpp index fb97d74..fe2dd83 100644 --- a/src/scanner.cpp +++ b/src/scanner.cpp @@ -19,23 +19,6 @@ namespace ts { return false; } - bool isWhiteSpaceSingleLine(const CharCode &ch) { - // Note: nextLine is in the Zs space, and should be considered to be a whitespace. - // It is explicitly not a line-break as it isn't in the exact set specified by EcmaScript. - return ch.code == CharacterCodes::space || - ch.code == CharacterCodes::tab || - ch.code == CharacterCodes::verticalTab || - ch.code == CharacterCodes::formFeed || - ch.code == CharacterCodes::nonBreakingSpace || - ch.code == CharacterCodes::nextLine || - ch.code == CharacterCodes::ogham || - (ch.code >= CharacterCodes::enQuad && ch.code <= CharacterCodes::zeroWidthSpace) || - ch.code == CharacterCodes::narrowNoBreakSpace || - ch.code == CharacterCodes::mathematicalSpace || - ch.code == CharacterCodes::ideographicSpace || - ch.code == CharacterCodes::byteOrderMark; - } - string Scanner::scanString(bool jsxAttributeString) { ZoneScoped; auto quote = charCodeAt(text, pos); @@ -561,10 +544,6 @@ namespace ts { return pos; } - bool isWhiteSpaceLike(const CharCode &ch) { - return isWhiteSpaceSingleLine(ch) || isLineBreak(ch); - } - /* @internal */ int ts::skipTrivia(string &text, int pos, optional stopAfterLineBreak, optional stopAtComments, optional inJSDoc) { ZoneScoped; @@ -894,24 +873,6 @@ namespace ts { return firstNonWhitespace == -1 ? SyntaxKind::JsxTextAllWhiteSpaces : SyntaxKind::JsxText; } - bool isLineBreak(const CharCode &ch) { - // ES5 7.3: - // The ECMAScript line terminator characters are listed in Table 3. - // Table 3: Line Terminator Characters - // Code Unit Value Name Formal Name - // \u000A Line Feed - // \u000D Carriage Return - // \u2028 Line separator - // \u2029 Paragraph separator - // Only the characters in Table 3 are treated as line terminators. Other new line or line - // breaking characters are treated as white space but not as line terminators. - - return ch.code == CharacterCodes::lineFeed || - ch.code == CharacterCodes::carriageReturn || - ch.code == CharacterCodes::lineSeparator || - ch.code == CharacterCodes::paragraphSeparator; - } - vector Scanner::appendIfCommentDirective( vector &commentDirectives, const string &text, diff --git a/src/tests/test_checker.cpp b/src/tests/test_checker.cpp index 92bb393..9836062 100644 --- a/src/tests/test_checker.cpp +++ b/src/tests/test_checker.cpp @@ -19,24 +19,27 @@ string compile(string code, bool print = true) { void test(string code, unsigned int expectedErrors = 0) { auto bin = compile(code); + auto module = make_shared(bin, "app.ts", code); vm::VM vm; - vm.run(bin, code); + vm.run(module); vm.printErrors(); EXPECT_EQ(expectedErrors, vm.getErrors()); } void testBench(string code, unsigned int expectedErrors = 0) { auto bin = compile(code); + auto module = make_shared(bin, "app.ts", code); vm::VM vm; - vm.run(bin, code); + vm.run(module); vm.printErrors(); EXPECT_EQ(expectedErrors, vm.getErrors()); auto iterations = 10; - auto warmTime = benchRun(iterations, [&bin, &code] { + auto warmTime = benchRun(iterations, [&module] { + module->clear(); vm::VM vm; - vm.run(bin, code); + vm.run(module); }); auto compileTime = benchRun(iterations, [&code] { @@ -45,7 +48,8 @@ void testBench(string code, unsigned int expectedErrors = 0) { auto coldTime = benchRun(iterations, [&code] { vm::VM vm; - vm.run(compile(code, false), code); + auto module = make_shared(compile(code, false), "app.ts", code); + vm.run(module); }); fmt::print("{} iterations: compile {}/op, cold {}/op, warm {}/op", iterations, compileTime.count() / iterations, coldTime.count() / iterations, warmTime.count() / iterations); @@ -75,21 +79,20 @@ TEST(checker, type) { const v2: number = 123; )"; - test(code, 0); + testBench(code, 0); } TEST(checker, typeError) { Parser parser; string code = R"( - const v1: string = "asd"; - const v2: number = "23"; +type a = T; const v2: a = "23"; )"; testBench(code, 1); } -TEST(checker, type2) { +TEST(checker, typeUnion) { Parser parser; string code = R"( @@ -103,7 +106,7 @@ TEST(checker, type2) { test(code, 1); } -TEST(checker, type31) { +TEST(checker, typeGeneric) { string code = R"( type a = T; const v1: a = 34; @@ -113,7 +116,7 @@ TEST(checker, type31) { test(code, 1); } -TEST(checker, type3) { +TEST(checker, typeGenericUnion) { Parser parser; string code = R"( diff --git a/src/tests/test_scanner.cpp b/src/tests/test_scanner.cpp index 6aa82fb..c2dc382 100644 --- a/src/tests/test_scanner.cpp +++ b/src/tests/test_scanner.cpp @@ -3,10 +3,7 @@ #include #include "../scanner.h" -#include "magic_enum.hpp" - using namespace ts; -using namespace magic_enum; TEST(scanner, basisc) { diff --git a/src/utf.h b/src/utf.h index be91583..080f727 100644 --- a/src/utf.h +++ b/src/utf.h @@ -144,7 +144,6 @@ namespace ts { verticalTab = 0x0B, // \v }; - // std::wstring utf16From(std::string text); struct CharCode { @@ -156,4 +155,54 @@ namespace ts { CharCode charCodeAt(const std::string &text, int position, int *size = nullptr); std::string fromCharCode(int cp); + + inline bool isWhiteSpaceSingleLine(const CharCode &ch) { + // Note: nextLine is in the Zs space, and should be considered to be a whitespace. + // It is explicitly not a line-break as it isn't in the exact set specified by EcmaScript. + return ch.code == CharacterCodes::space || + ch.code == CharacterCodes::tab || + ch.code == CharacterCodes::verticalTab || + ch.code == CharacterCodes::formFeed || + ch.code == CharacterCodes::nonBreakingSpace || + ch.code == CharacterCodes::nextLine || + ch.code == CharacterCodes::ogham || + (ch.code >= CharacterCodes::enQuad && ch.code <= CharacterCodes::zeroWidthSpace) || + ch.code == CharacterCodes::narrowNoBreakSpace || + ch.code == CharacterCodes::mathematicalSpace || + ch.code == CharacterCodes::ideographicSpace || + ch.code == CharacterCodes::byteOrderMark; + } + + inline bool isLineBreak(const CharCode &ch) { + // ES5 7.3: + // The ECMAScript line terminator characters are listed in Table 3. + // Table 3: Line Terminator Characters + // Code Unit Value Name Formal Name + // \u000A Line Feed + // \u000D Carriage Return + // \u2028 Line separator + // \u2029 Paragraph separator + // Only the characters in Table 3 are treated as line terminators. Other new line or line + // breaking characters are treated as white space but not as line terminators. + + return ch.code == CharacterCodes::lineFeed || + ch.code == CharacterCodes::carriageReturn || + ch.code == CharacterCodes::lineSeparator || + ch.code == CharacterCodes::paragraphSeparator; + } + + + inline bool isWhiteSpaceLike(const CharCode &ch) { + return isWhiteSpaceSingleLine(ch) || isLineBreak(ch); + } + + inline unsigned int eatWhitespace(const std::string &text, unsigned int pos) { + auto end = text.size(); + while (pos < end) { + auto charCode = charCodeAt(text, pos); + if (!isWhiteSpaceLike(charCode)) break; + pos += charCode.length; + } + return pos; + } }