// FIXME writeExcerpt does not work well with the last line in a file #include #include #include "zen/config.hpp" #include "bolt/CST.hpp" #include "bolt/Type.hpp" #include "bolt/DiagnosticEngine.hpp" #include "bolt/Diagnostics.hpp" #define ANSI_RESET "\u001b[0m" #define ANSI_BOLD "\u001b[1m" #define ANSI_UNDERLINE "\u001b[4m" #define ANSI_REVERSED "\u001b[7m" #define ANSI_FG_BLACK "\u001b[30m" #define ANSI_FG_RED "\u001b[31m" #define ANSI_FG_GREEN "\u001b[32m" #define ANSI_FG_YELLOW "\u001b[33m" #define ANSI_FG_BLUE "\u001b[34m" #define ANSI_FG_CYAN "\u001b[35m" #define ANSI_FG_MAGENTA "\u001b[36m" #define ANSI_FG_WHITE "\u001b[37m" #define ANSI_BG_BLACK "\u001b[40m" #define ANSI_BG_RED "\u001b[41m" #define ANSI_BG_GREEN "\u001b[42m" #define ANSI_BG_YELLOW "\u001b[43m" #define ANSI_BG_BLUE "\u001b[44m" #define ANSI_BG_CYAN "\u001b[45m" #define ANSI_BG_MAGENTA "\u001b[46m" #define ANSI_BG_WHITE "\u001b[47m" namespace bolt { template T countDigits(T number) { if (number == 0) { return 1; } return std::ceil(std::log10(number+1)); } Diagnostic::Diagnostic(DiagnosticKind Kind): std::runtime_error("a compiler error occurred without being caught"), Kind(Kind) {} static std::string describe(NodeKind Type) { switch (Type) { case NodeKind::Identifier: return "an identifier starting with a lowercase letter"; case NodeKind::IdentifierAlt: return "an identifier starting with a capital letter"; case NodeKind::CustomOperator: return "an operator"; case NodeKind::IntegerLiteral: return "an integer literal"; case NodeKind::EndOfFile: return "end-of-file"; case NodeKind::BlockStart: return "the start of a new indented block"; case NodeKind::BlockEnd: return "the end of the current indented block"; case NodeKind::LineFoldEnd: return "the end of the current line-fold"; case NodeKind::LParen: return "'('"; case NodeKind::RParen: return "')'"; case NodeKind::LBrace: return "'['"; case NodeKind::RBrace: return "']'"; case NodeKind::LBracket: return "'{'"; case NodeKind::RBracket: return "'}'"; case NodeKind::Colon: return "':'"; case NodeKind::Comma: return "','"; case NodeKind::Equals: return "'='"; case NodeKind::StringLiteral: return "a string literal"; case NodeKind::Dot: return "'.'"; case NodeKind::DotDot: return "'..'"; case NodeKind::Tilde: return "'~'"; case NodeKind::RArrow: return "'->'"; case NodeKind::RArrowAlt: return "'=>'"; case NodeKind::PubKeyword: return "'pub'"; case NodeKind::LetKeyword: return "'let'"; case NodeKind::MutKeyword: return "'mut'"; case NodeKind::MatchKeyword: return "'match'"; case NodeKind::ReturnKeyword: return "'return'"; case NodeKind::TypeKeyword: return "'type'"; default: ZEN_UNREACHABLE } } std::string describe(const Type* Ty) { switch (Ty->getKind()) { case TypeKind::Var: { auto TV = static_cast(Ty); if (TV->getVarKind() == VarKind::Rigid) { return static_cast(TV)->Name; } return "a" + std::to_string(TV->Id); } case TypeKind::Arrow: { auto Y = static_cast(Ty); std::ostringstream Out; Out << "("; bool First = true; for (auto PT: Y->ParamTypes) { if (First) First = false; else Out << ", "; Out << describe(PT); } Out << ") -> " << describe(Y->ReturnType); return Out.str(); } case TypeKind::Con: { auto Y = static_cast(Ty); std::ostringstream Out; if (!Y->DisplayName.empty()) { Out << Y->DisplayName; } else { Out << "C" << Y->Id; } for (auto Arg: Y->Args) { Out << " " << describe(Arg); } return Out.str(); } case TypeKind::Tuple: { std::ostringstream Out; auto Y = static_cast(Ty); Out << "("; if (Y->ElementTypes.size()) { auto Iter = Y->ElementTypes.begin(); Out << describe(*Iter++); while (Iter != Y->ElementTypes.end()) { Out << ", " << describe(*Iter++); } } Out << ")"; return Out.str(); } case TypeKind::TupleIndex: { auto Y = static_cast(Ty); return describe(Y->Ty) + "." + std::to_string(Y->I); } } } DiagnosticStore::~DiagnosticStore() { for (auto D: Diagnostics) { delete D; } } ConsoleDiagnostics::ConsoleDiagnostics(std::ostream& Out): Out(Out) {} void ConsoleDiagnostics::setForegroundColor(Color C) { if (EnableColors) { switch (C) { case Color::None: break; case Color::Black: Out << ANSI_FG_BLACK; break; case Color::White: Out << ANSI_FG_WHITE; break; case Color::Red: Out << ANSI_FG_RED; break; case Color::Yellow: Out << ANSI_FG_YELLOW; break; case Color::Green: Out << ANSI_FG_GREEN; break; case Color::Blue: Out << ANSI_FG_BLUE; break; case Color::Cyan: Out << ANSI_FG_CYAN; break; case Color::Magenta: Out << ANSI_FG_MAGENTA; break; } } } void ConsoleDiagnostics::setBackgroundColor(Color C) { if (EnableColors) { switch (C) { case Color::None: break; case Color::Black: Out << ANSI_BG_BLACK; break; case Color::White: Out << ANSI_BG_WHITE; break; case Color::Red: Out << ANSI_BG_RED; break; case Color::Yellow: Out << ANSI_BG_YELLOW; break; case Color::Green: Out << ANSI_BG_GREEN; break; case Color::Blue: Out << ANSI_BG_BLUE; break; case Color::Cyan: Out << ANSI_BG_CYAN; break; case Color::Magenta: Out << ANSI_BG_MAGENTA; break; } } } void ConsoleDiagnostics::setBold(bool Enable) { if (Enable) { Out << ANSI_BOLD; } } void ConsoleDiagnostics::setItalic(bool Enable) { if (Enable) { // TODO } } void ConsoleDiagnostics::setUnderline(bool Enable) { if (Enable) { Out << ANSI_UNDERLINE; } } void ConsoleDiagnostics::resetStyles() { if (EnableColors) { Out << ANSI_RESET; } } void ConsoleDiagnostics::writeGutter( std::size_t GutterWidth, std::size_t Line ) { auto LineNumberDigitCount = countDigits(Line); auto LeadingSpaces = GutterWidth - LineNumberDigitCount; Out << " "; setForegroundColor(Color::Black); setBackgroundColor(Color::White); for (std::size_t i = 0; i < LeadingSpaces; i++) { Out << ' '; } Out << Line; resetStyles(); Out << " "; } void ConsoleDiagnostics::writeHighlight( std::size_t GutterWidth, TextRange Range, Color HighlightColor, std::size_t Line, std::size_t LineLength ) { if (Line < Range.Start.Line || Range.End.Line < Line) { return; } Out << " "; setBackgroundColor(Color::White); for (std::size_t i = 0; i < GutterWidth; i++) { Out << ' '; } resetStyles(); Out << ' '; std::size_t start_column = Range.Start.Line == Line ? Range.Start.Column : 1; std::size_t end_column = Range.End.Line == Line ? Range.End.Column : LineLength+1; for (std::size_t i = 1; i < start_column; i++) { Out << ' '; } setForegroundColor(HighlightColor); for (std::size_t i = start_column; i < end_column; i++) { Out << '~'; } resetStyles(); Out << '\n'; } void ConsoleDiagnostics::writeExcerpt( const TextFile& File, TextRange ToPrint, TextRange ToHighlight, Color HighlightColor ) { auto Text = File.getText(); auto StartPos = ToPrint.Start; auto EndPos = ToPrint.End; auto StartLine = StartPos.Line-1 > ExcerptLinesPre ? StartPos.Line - ExcerptLinesPost : 1; auto StartOffset = File.getStartOffset(StartLine); auto EndLine = std::min(File.getLineCount(), EndPos.Line + ExcerptLinesPost); auto EndOffset = File.getStartOffset(EndLine+1); auto GutterWidth = std::max(2, countDigits(EndLine+1)); auto HighlightStart = ToHighlight.Start; auto HighlightEnd = ToHighlight.End; auto HighlightRange = TextRange { HighlightStart, HighlightEnd }; std::size_t CurrColumn = 1; std::size_t CurrLine = StartLine; writeGutter(GutterWidth, CurrLine); for (std::size_t i = StartOffset; i < EndOffset; i++) { auto C = Text[i]; Out << C; if (C == '\n') { writeHighlight(GutterWidth, HighlightRange, HighlightColor, CurrLine, CurrColumn); if (CurrLine == EndLine && C == '\n') { break; } CurrLine++; writeGutter(GutterWidth, CurrLine); CurrColumn = 1; } else { CurrColumn++; } } } void ConsoleDiagnostics::write(const std::string_view& S) { Out << S; } void ConsoleDiagnostics::write(std::size_t I) { Out << I; } void ConsoleDiagnostics::writeBinding(const ByteString& Name) { write("'"); write(Name); write("'"); } void ConsoleDiagnostics::writeType(const Type* Ty) { setForegroundColor(Color::Green); write(describe(Ty)); resetStyles(); } void ConsoleDiagnostics::writeType(std::size_t I) { setForegroundColor(Color::Green); write(I); resetStyles(); } void ConsoleDiagnostics::writeNode(const Node* N) { auto Range = N->getRange(); writeExcerpt(N->getSourceFile()->getTextFile(), Range, Range, Color::Red); } void ConsoleDiagnostics::writeLoc(const TextFile& File, const TextLoc& Loc) { setForegroundColor(Color::Yellow); write(File.getPath()); write(":"); write(Loc.Line); write(":"); write(Loc.Column); write(":"); resetStyles(); } void ConsoleDiagnostics::writePrefix(const Diagnostic& D) { setForegroundColor(Color::Red); setBold(true); write("error: "); resetStyles(); } void ConsoleDiagnostics::writeTypeclassName(const ByteString& Name) { setForegroundColor(Color::Magenta); write(Name); resetStyles(); } void ConsoleDiagnostics::writeTypeclassSignature(const TypeclassSignature& Sig) { setForegroundColor(Color::Magenta); write(Sig.Id); for (auto TV: Sig.Params) { write(" "); write(describe(TV)); } resetStyles(); } void ConsoleDiagnostics::addDiagnostic(Diagnostic* D) { printDiagnostic(*D); // Since this DiagnosticEngine is expected to own the diagnostic, we simply // destroy the processed diagnostic so that there are no memory leaks. delete D; } void ConsoleDiagnostics::printDiagnostic(const Diagnostic& D) { switch (D.getKind()) { case DiagnosticKind::BindingNotFound: { auto E = static_cast(D); writePrefix(E); write("binding "); writeBinding(E.Name); write(" was not found\n\n"); if (E.Initiator != nullptr) { auto Range = E.Initiator->getRange(); //std::cerr << Range.Start.Line << ":" << Range.Start.Column << "-" << Range.End.Line << ":" << Range.End.Column << "\n"; writeExcerpt(E.Initiator->getSourceFile()->getTextFile(), Range, Range, Color::Red); Out << "\n"; } break; } case DiagnosticKind::UnexpectedToken: { auto E = static_cast(D); writePrefix(E); writeLoc(E.File, E.Actual->getStartLoc()); write(" expected "); switch (E.Expected.size()) { case 0: write("nothing"); break; case 1: write(describe(E.Expected[0])); break; default: auto Iter = E.Expected.begin(); Out << describe(*Iter++); NodeKind Prev = *Iter++; while (Iter != E.Expected.end()) { write(", "); write(describe(Prev)); Prev = *Iter++; } write(" or "); write(describe(Prev)); break; } write(" but instead got '"); write(E.Actual->getText()); write("'\n\n"); writeExcerpt(E.File, E.Actual->getRange(), E.Actual->getRange(), Color::Red); write("\n"); break; } case DiagnosticKind::UnexpectedString: { auto E = static_cast(D); writePrefix(E); writeLoc(E.File, E.Location); write(" unexpected '"); for (auto Chr: E.Actual) { switch (Chr) { case '\\': write("\\\\"); break; case '\'': write("\\'"); break; default: write(Chr); break; } } write("'\n\n"); TextRange Range { E.Location, E.Location + E.Actual }; writeExcerpt(E.File, Range, Range, Color::Red); write("\n"); break; } case DiagnosticKind::UnificationError: { auto E = static_cast(D); writePrefix(E); auto Left = E.Left->resolve(E.LeftPath); auto Right = E.Right->resolve(E.RightPath); write("the types "); writeType(Left); write(" and "); writeType(Right); write(" failed to match\n\n"); if (E.Source) { writeNode(E.Source); Out << "\n"; } if (!E.LeftPath.empty()) { setForegroundColor(Color::Yellow); setBold(true); write(" info: "); resetStyles(); write("the type "); writeType(Left); write(" occurs in the full type "); writeType(E.Left); write("\n\n"); } if (!E.RightPath.empty()) { setForegroundColor(Color::Yellow); setBold(true); write(" info: "); resetStyles(); write("the type "); writeType(Right); write(" occurs in the full type "); writeType(E.Right); write("\n\n"); } break; } case DiagnosticKind::TypeclassMissing: { auto E = static_cast(D); writePrefix(E); write("the type class "); writeTypeclassSignature(E.Sig); write(" is missing from the declaration's type signature\n\n"); writeNode(E.Decl); write("\n\n"); break; } case DiagnosticKind::InstanceNotFound: { auto E = static_cast(D); writePrefix(E); write("a type class instance "); writeTypeclassName(E.TypeclassName); write(" "); writeType(E.Ty); write(" was not found.\n\n"); writeNode(E.Source); write("\n"); break; } case DiagnosticKind::ClassNotFound: { auto E = static_cast(D); writePrefix(E); write("the type class "); writeTypeclassName(E.Name); write(" was not found.\n\n"); break; } case DiagnosticKind::TupleIndexOutOfRange: { auto E = static_cast(D); writePrefix(E); write("the index "); writeType(E.I); write(" is out of range for tuple "); writeType(E.Tuple); break; } case DiagnosticKind::InvalidTypeToTypeclass: { auto E = static_cast(D); writePrefix(E); write("the type "); writeType(E.Actual); write(" was applied to type class names "); bool First = true; for (auto Class: E.Classes) { if (First) First = false; else write(", "); writeTypeclassName(Class); } write(" but this is invalid\n\n"); break; } } } }