A diagnostic library for printing compiler diagnostics.
- The diagnostic architecture is based on Google's Carbon Language diagnostic implementation, but not the exact implementation.
CowString
is inspired by Rust'sCow
implementation.- The error reporting or rendering on the terminal is closer to the Rust's.
Stream
has similar interface to llvm'sraw_ostream
with extensions.
Span
is an absolute position inside the source string. This does not accept signed positions.LocRelSpan
is a relative position that represent a subsection of the source that will be print by the diagnostic. It is similar toSpan
if the subsection is same as the whole source. This does not accept signed positions.MarkerRelSpan
represent position relative to marker, which could be a negative offset.
Source: |xxxxxxxxxxxxxxx|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|xxxxxxxxxxx|
^ | |
| | |
Span |xxxxxxxxxxxxxxxxxx^^^^^xxxxxxxxxxxxxxxx|
^ ^
| |
LocRelSpan MarkerRelSpan
This provides more information to the current diagnostic that will be rendered below the marked span.
Source: |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
^ ^
| |
| This is an error
|- This is a second error.
This helps the user to add multiple sub-diagnostics that is related to the current parent diagnostics; such as adding history or backtrace to the current diagnostic.
This is a type of location that allows the user to pass individual tokens with custom styles (color and bold). It does not allow escape characters, and they'll be escaped.
This is a single string source that supports newlines and could be split into multiple lines or skipped if it has too many lines that don't have diagnostic.
This is a custom struct or class that inherits from the BasicDiagnosticConverter<LocT, DiagnosticKind>
. It allows the user to customise or build diagnostic location to the diagnostic builder.
This an object that consumes the diagnostics, which could a consumer that prints the diagnostics on the terminal or sorts the consumers. These consumers can be plugged into each other; such as plugging sort and stream consumers, which will sort first then print it on the terminal. There are three predefined consumers:
BasicStreamDiagnosticConsumer<DiagnosticKind>
This outputs the diagnostic to theStream
BasicErrorTrackingDiagnosticConsumer<DiagnosticKind>
This tracks the error. If it encounters error, the error flag will be turned on.BasicSortingDiagnosticConsumer<DiagnosticKind>
This sorts the diagnostics and needs a explicit flush.
This is an object that wraps standard out file descriptor, std::ostream
, or llvm::raw_ostream
(if DARK_LLVM_OS_STREAM
is defined). This has almost similar interface to llvm's raw_ostream
like change_color
, reset_color
, Color
(enum)
#include <diagnostics.hpp>
int main() {
dark::out().change_color(dark::Stream::RED) << "Hello";
dark::out().change_color(dark::Stream::Color::GREEN, { .bold = true }) << " World";
dark::out().reset_color();
}
This uses std::format
under the hood. Therefore, any valid format string is a valid string. However, the user can specify data types that will be checked at compile-time.
Valid type specifier:
c
for chars
for string that could be both dynamic or static that will be stored inside theCowString
u8
,u16
,u32
, andu64
i8
,i16
,i32
, andi64
f32
andf64
- no type indicates any
"This is {s} with {}" // valid
"This is {u32:<20} with {}" // valid
"This is {0} with {}" // invalid
This library is a C++ header only library so it very easy to use. You can clone the repo and add the include path to the include
folder inside the cloned repo.
- Clone the repo
git clone [email protected]:amitsingh19975/diagnostics.git
- Include it in your project
# If you're using CMake
include_directories(repo_path/diagnostics/include)
# You could pass -I flag while compiling
cc -Irepo_path/diagnostics/include my_program
- Including the header inside the C++ project
#include <diagnostics.hpp>
int main() {
// ....
}
#include <diagnostics.hpp>
enum class DiagnosticKind {
InvalidFunctionDefinition,
InvalidFunctionPrototype
};
/*
* 1. line number and column number are 1-based so if the user gives 0 then the location will not be printed.
*/
struct SimpleConverter: BasicDiagnosticConverter<unsigned, DiagnosticKind> {
auto convert_loc(unsigned loc, [[maybe_unused]] builder_t builder) const -> dark::DiagnosticLocation override {
return dark::DiagnosticLocation {
.filename = "test.cpp",
.source = dark::BasicDiagnosticLocationItem {
.source = "void test( int a, int c );", // Subsection
.line_number = 1, // Line where the marker starts.
.column_number = loc + 1, // Column where the marker starts
.source_location = 0, // This is an absolute position inside the original source.
.length = 2 // Length of the diagnostic marker
}
};
}
};
int main() {
auto consumer = dark::BasicStreamDiagnosticConsumer<DiagnosticKind>(dark::out());
auto converter = SimpleConverter();
auto emitter = dark::BasicDiagnosticEmitter(
converter,
consumer
);
static constexpr auto InvalidFunctionDefinition = dark_make_diagnostic_with_kind(
DiagnosticKind::InvalidFunctionDefinition,
"Invalid function definition for {s} at {u32}"
);
static constexpr auto InvalidFunctionPrototype = dark_make_diagnostic_with_kind(
DiagnosticKind::InvalidFunctionPrototype,
"The prototype is defined here"
);
emitter
.error(1, InvalidFunctionDefinition, "Test", 0u)
.context(
dark::DiagnosticContext()
.insert(")", 2)
.insert_marker_rel(")", 3)
.del(dark::MarkerRelSpan(4, 8))
.error(
"prototype does not match the defination",
dark::LocRelSpan(0, 2),
dark::Span(19, 24)
)
.warn(dark::Span(6, 10), dark::Span(25, 27))
.note("Try to fix the error")
)
.sub_diagnostic()
.warn(0, InvalidFunctionPrototype)
.build()
.emit();
}
You can see more examples inside the example folder.