ArkScript
A small, fast, functional and scripting language for video games
Exceptions.cpp
Go to the documentation of this file.
1#include <Ark/Exceptions.hpp>
2
3#include <sstream>
4#include <algorithm>
5#include <fmt/core.h>
6#include <fmt/color.h>
7#include <fmt/ostream.h>
8
9#include <Ark/Constants.hpp>
10#include <Ark/Utils.hpp>
11#include <Ark/Files.hpp>
12#include <Ark/Literals.hpp>
14
15namespace Ark::Diagnostics
16{
23
24 inline bool isPairableChar(const char c)
25 {
26 return c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}';
27 }
28
29 void colorizeLine(const std::string& line, LineColorContextCounts& line_color_context_counts, std::ostream& ss)
30 {
31 // clang-format off
32 constexpr std::array pairing_color {
33 fmt::color::light_blue,
34 fmt::color::light_green,
35 fmt::color::light_salmon,
36 fmt::color::light_yellow,
37 fmt::color::light_cyan,
38 fmt::color::light_coral
39 };
40 // clang-format on
41 constexpr std::size_t pairing_color_size = pairing_color.size();
42
43 for (const char& c : line)
44 {
45 if (isPairableChar(c))
46 {
47 std::size_t pairing_color_index = 0;
48
49 switch (c)
50 {
51 case '(':
52 pairing_color_index = static_cast<std::size_t>(std::abs(line_color_context_counts.open_parentheses)) % pairing_color_size;
53 line_color_context_counts.open_parentheses++;
54 break;
55 case ')':
56 line_color_context_counts.open_parentheses--;
57 pairing_color_index = static_cast<std::size_t>(std::abs(line_color_context_counts.open_parentheses)) % pairing_color_size;
58 break;
59 case '[':
60 pairing_color_index = static_cast<std::size_t>(std::abs(line_color_context_counts.open_square_braces)) % pairing_color_size;
61 line_color_context_counts.open_square_braces++;
62 break;
63 case ']':
64 line_color_context_counts.open_square_braces--;
65 pairing_color_index = static_cast<std::size_t>(std::abs(line_color_context_counts.open_square_braces)) % pairing_color_size;
66 break;
67 case '{':
68 pairing_color_index = static_cast<std::size_t>(std::abs(line_color_context_counts.open_curly_braces)) % pairing_color_size;
69 line_color_context_counts.open_curly_braces++;
70 break;
71 case '}':
72 line_color_context_counts.open_curly_braces--;
73 pairing_color_index = static_cast<std::size_t>(std::abs(line_color_context_counts.open_curly_braces)) % pairing_color_size;
74 break;
75 default:
76 break;
77 }
78
79 fmt::print(ss, "{}", fmt::styled(c, fmt::fg(pairing_color[pairing_color_index])));
80 }
81 else
82 fmt::print(ss, "{}", c);
83 }
84 }
85
86 void makeContext(std::ostream& os, const std::string& code, const std::size_t target_line, const std::size_t col_start, const std::size_t sym_size, const bool colorize)
87 {
88 using namespace Ark::literals;
89
90 const std::vector<std::string> ctx = Utils::splitString(code, '\n');
91 if (target_line >= ctx.size())
92 return;
93
94 const std::size_t first_line = target_line >= 3 ? target_line - 3 : 0;
95 const std::size_t last_line = (target_line + 3) <= ctx.size() ? target_line + 3 : ctx.size();
96 std::size_t overflow = (col_start + sym_size < ctx[target_line].size()) ? 0 : col_start + sym_size - ctx[target_line].size(); // number of characters that are on more lines below
97 LineColorContextCounts line_color_context_counts;
98
99 for (auto i = first_line; i < last_line; ++i)
100 {
101 fmt::print(os, "{: >5} |{}", i + 1, !ctx[i].empty() ? " " : "");
102 if (colorize)
103 colorizeLine(ctx[i], line_color_context_counts, os);
104 else
105 fmt::print(os, "{}", ctx[i]);
106 fmt::print(os, "\n");
107
108 if (i == target_line || (i > target_line && overflow > 0))
109 {
110 fmt::print(os, " |");
111 // if we have an overflow then we start at the beginning of the line
112 const std::size_t curr_col_start = (overflow == 0) ? col_start : 0;
113 // if we have an overflow, it is used as the end of the line
114 const std::size_t col_end = (i == target_line) ? std::min<std::size_t>(col_start + sym_size, ctx[target_line].size())
115 : std::min<std::size_t>(overflow, ctx[i].size());
116 // update the overflow to avoid going here again if not needed
117 overflow = (overflow > ctx[i].size()) ? overflow - ctx[i].size() : 0;
118
119 fmt::print(
120 os,
121 "{: <{}}{:~<{}}\n",
122 // padding of spaces
123 " ",
124 std::max(1_z, curr_col_start), // fixing padding when the error is on the first character
125 // underline the error in red
126 fmt::styled("^", colorize ? fmt::fg(fmt::color::red) : fmt::text_style()),
127 col_end - curr_col_start);
128 }
129 }
130 }
131
132 template <typename T>
133 void helper(std::ostream& os, const std::string& message, const bool colorize, const std::string& filename, const std::string& code, const T& expr,
134 const std::size_t line, std::size_t column, const std::size_t sym_size)
135 {
136 if (filename != ARK_NO_NAME_FILE)
137 fmt::print(os, "In file {}\n", filename);
138 fmt::print(os, "At {} @ {}:{}\n", expr, line + 1, column);
139
140 if (!code.empty())
141 makeContext(os, code, line, column, sym_size, colorize);
142
143 const auto message_lines = Utils::splitString(message, '\n');
144 for (const auto& text : message_lines)
145 fmt::print(os, " {}\n", text);
146 }
147
148 std::string makeContextWithNode(const std::string& message, const internal::Node& node)
149 {
150 std::stringstream ss;
151
152 std::size_t size = 3;
153 if (node.isStringLike())
154 size = node.string().size();
155
156 helper(
157 ss,
158 message,
159 true,
160 node.filename(),
161 (node.filename() == ARK_NO_NAME_FILE) ? "" : Utils::readFile(node.filename()),
162 node.repr(),
163 node.line(),
164 node.col(),
165 size);
166
167 return ss.str();
168 }
169
170 void generate(const CodeError& e, std::ostream& os, bool colorize)
171 {
172 if (const char* nocolor = std::getenv("NOCOLOR"); nocolor != nullptr)
173 colorize = false;
174
175 std::string escaped_symbol;
176 if (e.symbol.has_value())
177 {
178 switch (e.symbol.value().codepoint())
179 {
180 case '\n': escaped_symbol = "'\\n'"; break;
181 case '\r': escaped_symbol = "'\\r'"; break;
182 case '\t': escaped_symbol = "'\\t'"; break;
183 case '\v': escaped_symbol = "'\\v'"; break;
184 case '\0': escaped_symbol = "EOF"; break;
185 case ' ': escaped_symbol = "' '"; break;
186 default:
187 escaped_symbol = e.symbol.value().c_str();
188 }
189 }
190 else
191 escaped_symbol = e.expr;
192
193 std::string file_content;
194 if (e.filename != ARK_NO_NAME_FILE)
195 file_content = Utils::readFile(e.filename);
196
197 // TODO enhance the error messages
198 helper(
199 os,
200 e.what(),
201 colorize,
202 e.filename,
203 file_content,
204 escaped_symbol,
205 e.line,
206 e.col,
207 e.expr.size());
208 }
209}
Lots of utilities about string, filesystem and more.
Constants used by ArkScript.
#define ARK_NO_NAME_FILE
Definition Constants.hpp:27
ArkScript homemade exceptions.
Lots of utilities about the filesystem.
User defined literals for Ark internals.
AST node used by the parser, optimizer and compiler.
A node of an Abstract Syntax Tree for ArkScript.
Definition Node.hpp:31
const std::string & filename() const noexcept
Return the filename in which this node was created.
Definition Node.cpp:145
const std::string & string() const noexcept
Return the string held by the value (if the node type allows it)
Definition Node.cpp:37
std::string repr() const noexcept
Compute a representation of the node without any comments or additional sugar, colors,...
Definition Node.cpp:160
std::size_t col() const noexcept
Get the column at which this node was created.
Definition Node.cpp:140
bool isStringLike() const noexcept
Check if the node is a string like node.
Definition Node.cpp:87
std::size_t line() const noexcept
Get the line at which this node was created.
Definition Node.cpp:135
bool isPairableChar(const char c)
ARK_API void makeContext(std::ostream &os, const std::string &code, std::size_t target_line, std::size_t col_start, std::size_t sym_size, bool colorize)
Helper to create a colorized context to report errors to the user.
void colorizeLine(const std::string &line, LineColorContextCounts &line_color_context_counts, std::ostream &ss)
void helper(std::ostream &os, const std::string &message, const bool colorize, const std::string &filename, const std::string &code, const T &expr, const std::size_t line, std::size_t column, const std::size_t sym_size)
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.
ARK_API std::string makeContextWithNode(const std::string &message, const internal::Node &node)
Helper used by the compiler to generate a colorized context from a node.
std::string readFile(const std::string &name)
Helper to read a file.
Definition Files.hpp:48
std::vector< std::string > splitString(const std::string &source, const char sep)
Cut a string into pieces, given a character separator.
Definition Utils.hpp:32
CodeError thrown by the compiler (parser, macro processor, optimizer, and compiler itself)
const std::size_t line
const std::string filename
const std::size_t col
const std::string expr
const std::optional< internal::utf8_char_t > symbol