From 8541085b27fdd61630913ac12edd0ecad65dfb89 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Tue, 18 Jun 2019 19:56:47 +0200 Subject: [PATCH] Added support for full error messages --- src/Diagnostics/Diagnostic.hpp | 12 +++- src/Diagnostics/DiagnosticCode.hpp | 6 +- src/Diagnostics/DiagnosticsHolder.cpp | 92 ++++++++++++++++++++++--- src/Diagnostics/DiagnosticsHolder.hpp | 36 +++------- src/Diagnostics/ErrorMessages-EN-US.cpp | 10 +++ src/Parser/Lexer.cpp | 6 +- tests/integration/DiagnosticsTests.cpp | 10 +++ 7 files changed, 133 insertions(+), 39 deletions(-) create mode 100644 src/Diagnostics/ErrorMessages-EN-US.cpp diff --git a/src/Diagnostics/Diagnostic.hpp b/src/Diagnostics/Diagnostic.hpp index dec322e..3d6a23c 100644 --- a/src/Diagnostics/Diagnostic.hpp +++ b/src/Diagnostics/Diagnostic.hpp @@ -1,7 +1,11 @@ +#include + #ifndef PORYGONLANG_DIAGNOSTIC_HPP #define PORYGONLANG_DIAGNOSTIC_HPP +#include +#include #include "DiagnosticSeverity.hpp" #include "DiagnosticCode.hpp" @@ -11,12 +15,14 @@ namespace Porygon::Diagnostics { DiagnosticCode _code; unsigned int _start; unsigned int _length; + std::vector _arguments; public: - Diagnostic(DiagnosticSeverity severity, DiagnosticCode code, unsigned int start, unsigned int length) { + Diagnostic(DiagnosticSeverity severity, DiagnosticCode code, unsigned int start, unsigned int length, std::vector arguments) { _severity = severity; _code = code; _start = start; _length = length; + _arguments = std::move(arguments); } DiagnosticSeverity GetSeverity() { @@ -34,6 +40,10 @@ namespace Porygon::Diagnostics { unsigned int GetLength() { return _length; } + + std::vector GetArguments(){ + return _arguments; + } }; } #endif //PORYGONLANG_DIAGNOSTIC_HPP diff --git a/src/Diagnostics/DiagnosticCode.hpp b/src/Diagnostics/DiagnosticCode.hpp index 9ea3434..dcc86da 100644 --- a/src/Diagnostics/DiagnosticCode.hpp +++ b/src/Diagnostics/DiagnosticCode.hpp @@ -5,14 +5,14 @@ namespace Porygon::Diagnostics { enum class DiagnosticCode { // Lex diagnostics - UnexpectedCharacter, + UnexpectedCharacter, InvalidStringControlCharacter, // Parse diagnostics - UnexpectedToken, + UnexpectedToken, // Bind diagnostics - NoBinaryOperationFound, + NoBinaryOperationFound, NoUnaryOperationFound, CantAssignVariable, VariableNotFound, diff --git a/src/Diagnostics/DiagnosticsHolder.cpp b/src/Diagnostics/DiagnosticsHolder.cpp index 856c5ad..41c245e 100644 --- a/src/Diagnostics/DiagnosticsHolder.cpp +++ b/src/Diagnostics/DiagnosticsHolder.cpp @@ -1,26 +1,32 @@ +#include + +#include // For std::unique_ptr #include "DiagnosticsHolder.hpp" +#include "ErrorMessages-EN-US.cpp" + using namespace Porygon::Diagnostics; vector DiagnosticsHolder::GetDiagnostics() { return _diagnostics; } -void DiagnosticsHolder::Log(DiagnosticSeverity severity, DiagnosticCode code, unsigned int start, unsigned int length) { - _diagnostics.emplace_back(severity, code, start, length); +void DiagnosticsHolder::Log(DiagnosticSeverity severity, DiagnosticCode code, unsigned int start, unsigned int length, + std::vector arguments) { + _diagnostics.emplace_back(severity, code, start, length, arguments); if (severity >= DiagnosticSeverity::Error){ _hasErrors = true; } } -void DiagnosticsHolder::LogError(DiagnosticCode code, unsigned int start, unsigned int length) { - Log(DiagnosticSeverity::Error, code, start, length); +void DiagnosticsHolder::LogError(DiagnosticCode code, unsigned int start, unsigned int length, std::vector arguments) { + Log(DiagnosticSeverity::Error, code, start, length, std::move(arguments)); } -void DiagnosticsHolder::LogWarning(DiagnosticCode code, unsigned int start, unsigned int length) { - Log(DiagnosticSeverity::Warning, code, start, length); +void DiagnosticsHolder::LogWarning(DiagnosticCode code, unsigned int start, unsigned int length, std::vector arguments) { + Log(DiagnosticSeverity::Warning, code, start, length, std::move(arguments)); } -void DiagnosticsHolder::LogInfo(DiagnosticCode code, unsigned int start, unsigned int length) { - Log(DiagnosticSeverity::Info, code, start, length); +void DiagnosticsHolder::LogInfo(DiagnosticCode code, unsigned int start, unsigned int length, std::vector arguments) { + Log(DiagnosticSeverity::Info, code, start, length, std::move(arguments)); } bool DiagnosticsHolder::HasErrors() { @@ -35,6 +41,76 @@ Diagnostic *DiagnosticsHolder::GetDiagnosticAt(int position) { return &_diagnostics[position]; } +size_t DiagnosticsHolder::GetLineFromPosition(size_t i) { + size_t topLimit = _lineStarts.size() - 1; + size_t bottomLimit = 0; + while (true){ + if (bottomLimit == topLimit){ + return bottomLimit; + } + size_t half = (topLimit - bottomLimit) / 2; + size_t pos = _lineStarts[half]; + size_t length = _lineLength[half]; + if (pos < i && pos + length > i){ + return half; + } + if (pos > i){ + bottomLimit = half; + } else if (pos < i){ + topLimit = half; + } else{ + return half; + } + } +} + +std::string SeverityToString(DiagnosticSeverity s){ + switch (s){ + case DiagnosticSeverity::Info: return "Info"; + case DiagnosticSeverity::Warning: return "Warning"; + case DiagnosticSeverity::Error: return "Error"; + } +} + +void findAndReplaceAll(std::string & data, const std::string& toSearch, const std::string& replaceStr) +{ + // Get the first occurrence + size_t pos = data.find(toSearch); + // Repeat till end is reached + while( pos != std::string::npos) + { + // Replace this occurrence of Sub String + data.replace(pos, toSearch.size(), replaceStr); + // Get the next occurrence from the current position + pos =data.find(toSearch, pos + replaceStr.size()); + } +} + +const string PrettyDiagnostic ( const char * format, vector arguments) +{ + string result = string(format); + for (int i = 0; i < arguments.size(); i++){ + auto keyToReplace = "{" + to_string(i) + "}"; + auto argument = arguments[i]; + findAndReplaceAll(result, keyToReplace, argument); + } + return result; +} + +std::string DiagnosticsHolder::GetFullErrorMessage(Diagnostic diagnostic) { + stringstream stream; + stream << "[" << SeverityToString(diagnostic.GetSeverity()) << "] "; + auto startPos = diagnostic.GetStartPosition(); + auto line = this -> GetLineFromPosition(startPos); + auto linePos = startPos - this ->GetStartPositionForLine(line); + stream << " (" << line << ", " << linePos << ") "; + auto unformatted = ErrorMessages.find(diagnostic.GetCode()); + if (unformatted != ErrorMessages.end()){ + stream << PrettyDiagnostic(unformatted->second, diagnostic.GetArguments()); + } + return stream.str(); +} + extern "C" int GetDiagnosticsCount (DiagnosticsHolder* diagnostics){ return diagnostics->DiagnosticsCount(); } diff --git a/src/Diagnostics/DiagnosticsHolder.hpp b/src/Diagnostics/DiagnosticsHolder.hpp index ee85418..fbdc795 100644 --- a/src/Diagnostics/DiagnosticsHolder.hpp +++ b/src/Diagnostics/DiagnosticsHolder.hpp @@ -4,6 +4,7 @@ #include #include +#include #include "DiagnosticSeverity.hpp" #include "DiagnosticCode.hpp" #include "Diagnostic.hpp" @@ -16,6 +17,7 @@ namespace Porygon::Diagnostics { vector _diagnostics; vector _lineStarts; vector _lineLength; + public: explicit DiagnosticsHolder(const u16string& str) { _hasErrors = false; @@ -35,13 +37,13 @@ namespace Porygon::Diagnostics { _diagnostics.clear(); } - void Log(DiagnosticSeverity severity, DiagnosticCode code, unsigned int start, unsigned int length); + void Log(DiagnosticSeverity severity, DiagnosticCode code, unsigned int start, unsigned int length, std::vector arguments = {}); - void LogError(DiagnosticCode code, unsigned int start, unsigned int length); + void LogError(DiagnosticCode code, unsigned int start, unsigned int length, std::vector arguments = {}); - void LogWarning(DiagnosticCode code, unsigned int start, unsigned int length); + void LogWarning(DiagnosticCode code, unsigned int start, unsigned int length, std::vector arguments = {}); - void LogInfo(DiagnosticCode code, unsigned int start, unsigned int length); + void LogInfo(DiagnosticCode code, unsigned int start, unsigned int length, std::vector arguments = {}); bool HasErrors(); @@ -51,28 +53,12 @@ namespace Porygon::Diagnostics { Diagnostic *GetDiagnosticAt(int position); - size_t GetLineFromPosition(size_t i){ - size_t topLimit = _lineStarts.size() - 1; - size_t bottomLimit = 0; - while (true){ - if (bottomLimit == topLimit){ - return bottomLimit; - } - size_t half = (topLimit - bottomLimit) / 2; - size_t pos = _lineStarts[half]; - size_t length = _lineLength[half]; - if (pos < i && pos + length > i){ - return half; - } - if (pos > i){ - bottomLimit = half; - } else if (pos < i){ - topLimit = half; - } else{ - return half; - } - } + size_t GetLineFromPosition(size_t i); + size_t GetStartPositionForLine(size_t i){ + return _lineStarts[i]; } + + std::string GetFullErrorMessage(Diagnostic diagnostic); }; } diff --git a/src/Diagnostics/ErrorMessages-EN-US.cpp b/src/Diagnostics/ErrorMessages-EN-US.cpp new file mode 100644 index 0000000..14636ca --- /dev/null +++ b/src/Diagnostics/ErrorMessages-EN-US.cpp @@ -0,0 +1,10 @@ + +#include +#include "DiagnosticCode.hpp" + +namespace Porygon::Diagnostics { + const std::unordered_map ErrorMessages{ // NOLINT(cert-err58-cpp) + {DiagnosticCode ::UnexpectedCharacter, "Encountered an unexpected character: '{0}'."}, + {DiagnosticCode ::InvalidStringControlCharacter, "'{0}' is not a valid control character."}, + }; +} \ No newline at end of file diff --git a/src/Parser/Lexer.cpp b/src/Parser/Lexer.cpp index 0d3df63..3773bbb 100644 --- a/src/Parser/Lexer.cpp +++ b/src/Parser/Lexer.cpp @@ -289,7 +289,8 @@ namespace Porygon::Parser { } auto closeToken = this->Next(); if (closeToken != c) { - this->ScriptData->Diagnostics->LogError(Diagnostics::DiagnosticCode::UnexpectedCharacter, this->_position - 1, 1); + const char* s = string(1, closeToken).c_str(); + this->ScriptData->Diagnostics->LogError(Diagnostics::DiagnosticCode::UnexpectedCharacter, this->_position - 1, 1, { s }); return new SimpleToken(TokenKind::BadToken, start, end - start + 1); } @@ -303,8 +304,9 @@ namespace Porygon::Parser { if (ControlCharacters.find(c) != ControlCharacters.end()) { stream << ControlCharacters.at(c); } else { + auto v = string(1, c).c_str(); this->ScriptData->Diagnostics->LogError(Diagnostics::DiagnosticCode::InvalidStringControlCharacter, - start + 1 + i, 1); + start + 1 + i, 1, {v}); stream << c; } } else { diff --git a/tests/integration/DiagnosticsTests.cpp b/tests/integration/DiagnosticsTests.cpp index 530f919..1ed9e31 100644 --- a/tests/integration/DiagnosticsTests.cpp +++ b/tests/integration/DiagnosticsTests.cpp @@ -41,6 +41,16 @@ TEST_CASE( "Get diagnostic line", "[integration]" ) { delete script; } +TEST_CASE( "Get full diagnostic message", "[integration]" ) { + auto script = Script::Create(uR"( +"\x" +)"); + REQUIRE(script->Diagnostics -> HasErrors()); + auto diags = script->Diagnostics -> GetDiagnostics(); + auto msg = script -> Diagnostics -> GetFullErrorMessage(diags[0]); + REQUIRE(msg == "[Error] (1, 2) 'x' is not a valid control character."); + delete script; +} #endif \ No newline at end of file