From 91944b19f7e381669db2f5a5ef7b490365334e0d Mon Sep 17 00:00:00 2001 From: zimavi Date: Sat, 6 Dec 2025 01:51:11 +0200 Subject: [PATCH 1/6] feat: Add basic diagnostic system Add argument parsing library as well as simple error/warning repoting system. --- CMakeLists.txt | 12 +++++++ include/Args/CompilerOptions.hpp | 44 ++++++++++++++++++++++++ include/Diagnostic/Diagnostic.hpp | 27 +++++++++++++++ include/Diagnostic/DiagnosticLevel.hpp | 7 ++++ include/Diagnostic/DiagnosticsEngine.hpp | 26 ++++++++++++++ include/Diagnostic/SourceLocation.hpp | 16 +++++++++ src/main.cpp | 28 ++++++++++++--- 7 files changed, 156 insertions(+), 4 deletions(-) create mode 100644 include/Args/CompilerOptions.hpp create mode 100644 include/Diagnostic/Diagnostic.hpp create mode 100644 include/Diagnostic/DiagnosticLevel.hpp create mode 100644 include/Diagnostic/DiagnosticsEngine.hpp create mode 100644 include/Diagnostic/SourceLocation.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 39aa735..d76dd17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,7 @@ cmake_minimum_required(VERSION 3.31.2) +include(FetchContent) + set(BUILD_SHARED_LIBS OFF CACHE BOOL "Link libraries statically") if(CMAKE_CXX_COMPILER_ID MATCHES "CNU|Clang") @@ -14,6 +16,14 @@ endif() #add_subdirectory("thirdparty/lib") +FetchContent_Declare( + cxxopts + GIT_REPOSITORY https://github.com/jarro2783/cxxopts.git + GIT_TAG "v3.3.1" +) + +FetchContent_MakeAvailable(cxxopts) + project(oxygen LANGUAGES CXX) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) @@ -30,6 +40,8 @@ target_compile_definitions("${CMAKE_PROJECT_NAME}" PUBLIC RESOURCE_PATH="${CMAKE target_include_directories(oxygen PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include/") #target_link_libraries(oxygen PRIVATE lib) +target_link_libraries(oxygen PRIVATE cxxopts::cxxopts) + # ----- LLVM ------ find_package(LLVM REQUIRED CONFIG) diff --git a/include/Args/CompilerOptions.hpp b/include/Args/CompilerOptions.hpp new file mode 100644 index 0000000..d0249b4 --- /dev/null +++ b/include/Args/CompilerOptions.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "Diagnostic/DiagnosticLevel.hpp" +#include "Diagnostic/DiagnosticsEngine.hpp" +#include +#include + +struct CompilerOptions { + bool wall = false; // Enable basic warnings + bool wextra = false; // Enable more warnings + bool werror = false; // Produce errors instead of warnings + std::string inputFile; + std::string outputFile = "a.out"; +}; + +CompilerOptions parseArguments(int argc, char** argv, DiagnosticsEngine& diag) { + CompilerOptions opts; + + cxxopts::Options options(argv[0], "Oxygen compiler"); + options.positional_help("input file").show_positional_help(); + + options.add_options() + ("h,help", "Print help") + ("Wall", "Enable all warnings", cxxopts::value(opts.wall)) + ("Wextra", "Enable extra warning", cxxopts::value(opts.wextra)) + ("Werror", "Produce errors instead of warnings", cxxopts::value(opts.werror)) + ("o", "Output file", cxxopts::value(opts.outputFile)) + ("input", "Input file", cxxopts::value(opts.outputFile)); + + options.parse_positional({"input"}); + + auto result = options.parse(argc, argv); + + if (result.count("help")) { + std::cout << options.help() << "\n"; + exit(0); + } + + if (!result.count("input")) { + diag.report(DiagnosticLevel::Error, {"", 1, 1}, "No input file provided", "Provide a source file as positional argument"); + } + + return opts; +} diff --git a/include/Diagnostic/Diagnostic.hpp b/include/Diagnostic/Diagnostic.hpp new file mode 100644 index 0000000..03cca32 --- /dev/null +++ b/include/Diagnostic/Diagnostic.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "Diagnostic/DiagnosticLevel.hpp" +#include "Diagnostic/SourceLocation.hpp" +#include +#include + +struct Diagnostic { + DiagnosticLevel level; + SourceLocation loc; + std::string message; + std::string suggestion; + + void print() const { + std::string prefix; + switch (level) { + case DiagnosticLevel::Info: prefix = "info"; break; + case DiagnosticLevel::Warning: prefix = "warning"; break; + case DiagnosticLevel::Error: prefix = "error"; break; + } + + std::cerr << loc.toString() << ": " << prefix << ": " << message << "\n"; + if (!suggestion.empty()) { + std::cerr << " suggestion: " << suggestion << "\n"; + } + } +}; diff --git a/include/Diagnostic/DiagnosticLevel.hpp b/include/Diagnostic/DiagnosticLevel.hpp new file mode 100644 index 0000000..9a986e9 --- /dev/null +++ b/include/Diagnostic/DiagnosticLevel.hpp @@ -0,0 +1,7 @@ +#pragma once + +enum class DiagnosticLevel { + Info, + Warning, + Error +}; diff --git a/include/Diagnostic/DiagnosticsEngine.hpp b/include/Diagnostic/DiagnosticsEngine.hpp new file mode 100644 index 0000000..5cd0f27 --- /dev/null +++ b/include/Diagnostic/DiagnosticsEngine.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include "Diagnostic/Diagnostic.hpp" +#include "Diagnostic/DiagnosticLevel.hpp" +#include "Diagnostic/SourceLocation.hpp" + +class DiagnosticsEngine { + std::vector diagnostics; +public: + bool errorWarnings = false; + + void report(DiagnosticLevel level, const SourceLocation& loc, const std::string& msg, const std::string& suggestion = "") { + auto lvl = level == DiagnosticLevel::Warning ? (errorWarnings ? DiagnosticLevel::Error : level) : level; + Diagnostic diag {lvl, loc, msg, suggestion}; + diagnostics.push_back(diag); + diag.print(); + } + + bool hasError() const { + for (auto& d : diagnostics) + if (d.level == DiagnosticLevel::Error) return true; + return false; + } +}; diff --git a/include/Diagnostic/SourceLocation.hpp b/include/Diagnostic/SourceLocation.hpp new file mode 100644 index 0000000..c75c44b --- /dev/null +++ b/include/Diagnostic/SourceLocation.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +struct SourceLocation { + std::string file; + int line; + int column; + + std::string toString() const { + std::ostringstream oss; + oss << file << ":" << line << ":" << column; + return oss.str(); + } +}; diff --git a/src/main.cpp b/src/main.cpp index d1af452..c6a2dd3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,26 @@ -#include +#include "Args/CompilerOptions.hpp" +#include "Diagnostic/DiagnosticLevel.hpp" +#include "Diagnostic/DiagnosticsEngine.hpp" +#include -int main() -{ - std::println("hello, world!"); +int main(int argc, char** argv) { + DiagnosticsEngine diag; + CompilerOptions opts = parseArguments(argc, argv, diag); + + if (diag.hasError()) { + std::cerr << "Compilation aborted due to previous errors.\n"; + return 1; + } + + diag.errorWarnings = opts.werror; + + if (opts.wall) { + diag.report(DiagnosticLevel::Info, {"main.cpp", 16, 9}, "Wall enabled, all warnings are active"); + } + + std::cout << "Compiling " << opts.inputFile << " -> " << opts.outputFile << "\n"; + + diag.report(DiagnosticLevel::Warning, {"main.cpp", 21, 5}, "Sample warning"); + + return 0; } From fbdcb0d45aeaa501894d5b5b48baae5f8097f965 Mon Sep 17 00:00:00 2001 From: zimavi Date: Wed, 10 Dec 2025 15:55:01 +0200 Subject: [PATCH 2/6] feat: improve diagnostic reports Improve formatting and report contents, to show where in source error happened. Added debug flags --- include/Args/CompilerOptions.hpp | 39 ++++-- include/Diagnostic/Colors.hpp | 13 ++ include/Diagnostic/Diagnostic.hpp | 19 +-- include/Diagnostic/DiagnosticNote.hpp | 9 ++ include/Diagnostic/DiagnosticsEngine.hpp | 153 +++++++++++++++++++++-- include/Diagnostic/SourceLocation.hpp | 8 +- include/Diagnostic/SourceManager.hpp | 71 +++++++++++ src/main.cpp | 46 +++++-- test.oxy | 7 ++ 9 files changed, 315 insertions(+), 50 deletions(-) create mode 100644 include/Diagnostic/Colors.hpp create mode 100644 include/Diagnostic/DiagnosticNote.hpp create mode 100644 include/Diagnostic/SourceManager.hpp create mode 100644 test.oxy diff --git a/include/Args/CompilerOptions.hpp b/include/Args/CompilerOptions.hpp index d0249b4..5b8a82f 100644 --- a/include/Args/CompilerOptions.hpp +++ b/include/Args/CompilerOptions.hpp @@ -2,18 +2,24 @@ #include "Diagnostic/DiagnosticLevel.hpp" #include "Diagnostic/DiagnosticsEngine.hpp" +#include #include #include struct CompilerOptions { - bool wall = false; // Enable basic warnings - bool wextra = false; // Enable more warnings bool werror = false; // Produce errors instead of warnings + bool wignore = false; // Disable all warnings + std::string inputFile; std::string outputFile = "a.out"; + + int context = 1; + + /* -=-=-=- DEBUG -=-=-=- */ + bool dumpArgs = false; }; -CompilerOptions parseArguments(int argc, char** argv, DiagnosticsEngine& diag) { +CompilerOptions parseArguments(int argc, char** argv) { CompilerOptions opts; cxxopts::Options options(argv[0], "Oxygen compiler"); @@ -21,11 +27,20 @@ CompilerOptions parseArguments(int argc, char** argv, DiagnosticsEngine& diag) { options.add_options() ("h,help", "Print help") - ("Wall", "Enable all warnings", cxxopts::value(opts.wall)) - ("Wextra", "Enable extra warning", cxxopts::value(opts.wextra)) + + ("Wignore", "Disable all warnings", cxxopts::value(opts.wignore)) ("Werror", "Produce errors instead of warnings", cxxopts::value(opts.werror)) + ("o", "Output file", cxxopts::value(opts.outputFile)) - ("input", "Input file", cxxopts::value(opts.outputFile)); + ("input", "Input file", cxxopts::value(opts.inputFile)) + + ("context", "Lines of context around diagnostic", cxxopts::value(opts.context)->default_value("1")) + +#ifdef DBG_OXY_DUMP_ARGS + ("dArgs", "Dumps parsed arguments to standard output", cxxopts::value(opts.dumpArgs)) +#endif +; // options.add_options() + options.parse_positional({"input"}); @@ -37,7 +52,17 @@ CompilerOptions parseArguments(int argc, char** argv, DiagnosticsEngine& diag) { } if (!result.count("input")) { - diag.report(DiagnosticLevel::Error, {"", 1, 1}, "No input file provided", "Provide a source file as positional argument"); + std::cerr << "No input file provded. Use --help\n"; + exit(1); + } + + if(opts.dumpArgs) { + std::cout << "PARSED ARGS:\n"; + std::cout << " Wignore -> " << opts.wignore << "\n"; + std::cout << " Werror -> " << opts.werror << "\n"; + std::cout << " output -> " << opts.outputFile << "\n"; + std::cout << " input -> " << opts.inputFile << "\n"; + std::cout << " context -> " << opts.context << "\n"; } return opts; diff --git a/include/Diagnostic/Colors.hpp b/include/Diagnostic/Colors.hpp new file mode 100644 index 0000000..d7defdf --- /dev/null +++ b/include/Diagnostic/Colors.hpp @@ -0,0 +1,13 @@ +#pragma once + +namespace Colors { + constexpr const char* Reset = "\033[0m"; + constexpr const char* Red = "\033[31m"; + constexpr const char* Yellow = "\033[33m"; + constexpr const char* Blue = "\033[34m"; + constexpr const char* Cyan = "\033[36m"; + constexpr const char* Gray = "\033[90m"; + + constexpr const char* Bold = "\033[1m"; + constexpr const char* Underline = "\033[4m"; +} diff --git a/include/Diagnostic/Diagnostic.hpp b/include/Diagnostic/Diagnostic.hpp index 03cca32..0b6979d 100644 --- a/include/Diagnostic/Diagnostic.hpp +++ b/include/Diagnostic/Diagnostic.hpp @@ -2,26 +2,15 @@ #include "Diagnostic/DiagnosticLevel.hpp" #include "Diagnostic/SourceLocation.hpp" -#include +#include "Diagnostic/DiagnosticNote.hpp" #include +#include struct Diagnostic { DiagnosticLevel level; SourceLocation loc; std::string message; std::string suggestion; - - void print() const { - std::string prefix; - switch (level) { - case DiagnosticLevel::Info: prefix = "info"; break; - case DiagnosticLevel::Warning: prefix = "warning"; break; - case DiagnosticLevel::Error: prefix = "error"; break; - } - - std::cerr << loc.toString() << ": " << prefix << ": " << message << "\n"; - if (!suggestion.empty()) { - std::cerr << " suggestion: " << suggestion << "\n"; - } - } + int length = 1; + std::vector notes; }; diff --git a/include/Diagnostic/DiagnosticNote.hpp b/include/Diagnostic/DiagnosticNote.hpp new file mode 100644 index 0000000..988d00b --- /dev/null +++ b/include/Diagnostic/DiagnosticNote.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "Diagnostic/SourceLocation.hpp" +#include + +struct DiagnosticNote { + SourceLocation loc; + std::string message; +}; diff --git a/include/Diagnostic/DiagnosticsEngine.hpp b/include/Diagnostic/DiagnosticsEngine.hpp index 5cd0f27..fc69501 100644 --- a/include/Diagnostic/DiagnosticsEngine.hpp +++ b/include/Diagnostic/DiagnosticsEngine.hpp @@ -1,26 +1,159 @@ #pragma once +#include +#include +#include +#include #include #include #include "Diagnostic/Diagnostic.hpp" +#include "Diagnostic/SourceManager.hpp" #include "Diagnostic/DiagnosticLevel.hpp" #include "Diagnostic/SourceLocation.hpp" +#include "Diagnostic/Colors.hpp" class DiagnosticsEngine { - std::vector diagnostics; public: - bool errorWarnings = false; + DiagnosticsEngine(SourceManager& sm, int contextLines = 1) + : srcMgr(sm), context(contextLines) {} - void report(DiagnosticLevel level, const SourceLocation& loc, const std::string& msg, const std::string& suggestion = "") { - auto lvl = level == DiagnosticLevel::Warning ? (errorWarnings ? DiagnosticLevel::Error : level) : level; - Diagnostic diag {lvl, loc, msg, suggestion}; - diagnostics.push_back(diag); - diag.print(); + void report(const Diagnostic& d) { + diags.push_back(d); + printDiagnostic(d); } - bool hasError() const { - for (auto& d : diagnostics) - if (d.level == DiagnosticLevel::Error) return true; + bool hasErrors() const { + for (auto &d : diags) { + if (d.level == DiagnosticLevel::Error) + return true; + } return false; } + + void setContext(int ctx) { context = ctx; } + +private: + SourceManager& srcMgr; + int context; + std::vector diags; + + static std::string expandTabs(const std::string& s, unsigned tabSize = 4) { + std::string out; + out.reserve(s.size()); + unsigned col = 0; + for (char ch : s) { + if (ch == '\t') { + unsigned spaces = tabSize - (col % tabSize); + out.append(spaces, ' '); + col += spaces; + } else { + out.push_back(ch); + ++col; + } + } + return out; + } + + static const char* levelToString(DiagnosticLevel level) { + switch (level) { + case DiagnosticLevel::Info: return "note"; + case DiagnosticLevel::Warning: return "warning"; + case DiagnosticLevel::Error: return "error"; + } + return ""; + } + + static const char* levelColor(DiagnosticLevel level) { + switch (level) { + case DiagnosticLevel::Info: return Colors::Blue; + case DiagnosticLevel::Warning: return Colors::Yellow; + case DiagnosticLevel::Error: return Colors::Red; + } + return ""; + } + + void printDiagnostic(const Diagnostic& d) { + // header -> file:line:column: : + std::ostringstream header; + header << d.loc.file << ":" << d.loc.line << ":" << d.loc.column << ": "; + + std::string lev = levelToString(d.level); + const char* color = levelColor(d.level); + + std::cerr << color << header.str() << lev << ": " << Colors::Bold << d.message << Colors::Reset << "\n"; + + if (!d.suggestion.empty()) { + std::cerr << Colors::Gray << " hint: " << Colors::Reset << d.suggestion << "\n"; + } + + printSourceSnippet(d.loc, d.length); + + for(const auto ¬e : d.notes) { + std::ostringstream noteHeader; + noteHeader << note.loc.file << ":" << note.loc.line << ":" << note.loc.column << ": "; + std::cerr << Colors::Cyan << noteHeader.str() << "note: " << Colors::Reset << note.message << "\n"; + printSourceSnippet(note.loc, 1); + } + } + + void printSourceSnippet(const SourceLocation& loc, int length) { + const auto& lines = srcMgr.getLines(loc.fileId); + if(lines.empty()) { + std::cerr << Colors::Gray << " (source not available)\n" << Colors::Reset; + return; + } + + int totalLines = (int)lines.size(); + int lineIdx = std::max(1, loc.line); + if (lineIdx > totalLines) { + std::cerr << Colors::Gray << " (location outside file: line " << loc.line << " > " << totalLines << ")\n" << Colors::Reset; + return; + } + + int start = std::max(1, loc.line - context); + int end = std::min(totalLines, loc.line + context); + + int width = 1; + int maxLine = end; + while(maxLine >= 10) { maxLine /= 10; ++width; } + + for (int L = start; L <= end; ++L) { + std::string rawLine = lines[L - 1]; + std::string expanded = expandTabs(rawLine); + + std::ostringstream gutter; + if (L == loc.line) { + gutter << Colors::Red << ">" << Colors::Reset << " "; + } else { + gutter << " "; + } + + std::ostringstream ln; + ln << std::setw(width) << L; + + std::cerr << gutter.str() << Colors::Gray << ln.str() << " | " << Colors::Reset; + std::cerr << expanded << "\n"; + + if (L == loc.line) { + int col = std::max(1, loc.column); + + std::ostringstream prefixSpaces; + prefixSpaces << " " << std::string(width, ' ') << " | "; + + std::cerr << prefixSpaces.str(); + + int lineLen = (int)expanded.size(); + int startCol = std::min(col, lineLen + 1); + int maxAvail = std::max(0, lineLen - (startCol - 1)); + int caretCount = (length > 0) ? std::min(length, std::max(1, maxAvail)) : 1; + + if (startCol > 1) + std::cerr << std::string(startCol - 1, ' '); + + std::cerr << Colors::Red << "^"; + for (int i = 1; i < caretCount; ++i) std::cerr << "~"; + std::cerr << Colors::Reset << "\n"; + } + } + } }; diff --git a/include/Diagnostic/SourceLocation.hpp b/include/Diagnostic/SourceLocation.hpp index c75c44b..d9cfa99 100644 --- a/include/Diagnostic/SourceLocation.hpp +++ b/include/Diagnostic/SourceLocation.hpp @@ -1,16 +1,10 @@ #pragma once -#include #include struct SourceLocation { std::string file; + int fileId = -1; int line; int column; - - std::string toString() const { - std::ostringstream oss; - oss << file << ":" << line << ":" << column; - return oss.str(); - } }; diff --git a/include/Diagnostic/SourceManager.hpp b/include/Diagnostic/SourceManager.hpp new file mode 100644 index 0000000..31e53d5 --- /dev/null +++ b/include/Diagnostic/SourceManager.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class SourceManager { +public: + struct FileInfo { + std::string path; + std::vector lines; + std::string content; + }; + + int loadFile(const std::string& path) { + if (fileIDs.count(path)) { + return fileIDs[path]; + } + + std::ifstream ifs(path); + if (!ifs) return -1; + + FileInfo info; + info.path = path; + + std::ostringstream ss; + ss << ifs.rdbuf(); + info.content = ss.str(); + + std::string line; + std::istringstream ls(info.content); + while (std::getline(ls, line)) { + info.lines.push_back(line); + } + + int id = nextID++; + files[id] = std::move(info); + + return id; + } + + const FileInfo* getFile(int fileID) const { + auto it = files.find(fileID); + return (it == files.end() ? nullptr : &it->second); + } + + const std::vector& getLines(int fileID) const { + static const std::vector empty; + auto it = files.find(fileID); + return it != files.end() ? it->second.lines : empty; + } + + const std::string& getContent(int fileID) const { + static const std::string empty; + auto it = files.find(fileID); + return it != files.end() ? it->second.content : empty; + } + + bool exists(int fileID) const { + auto it = files.find(fileID); + return it != files.end(); + } + +private: + int nextID = 1; + std::map files; + std::map fileIDs; +}; diff --git a/src/main.cpp b/src/main.cpp index c6a2dd3..98920ca 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,26 +1,50 @@ +#define DBG_OXY_DUMP_ARGS + #include "Args/CompilerOptions.hpp" +#include "Diagnostic/Diagnostic.hpp" #include "Diagnostic/DiagnosticLevel.hpp" #include "Diagnostic/DiagnosticsEngine.hpp" -#include +#include "Diagnostic/SourceManager.hpp" int main(int argc, char** argv) { - DiagnosticsEngine diag; - CompilerOptions opts = parseArguments(argc, argv, diag); + CompilerOptions opts = parseArguments(argc, argv); + SourceManager sm; + DiagnosticsEngine diag(sm, opts.context); + + auto mainId = sm.loadFile(opts.inputFile); - if (diag.hasError()) { - std::cerr << "Compilation aborted due to previous errors.\n"; + if (!sm.exists(mainId)) { + Diagnostic d; + d.level = DiagnosticLevel::Error; + d.loc = { opts.inputFile, 1, 1 }; + d.message = "cannot open input file"; + d.length = 6; + d.suggestion = "make sure file exists and you have permissions to read it"; + diag.report(d); return 1; } - diag.errorWarnings = opts.werror; - - if (opts.wall) { - diag.report(DiagnosticLevel::Info, {"main.cpp", 16, 9}, "Wall enabled, all warnings are active"); + { + Diagnostic w; + w.level = DiagnosticLevel::Warning; + w.loc = { opts.inputFile, mainId, 1, 8 }; + w.message = "unable to resolve 'std.io'"; + w.length = 6; + w.suggestion = "is it spelled corectly?"; + diag.report(w); } - std::cout << "Compiling " << opts.inputFile << " -> " << opts.outputFile << "\n"; + { + Diagnostic d; + d.level = DiagnosticLevel::Error; + d.loc = { opts.inputFile, mainId, 4, 5 }; + d.message = "Unexpected identifier 'println'"; + d.length = 7; + d.suggestion = "check if it is defined, or module is imported"; - diag.report(DiagnosticLevel::Warning, {"main.cpp", 21, 5}, "Sample warning"); + d.notes.push_back({ { opts.inputFile, mainId, 1, 8 }, "this unresovled import might contain identifier" }); + diag.report(d); + } return 0; } diff --git a/test.oxy b/test.oxy new file mode 100644 index 0000000..d4bd96b --- /dev/null +++ b/test.oxy @@ -0,0 +1,7 @@ +import std.io; + +func int main() { + println("hello, world"); + + return 0; +} From db1f1028b05306c2045f714e42d80ca68691b020 Mon Sep 17 00:00:00 2001 From: zimavi Date: Wed, 10 Dec 2025 16:10:22 +0200 Subject: [PATCH 3/6] feat: warning handling via flags Support for ignoring warnings as well as reporting them as errors --- include/Diagnostic/DiagnosticsEngine.hpp | 25 ++++++++++++++++++++---- src/main.cpp | 2 +- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/include/Diagnostic/DiagnosticsEngine.hpp b/include/Diagnostic/DiagnosticsEngine.hpp index fc69501..a72fd19 100644 --- a/include/Diagnostic/DiagnosticsEngine.hpp +++ b/include/Diagnostic/DiagnosticsEngine.hpp @@ -14,12 +14,27 @@ class DiagnosticsEngine { public: - DiagnosticsEngine(SourceManager& sm, int contextLines = 1) - : srcMgr(sm), context(contextLines) {} + DiagnosticsEngine(SourceManager& sm, int contextLines = 1, bool ignoreWarnings = false, bool warningsAreErrors = false) + : srcMgr(sm), context(contextLines), ignoreWarnings(ignoreWarnings), warningsAsErrors(warningsAreErrors) {} void report(const Diagnostic& d) { - diags.push_back(d); - printDiagnostic(d); + if (d.level == DiagnosticLevel::Warning && ignoreWarnings) + return; + + Diagnostic newD; + if (d.level == DiagnosticLevel::Warning && warningsAsErrors) { + newD.loc = d.loc; + newD.level = DiagnosticLevel::Error; + newD.length = d.length; + newD.message = d.message; + newD.notes = d.notes; + newD.suggestion = d.suggestion; + } else { + newD = d; + } + + diags.push_back(newD); + printDiagnostic(newD); } bool hasErrors() const { @@ -36,6 +51,8 @@ class DiagnosticsEngine { SourceManager& srcMgr; int context; std::vector diags; + bool ignoreWarnings = false; + bool warningsAsErrors = false; static std::string expandTabs(const std::string& s, unsigned tabSize = 4) { std::string out; diff --git a/src/main.cpp b/src/main.cpp index 98920ca..29a4f3d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,7 +9,7 @@ int main(int argc, char** argv) { CompilerOptions opts = parseArguments(argc, argv); SourceManager sm; - DiagnosticsEngine diag(sm, opts.context); + DiagnosticsEngine diag(sm, opts.context, opts.wignore, opts.werror); auto mainId = sm.loadFile(opts.inputFile); From c2c6bde60161552723f4824e889895f0e7560150 Mon Sep 17 00:00:00 2001 From: zimavi Date: Thu, 11 Dec 2025 12:10:58 +0200 Subject: [PATCH 4/6] fix: Highlight error on the line Highlight error on the line with bold red. Notes have length property --- include/Diagnostic/DiagnosticNote.hpp | 1 + include/Diagnostic/DiagnosticsEngine.hpp | 34 +++++++++++++++++++----- src/main.cpp | 2 +- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/include/Diagnostic/DiagnosticNote.hpp b/include/Diagnostic/DiagnosticNote.hpp index 988d00b..edb2da7 100644 --- a/include/Diagnostic/DiagnosticNote.hpp +++ b/include/Diagnostic/DiagnosticNote.hpp @@ -5,5 +5,6 @@ struct DiagnosticNote { SourceLocation loc; + int length = 1; std::string message; }; diff --git a/include/Diagnostic/DiagnosticsEngine.hpp b/include/Diagnostic/DiagnosticsEngine.hpp index a72fd19..c968599 100644 --- a/include/Diagnostic/DiagnosticsEngine.hpp +++ b/include/Diagnostic/DiagnosticsEngine.hpp @@ -109,7 +109,7 @@ class DiagnosticsEngine { std::ostringstream noteHeader; noteHeader << note.loc.file << ":" << note.loc.line << ":" << note.loc.column << ": "; std::cerr << Colors::Cyan << noteHeader.str() << "note: " << Colors::Reset << note.message << "\n"; - printSourceSnippet(note.loc, 1); + printSourceSnippet(note.loc, note.length); } } @@ -149,27 +149,47 @@ class DiagnosticsEngine { ln << std::setw(width) << L; std::cerr << gutter.str() << Colors::Gray << ln.str() << " | " << Colors::Reset; - std::cerr << expanded << "\n"; + //std::cerr << expanded << "\n"; if (L == loc.line) { int col = std::max(1, loc.column); - std::ostringstream prefixSpaces; - prefixSpaces << " " << std::string(width, ' ') << " | "; - - std::cerr << prefixSpaces.str(); - int lineLen = (int)expanded.size(); int startCol = std::min(col, lineLen + 1); int maxAvail = std::max(0, lineLen - (startCol - 1)); int caretCount = (length > 0) ? std::min(length, std::max(1, maxAvail)) : 1; + int prefixLen = (startCol > 1) ? startCol - 1 : 0; + if (prefixLen > expanded.size()) prefixLen = expanded.size(); + + int errorLen = 0; + if (prefixLen < expanded.size()) { + errorLen = std::min((int)(expanded.size() - prefixLen), caretCount); + } + + if (errorLen > 0) { + std::cerr << expanded.substr(0, prefixLen) + << Colors::Bold << Colors::Red + << expanded.substr(prefixLen, errorLen) + << Colors::Reset + << expanded.substr(prefixLen + errorLen) << "\n"; + } else { + std::cerr << expanded << "\n"; + } + + std::ostringstream prefixSpaces; + prefixSpaces << " " << std::string(width, ' ') << " | "; + + std::cerr << prefixSpaces.str(); + if (startCol > 1) std::cerr << std::string(startCol - 1, ' '); std::cerr << Colors::Red << "^"; for (int i = 1; i < caretCount; ++i) std::cerr << "~"; std::cerr << Colors::Reset << "\n"; + } else { + std::cerr << expanded << "\n"; } } } diff --git a/src/main.cpp b/src/main.cpp index 29a4f3d..52eb174 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,7 +42,7 @@ int main(int argc, char** argv) { d.length = 7; d.suggestion = "check if it is defined, or module is imported"; - d.notes.push_back({ { opts.inputFile, mainId, 1, 8 }, "this unresovled import might contain identifier" }); + d.notes.push_back({ { opts.inputFile, mainId, 1, 8 }, 6, "this unresovled import might contain identifier" }); diag.report(d); } From b795a1c2f11ed3e711cff8d627b5f5a7a78281a6 Mon Sep 17 00:00:00 2001 From: zimavi Date: Thu, 11 Dec 2025 12:43:42 +0200 Subject: [PATCH 5/6] feat: Allow debug comilation via CMake args --- CMakeLists.txt | 6 ++++++ src/main.cpp | 2 -- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d76dd17..5540b7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,7 @@ if(MSVC) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() + #add_subdirectory("thirdparty/lib") FetchContent_Declare( @@ -37,6 +38,11 @@ add_executable(oxygen "${MY_SOURCES}") target_compile_definitions("${CMAKE_PROJECT_NAME}" PUBLIC RESOURCE_PATH="${CMAKE_CURRENT_SOURCE_DIR}/resources/") #target_compile_definitions("${CMAKE_PROJECT_NAME}" PUBLIC RESOURCE_PATH="./resources/") +# ----- Preprocessor ----- +if(include_dump_args_flag) + target_compile_definitions("${CMAKE_PROJECT_NAME}" PRIVATE DBG_OXY_DUMP_ARGS=1) +endif() + target_include_directories(oxygen PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include/") #target_link_libraries(oxygen PRIVATE lib) diff --git a/src/main.cpp b/src/main.cpp index 52eb174..fe611de 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,3 @@ -#define DBG_OXY_DUMP_ARGS - #include "Args/CompilerOptions.hpp" #include "Diagnostic/Diagnostic.hpp" #include "Diagnostic/DiagnosticLevel.hpp" From 9af0b74ad17dde3bc4618bfc1693ee41e91f3f1f Mon Sep 17 00:00:00 2001 From: zimavi Date: Thu, 11 Dec 2025 12:46:47 +0200 Subject: [PATCH 6/6] fix: Update README Update README to be up-to-date with current stage --- README.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b4c9636..8c45857 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - # Oxygen A frankenstein compiler of C-like languages (C/C++) and Rust with its borrow system, using LLVM as backend. @@ -11,21 +10,27 @@ A frankenstein compiler of C-like languages (C/C++) and Rust with its borrow sys The project rely on CMake and LLVM, specifically, CMake 3.31.2, LLVM 19 respectively. It also uses C++ 23 standartd. -## Installation +Other dependencies are downloaded using CMake. + +## Building First, ensure you have CMake, LLVM and any sutable C++ compiler installed. Then clone repo and run CMake ```bash - git clone ... - cd oxygen mkdir build/ cd build cmake .. make ``` - + ## Usage/Examples -Well, as the compiler in earliest stage of development, there aren't much of an example to give, +The compiler accepts input file, as well as output file with `-o` flag. +Warnings can be ignored using `--Wignore` or produce errors instead, with `--Werror` flags. + +If defined `DBG_OXY_DUMP_ARGS` in source, or `-Dinclude_dump_args_flag=1` in CMake via shell, +the `-dArgs` flag will be availabe, to dump options structure. + +The compiler in earliest stage of development, so there aren't much of an example to give, nor what syntax or features it implies