• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ArkScript-lang / Ark / 15292297599

27 May 2025 03:31PM UTC coverage: 86.63% (+0.004%) from 86.626%
15292297599

push

github

SuperFola
fix(name resolver): couldn't import two different files with the same symbol names inside, even though both were hidden and prefixed by their file

5 of 5 new or added lines in 2 files covered. (100.0%)

61 existing lines in 5 files now uncovered.

7231 of 8347 relevant lines covered (86.63%)

91165.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.2
/src/arkreactor/Exceptions.cpp
1
#include <Ark/Exceptions.hpp>
2

3
#include <cassert>
4
#include <sstream>
5
#include <algorithm>
6
#include <fmt/core.h>
7
#include <fmt/color.h>
8
#include <fmt/ostream.h>
9

10
#include <Ark/Constants.hpp>
11
#include <Ark/Utils.hpp>
12
#include <Ark/Files.hpp>
13
#include <Ark/Literals.hpp>
14
#include <Ark/Compiler/AST/Node.hpp>
15

16
namespace Ark::Diagnostics
17
{
18
    struct LineColorContextCounts
227✔
19
    {
20
        int open_parentheses = 0;
227✔
21
        int open_square_braces = 0;
227✔
22
        int open_curly_braces = 0;
227✔
23
    };
24

25
    inline bool isPairableChar(const char c)
×
26
    {
×
27
        return c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}';
×
28
    }
29

30
    void colorizeLine(const std::string& line, LineColorContextCounts& line_color_context_counts, std::ostream& ss)
×
31
    {
×
32
        // clang-format off
33
        constexpr std::array pairing_color {
×
34
            fmt::color::light_blue,
35
            fmt::color::light_green,
36
            fmt::color::light_salmon,
37
            fmt::color::light_yellow,
38
            fmt::color::light_cyan,
39
            fmt::color::light_coral
40
        };
41
        // clang-format on
42
        constexpr std::size_t pairing_color_size = pairing_color.size();
×
43

44
        for (const char& c : line)
×
45
        {
46
            if (isPairableChar(c))
×
47
            {
48
                std::size_t pairing_color_index = 0;
×
49

50
                switch (c)
×
51
                {
×
52
                    case '(':
53
                        pairing_color_index = static_cast<std::size_t>(std::abs(line_color_context_counts.open_parentheses)) % pairing_color_size;
×
54
                        line_color_context_counts.open_parentheses++;
×
55
                        break;
×
56
                    case ')':
57
                        line_color_context_counts.open_parentheses--;
×
58
                        pairing_color_index = static_cast<std::size_t>(std::abs(line_color_context_counts.open_parentheses)) % pairing_color_size;
×
59
                        break;
×
60
                    case '[':
61
                        pairing_color_index = static_cast<std::size_t>(std::abs(line_color_context_counts.open_square_braces)) % pairing_color_size;
×
62
                        line_color_context_counts.open_square_braces++;
×
63
                        break;
×
64
                    case ']':
65
                        line_color_context_counts.open_square_braces--;
×
66
                        pairing_color_index = static_cast<std::size_t>(std::abs(line_color_context_counts.open_square_braces)) % pairing_color_size;
×
67
                        break;
×
68
                    case '{':
69
                        pairing_color_index = static_cast<std::size_t>(std::abs(line_color_context_counts.open_curly_braces)) % pairing_color_size;
×
70
                        line_color_context_counts.open_curly_braces++;
×
71
                        break;
×
72
                    case '}':
73
                        line_color_context_counts.open_curly_braces--;
×
74
                        pairing_color_index = static_cast<std::size_t>(std::abs(line_color_context_counts.open_curly_braces)) % pairing_color_size;
×
75
                        break;
×
76
                    default:
77
                        break;
×
78
                }
×
79

80
                fmt::print(ss, "{}", fmt::styled(c, fmt::fg(pairing_color[pairing_color_index])));
×
81
            }
×
82
            else
83
                fmt::print(ss, "{}", c);
×
84
        }
×
85
    }
×
86

87
    void makeContext(
228✔
88
        std::ostream& os,
89
        const std::string& filename,
90
        const std::optional<std::string>& expr,
91
        const std::size_t sym_size,
92
        const std::size_t target_line,
93
        const std::size_t col_start,
94
        const std::optional<CodeErrorContext>& maybe_context,  // can not be populated at runtime, only compile time
95
        const bool whole_line,
96
        const bool colorize)
97
    {
228✔
98
        assert(!(maybe_context && whole_line) && "Can not create error context when a context is given AND the whole line has to be underlined");
228✔
99

100
        using namespace Ark::literals;
101

102
        auto show_file_location = [&] {
456✔
103
            if (filename != ARK_NO_NAME_FILE)
228✔
104
                fmt::print(os, "In file {}:{}\n", filename, target_line + 1);
228✔
105
            if (expr)
228✔
106
                fmt::print(os, "At {} @ {}:{}\n", expr.value(), target_line + 1, col_start);
119✔
107
        };
228✔
108

109
        auto compute_start_end_window = [](const std::size_t center_of_window, const std::size_t line_count) {
455✔
110
            std::size_t start = center_of_window >= 3 ? center_of_window - 3 : 0;
227✔
111
            std::size_t end = center_of_window + 3 <= line_count ? center_of_window + 3 : line_count;
227✔
112
            return std::make_pair(start, end);
227✔
113
        };
227✔
114

115
        auto print_line = [&os, colorize](const std::size_t i, const std::vector<std::string>& lines, LineColorContextCounts& color_context) {
803✔
116
            // show current line with its number
117
            fmt::print(os, "{: >5} |{}", i + 1, !lines[i].empty() ? " " : "");
575✔
118
            if (colorize)
575✔
119
                colorizeLine(lines[i], color_context, os);
×
120
            else
121
                fmt::print(os, "{}", lines[i]);
575✔
122
            fmt::print(os, "\n");
575✔
123
        };
575✔
124

125
        const std::string line_no_num = "      |";
228✔
126

127
        auto print_context_hint = [&os, &maybe_context, &line_no_num, colorize]() mutable {
237✔
128
            if (!maybe_context)
9✔
129
                return;
×
130

131
            fmt::print(os, "{}", line_no_num);
9✔
132
            fmt::print(
27✔
133
                os,
9✔
134
                "{: <{}}{}\n",
9✔
135
                // padding os spaces
136
                " ",
137
                std::max(1_z, maybe_context->col),  // fixing padding when the error is on the first character
9✔
138
                // underline the parent of the error in red
139
                fmt::styled(
18✔
140
                    maybe_context->is_macro_expansion ? "^ macro expansion started here" : "^ expression started here",
9✔
141
                    colorize ? fmt::fg(fmt::color::red) : fmt::text_style()));
9✔
142
        };
9✔
143

144
        const std::string code = filename == ARK_NO_NAME_FILE ? "" : Utils::readFile(filename);
228✔
145
        const std::vector<std::string> lines = Utils::splitString(code, '\n');
228✔
146
        if (target_line >= lines.size() || code.empty())
228✔
147
        {
148
            // show the "in file..." before early return
149
            show_file_location();
1✔
150
            return;
1✔
151
        }
152

153
        auto [first_line, last_line] = compute_start_end_window(target_line, lines.size());
1,307✔
154
        // overflow is non-zero when the expression doesn't fit on the target line
155
        std::size_t overflow = (col_start + sym_size <= lines[target_line].size()) ? 0 : sym_size;
227✔
156

157
        const bool ctx_same_file = maybe_context && maybe_context->filename == filename;
227✔
158
        const bool ctx_in_window = ctx_same_file && maybe_context &&
242✔
159
            maybe_context->line >= first_line &&
27✔
160
            maybe_context->line < last_line;
12✔
161

162
        std::size_t start_line_skipping_at = 0;
227✔
163
        std::size_t stop_line_skipping_at = first_line;
454✔
164
        if (ctx_same_file && !ctx_in_window)
227✔
165
        {
166
            // showing the context will require an ellipsis, to avoid showing too many lines in the error message
167
            if (maybe_context->line + 3 < first_line)
3✔
168
                start_line_skipping_at = maybe_context->line + 3;
3✔
169
            else
170
                stop_line_skipping_at = start_line_skipping_at;
×
171

172
            // due to how context works, if it points to the same file,
173
            // we are guaranteed it will be before our error
174
            first_line = maybe_context->line >= 3 ? maybe_context->line - 3 : 0;
3✔
175
        }
3✔
176
        else if (maybe_context && !ctx_same_file && !maybe_context->filename.empty())
224✔
177
        {
178
            // show the location of the parent of our error first
179
            fmt::print(os, "Error originated from file {}:{}\n", maybe_context->filename, maybe_context->line + 1);
×
180

181
            const std::vector<std::string> ctx_source_lines = Utils::splitString(Utils::readFile(maybe_context->filename), '\n');
×
182
            auto [ctx_first_line, ctx_last_line] = compute_start_end_window(maybe_context->line, ctx_source_lines.size());
×
183
            LineColorContextCounts line_color_context_counts;
×
184

185
            for (auto i = ctx_first_line; i < ctx_last_line; ++i)
×
186
            {
187
                print_line(i, ctx_source_lines, line_color_context_counts);
×
188
                if (i == maybe_context->line)
×
189
                    print_context_hint();
×
190
            }
×
191

192
            fmt::print(os, "\n");
×
193
        }
×
194

195
        show_file_location();
227✔
196
        LineColorContextCounts line_color_context_counts;
227✔
197

198
        for (auto i = first_line; i < last_line; ++i)
1,042✔
199
        {
200
            if (i >= start_line_skipping_at && i < stop_line_skipping_at)
588✔
201
                continue;
13✔
202
            print_line(i, lines, line_color_context_counts);
575✔
203

204
            // if the error context is in the current file, point to it as the parent of our error
205
            if (maybe_context && i == maybe_context->line && i != target_line)
575✔
206
                print_context_hint();
9✔
207

208
            // if the next line number wants us to skip line, and start != stop (meaning they got adjusted),
209
            // display an ellipsis
210
            if (i + 1 == start_line_skipping_at && i + 1 != stop_line_skipping_at)
575✔
211
                fmt::print(os, "  ... |\n");
3✔
212

213
            // show where the error occurred (do not mark empty lines as being part of the error when we have overflow)
214
            if (i == target_line || (i > target_line && overflow > 0 && !lines[i].empty()))
575✔
215
            {
216
                fmt::print(os, "{}", line_no_num);
231✔
217

218
                if (!whole_line)
231✔
219
                {
220
                    std::size_t line_first_char = lines[i].find_first_not_of(" \t\v");
122✔
221
                    line_first_char = line_first_char == std::string::npos ? 0 : line_first_char;
122✔
222

223
                    // if we have an overflow then we start at the beginning of the line (first non-space character)
224
                    const std::size_t curr_col_start = (i == target_line) ? col_start : (overflow == 0 ? col_start : line_first_char + 1);
122✔
225
                    // if we have an overflow, it is used as the end of the line
226
                    const std::size_t col_end = (i == target_line) ? std::min<std::size_t>(col_start + sym_size, lines[target_line].size())
126✔
227
                                                                   : std::min<std::size_t>(line_first_char + overflow, lines[i].size());
4✔
228
                    // update the overflow to avoid going here again if not needed
229
                    // using min between overflow and what we need to delete to avoid underflow
230
                    overflow -= std::min(overflow, lines[i].size() - line_first_char);
122✔
231
                    // if there is overflow left, and it's the last line of the context, extend it
232
                    if (overflow > 0 && i + 1 == last_line)
122✔
233
                        ++last_line;
1✔
234

235
                    // show the error where it's at, using the normal process, if there is no context OR if the context line is different from the error line
236
                    if (!maybe_context || maybe_context->line != target_line)
122✔
237
                        fmt::print(
336✔
238
                            os,
112✔
239
                            "{: <{}}{:~<{}}\n",
112✔
240
                            // padding of spaces
241
                            " ",
242
                            std::max(1_z, curr_col_start),  // fixing padding when the error is on the first character
112✔
243
                            // underline the error in red
244
                            fmt::styled("^", colorize ? fmt::fg(fmt::color::red) : fmt::text_style()),
112✔
245
                            col_end - curr_col_start);
112✔
246
                    else if (i == target_line)  // maybe_context has a value, i == target_line to avoid having to deal with overflow
10✔
247
                    {
248
                        const auto padding_size = std::max(1_z, maybe_context->col);
10✔
249

250
                        fmt::print(
20✔
251
                            os,
10✔
252
                            "{: <{}}{}{}{}\n",
10✔
253
                            // padding of spaces
254
                            " ",
255
                            padding_size,
256
                            // indicate where the parent is, with color
257
                            fmt::styled("│", colorize ? fmt::fg(fmt::color::red) : fmt::text_style()),
10✔
258
                            // yet another padding of spaces between the parent and error column (if need be)
259
                            // -2 to account for the │ and then └
260
                            (col_start - maybe_context->col <= 2) ? "" : fmt::format("{: <{}}", " ", col_start - maybe_context->col - 2),
10✔
261
                            // underline the error in red
262
                            fmt::styled("└─ error", colorize ? fmt::fg(fmt::color::red) : fmt::text_style()));
10✔
263
                        // new line, some spacing between the error and the parent
264
                        fmt::print(os, "{}{: <{}}{}\n", line_no_num, " ", padding_size, fmt::styled("│", colorize ? fmt::fg(fmt::color::red) : fmt::text_style()));
10✔
265
                        // new line, now show the "expression started here for the source"
266
                        fmt::print(
20✔
267
                            os,
10✔
268
                            "{}{: <{}}{}\n",
10✔
269
                            line_no_num,
270
                            // padding of spaces
271
                            " ",
272
                            padding_size,
273
                            fmt::styled(
20✔
274
                                maybe_context->is_macro_expansion ? "└─ macro expansion started here" : "└─ expression started here",
10✔
275
                                colorize ? fmt::fg(fmt::color::red) : fmt::text_style()));
10✔
276
                    }
10✔
277
                }
122✔
278
                else
279
                {
280
                    // first non-whitespace character of the line
281
                    // +1 for the leading whitespace after `    |` before the code
282
                    const std::size_t curr_col_start = lines[i].find_first_not_of(" \t\v") + 1;
109✔
283

284
                    // highlight the current line but skip any leading whitespace
285
                    fmt::print(
218✔
286
                        os,
109✔
287
                        "{: <{}}{:~<{}}\n",
109✔
288
                        // padding of spaces
289
                        " ",
290
                        curr_col_start,
291
                        // underline the whole line in red
292
                        fmt::styled("^", colorize ? fmt::fg(fmt::color::red) : fmt::text_style()),
109✔
293
                        lines[target_line].size() - curr_col_start);
109✔
294
                }
109✔
295
            }
231✔
296
        }
575✔
297
    }
228✔
298

299
    void helper(std::ostream& os, const std::string& message, const bool colorize,
119✔
300
                const std::string& filename,
301
                const std::optional<std::string>& expr, const std::size_t sym_size,
302
                const std::size_t line, const std::size_t column,
303
                const std::optional<CodeErrorContext>& maybe_context = std::nullopt)
304
    {
119✔
305
        makeContext(os, filename, expr, sym_size, line, column, maybe_context, /* whole_line= */ false, colorize);
119✔
306

307
        const auto message_lines = Utils::splitString(message, '\n');
119✔
308
        for (const auto& text : message_lines)
238✔
309
            fmt::print(os, "        {}\n", text);
119✔
310
    }
119✔
311

UNCOV
312
    std::string makeContextWithNode(const std::string& message, const internal::Node& node)
×
313
    {
×
314
        std::stringstream ss;
×
315

UNCOV
316
        std::size_t size = 3;
×
317
        if (node.isStringLike())
×
318
            size = node.string().size();
×
319

320
        helper(
×
321
            ss,
×
UNCOV
322
            message,
×
323
            true,
324
            node.filename(),
×
UNCOV
325
            node.repr(),
×
UNCOV
326
            size,
×
UNCOV
327
            node.line(),
×
UNCOV
328
            node.col());
×
329

330
        return ss.str();
×
UNCOV
331
    }
×
332

333
    void generate(const CodeError& e, std::ostream& os, bool colorize)
119✔
334
    {
119✔
335
#ifdef ARK_BUILD_EXE
336
        if (const char* nocolor = std::getenv("NOCOLOR"); nocolor != nullptr)
119✔
337
            colorize = false;
×
338
#endif
339

340
        std::string escaped_symbol;
119✔
341
        if (e.context.symbol.has_value())
119✔
342
        {
343
            switch (e.context.symbol.value().codepoint())
44✔
UNCOV
344
            {
×
UNCOV
345
                case '\n': escaped_symbol = "'\\n'"; break;
×
UNCOV
346
                case '\r': escaped_symbol = "'\\r'"; break;
×
UNCOV
347
                case '\t': escaped_symbol = "'\\t'"; break;
×
348
                case '\v': escaped_symbol = "'\\v'"; break;
11✔
349
                case '\0': escaped_symbol = "EOF"; break;
13✔
350
                case ' ': escaped_symbol = "' '"; break;
33✔
351
                default:
352
                    escaped_symbol = e.context.symbol.value().c_str();
31✔
353
            }
44✔
354
        }
44✔
355
        else
356
            escaped_symbol = e.context.expr;
75✔
357

358
        helper(
357✔
359
            os,
119✔
360
            e.what(),
119✔
361
            colorize,
119✔
362
            e.context.filename,
119✔
363
            escaped_symbol,
119✔
364
            e.context.expr.size(),
119✔
365
            e.context.line,
119✔
366
            e.context.col,
119✔
367
            e.additional_context);
119✔
368
    }
119✔
369
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc