ArkScript
A small, lisp-inspired, functional scripting language
Diagnostics.cpp
Go to the documentation of this file.
2
3#include <cassert>
4#include <sstream>
5#include <algorithm>
6#include <fmt/color.h>
7#include <fmt/ostream.h>
8
9#include <Ark/Constants.hpp>
10#include <Ark/Utils/Utils.hpp>
14
15namespace Ark::Diagnostics
16{
17 using namespace Ark::literals;
18
19 void showFileLocation(std::ostream& os, const ErrorLocation& loc)
20 {
21 if (loc.filename != ARK_NO_NAME_FILE)
22 fmt::print(os, "In file {}:{}\n", loc.filename, loc.start.line + 1);
23 }
24
25 void hintWithContext(std::ostream& os, const std::optional<CodeErrorContext>& maybe_context, const bool colorize)
26 {
27 if (!maybe_context)
28 return;
29
30 fmt::print(os, "{}", Printer::GhostLinePrefix);
31 fmt::print(
32 os,
33 "{: <{}}{}\n",
34 // padding os spaces
35 " ",
36 std::max(1_z, maybe_context->at.start.column), // fixing padding when the error is on the first character
37 // underline the parent of the error in red
38 fmt::styled(
39 maybe_context->is_macro_expansion ? "^ macro expansion started here" : "^ expression started here",
40 colorize ? fmt::fg(fmt::color::red) : fmt::text_style()));
41 }
42
44 const ErrorLocation& loc,
45 std::ostream& os,
46 const std::optional<CodeErrorContext>& maybe_context, // can not be populated at runtime, only compile time
47 const bool colorize)
48 {
49 assert(!(maybe_context && loc.wholeLineIsError()) && "Can not create error context when a context is given AND the whole line has to be underlined");
50
51 Printer source_printer(loc.filename, loc.start.line, loc.maybeEndLine(), colorize);
52 if (!source_printer.hasContent())
53 {
54 showFileLocation(os, loc);
55 return;
56 }
57
58 const bool ctx_same_file = maybe_context && maybe_context->filename == loc.filename;
59 const bool ctx_in_window = ctx_same_file && maybe_context && source_printer.coversLine(maybe_context->at.start.line);
60
61 if (ctx_same_file && !ctx_in_window)
62 source_printer.extendWindow(maybe_context->at.start.line);
63 else if (maybe_context && !ctx_same_file && !maybe_context->filename.empty())
64 {
65 // show the location of the parent of our error first
66 fmt::print(os, "Error originated from file {}:{}\n", maybe_context->filename, maybe_context->at.start.line + 1);
67
68 std::optional<decltype(internal::FilePos::line)> maybe_end_line = std::nullopt;
69 if (maybe_context->at.end)
70 maybe_end_line = maybe_context->at.end->line;
71 Printer printer(maybe_context->filename, maybe_context->at.start.line, maybe_end_line, colorize);
72
73 while (printer.hasContent())
74 {
75 printer.printLine(os);
76 if (printer.isTargetLine())
77 hintWithContext(os, maybe_context, colorize);
78 }
79
80 fmt::print(os, "\n");
81 }
82
83 showFileLocation(os, loc);
84
85 while (source_printer.hasContent())
86 {
87 const std::size_t i = source_printer.current();
88 const std::string& line = source_printer.currentLine();
89
90 source_printer.printLine(os);
91
92 // if the error context is in the current file, point to it as the parent of our error
93 if (ctx_same_file && i == maybe_context->at.start.line && i != loc.start.line)
94 hintWithContext(os, maybe_context, colorize);
95
96 // show where the error occurred
97 if (source_printer.isTargetLine() && !line.empty())
98 {
99 if (!loc.wholeLineIsError())
100 {
101 const std::size_t line_first_char = (line.find_first_not_of(" \t\v") == std::string::npos) ? 0 : line.find_first_not_of(" \t\v");
102
103 const std::size_t col_start = (i == loc.start.line) ? loc.start.column : line_first_char + 1;
104 // due to the `!loc.wholeLineIsError()` check, we are guaranteed to have a value in loc.end
105 const std::size_t col_end = (i == loc.end->line) ? loc.end->column : line.size();
106
107 // ignore the last line that is sometimes erroneous in multiline contexts
108 if (i == loc.end->line && loc.end->line != loc.start.line && col_start >= col_end)
109 continue;
110 fmt::print(os, "{}", Printer::GhostLinePrefix);
111
112 // show the error where it's at, using the normal process, if there is no context OR
113 // if the context line is different from the error line
114 if (!maybe_context || maybe_context->at.start.line != loc.start.line)
115 fmt::print(
116 os,
117 "{: <{}}{:~<{}}\n",
118 // padding of spaces
119 " ",
120 std::max(1_z, std::min(col_start, col_end)), // fixing padding when the error is on the first character
121 // underline the error in red
122 fmt::styled("^", colorize ? fmt::fg(fmt::color::red) : fmt::text_style()),
123 col_start < col_end ? col_end - col_start : 1);
124 // cppcheck-suppress knownConditionTrueFalse ; suppressing error so that the condition is explicit to the reader
125 else if (maybe_context && maybe_context->at.start.line == loc.start.line && i == loc.start.line)
126 {
127 const auto padding_size = std::max(1_z, maybe_context->at.start.column);
128 const std::string inner_padding =
129 // -2 to account for the │ and then └
130 (loc.start.column < padding_size || loc.start.column - padding_size < 2)
131 ? ""
132 : std::string(std::max(1_z, loc.start.column - padding_size - 1), ' ');
133
134 fmt::print(
135 os,
136 "{: <{}}{}{}{}\n",
137 // padding of spaces
138 " ",
139 padding_size,
140 // indicate where the parent is, with color
141 fmt::styled("│", colorize ? fmt::fg(fmt::color::red) : fmt::text_style()),
142 // yet another padding of spaces between the parent and error column (if need be)
143 inner_padding,
144 // underline the error in red
145 fmt::styled("└─ error", colorize ? fmt::fg(fmt::color::red) : fmt::text_style()));
146 // new line, some spacing between the error and the parent
147 fmt::print(
148 os,
149 "{}{: <{}}{}\n", Printer::GhostLinePrefix,
150 " ",
151 padding_size,
152 fmt::styled("│", colorize ? fmt::fg(fmt::color::red) : fmt::text_style()));
153 // new line, now show the "expression started here for the source"
154 fmt::print(
155 os,
156 "{}{: <{}}{}\n",
158 // padding of spaces
159 " ",
160 padding_size,
161 fmt::styled(
162 maybe_context->is_macro_expansion ? "└─ macro expansion started here" : "└─ expression started here",
163 colorize ? fmt::fg(fmt::color::red) : fmt::text_style()));
164 }
165 }
166 else
167 {
168 fmt::print(os, "{}", Printer::GhostLinePrefix);
169
170 // first non-whitespace character of the line
171 // +1 for the leading whitespace after ` |` before the code
172 const std::size_t col_start = line.find_first_not_of(" \t\v") + 1;
173
174 // highlight the current line but skip any leading whitespace
175 fmt::print(
176 os,
177 "{: <{}}{:~<{}}\n",
178 // padding of spaces
179 " ",
180 col_start,
181 // underline the whole line in red
182 fmt::styled("^", colorize ? fmt::fg(fmt::color::red) : fmt::text_style()),
183 line.size() - col_start);
184 }
185 }
186 }
187 }
188
189 void helper(std::ostream& os, const std::string& message, const bool colorize,
190 const std::string& filename, const internal::FileSpan& at,
191 const std::optional<CodeErrorContext>& maybe_context = std::nullopt)
192 {
195 .filename = filename,
196 .start = at.start,
197 .end = at.end },
198 os, maybe_context, colorize);
199
200 for (const auto& text : Utils::splitString(message, '\n'))
201 fmt::print(os, " {}\n", text);
202 }
203
204 std::string makeContextWithNode(const std::string& message, const internal::Node& node)
205 {
206 std::stringstream ss;
207
208 helper(
209 ss,
210 message,
211 true,
212 node.filename(),
213 node.position());
214
215 return ss.str();
216 }
217
218 void generate(const CodeError& e, std::ostream& os, bool colorize)
219 {
220#ifdef ARK_BUILD_EXE
221 if (const char* nocolor = std::getenv("NOCOLOR"); nocolor != nullptr)
222 colorize = false;
223#endif
224
225 helper(
226 os,
227 e.what(),
228 colorize,
230 e.context.at,
232 }
233}
Lots of utilities about string, filesystem and more.
Constants used by ArkScript.
#define ARK_NO_NAME_FILE
Definition Constants.hpp:27
Tools to report code errors nicely to the user.
User defined literals for Ark internals.
AST node used by the parser, optimizer and compiler.
Pretty printing utilities for diagnostics.
Source printer for diagnostics.
void extendWindow(std::size_t line_to_include)
Extend the window of lines to show, to include a given line. Useful to display the origin of an error...
const std::string & currentLine() const
static const std::string GhostLinePrefix
bool isTargetLine() const
Check if we printed the target line.
bool coversLine(std::size_t line_number) const
bool hasContent() const
Check if there are lines to print.
std::size_t current() const
void printLine(std::ostream &os)
Print the current line and advance by one.
A node of an Abstract Syntax Tree for ArkScript.
Definition Node.hpp:32
const std::string & filename() const noexcept
Return the filename in which this node was created.
Definition Node.cpp:164
FileSpan position() const noexcept
Get the span of the node (start and end)
Definition Node.cpp:159
void helper(std::ostream &os, const std::string &message, const bool colorize, const std::string &filename, const internal::FileSpan &at, const std::optional< CodeErrorContext > &maybe_context=std::nullopt)
void hintWithContext(std::ostream &os, const std::optional< CodeErrorContext > &maybe_context, const bool colorize)
ARK_API void generate(const CodeError &e, std::ostream &os=std::cout, bool colorize=true)
Generate a diagnostic from an error and print it to the standard output.
std::string makeContextWithNode(const std::string &message, const internal::Node &node)
Helper used by the compiler to generate a colorized context from a node.
void showFileLocation(std::ostream &os, const ErrorLocation &loc)
ARK_API void makeContext(const ErrorLocation &loc, std::ostream &os, const std::optional< CodeErrorContext > &maybe_context, bool colorize)
Helper to create a colorized context to report errors to the user.
std::vector< std::string > splitString(const std::string &source, const char sep)
Cut a string into pieces, given a character separator.
Definition Utils.hpp:31
const internal::FileSpan at
const std::string filename
CodeError thrown by the compiler (parser, macro processor, optimizer, and compiler itself)
const std::optional< CodeErrorContext > additional_context
const CodeErrorContext context
std::optional< internal::FilePos > end
std::string filename
Complete path to the file where the error is.
std::optional< decltype(internal::FilePos::line)> maybeEndLine() const noexcept
std::size_t column
0-indexed column number
Definition Position.hpp:23
std::size_t line
0-indexed line number
Definition Position.hpp:22
Describes a span for a node/atom in a file, its start position and end position.
Definition Position.hpp:35