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

ArkScript-lang / Ark / 15006215616

13 May 2025 08:34PM UTC coverage: 86.726% (+0.3%) from 86.474%
15006215616

push

github

SuperFola
feat(macro processor, error): adding better error messages when a macro fails, to show the macro we were expanding and what failed

60 of 60 new or added lines in 8 files covered. (100.0%)

83 existing lines in 6 files now uncovered.

7017 of 8091 relevant lines covered (86.73%)

79023.01 hits per line

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

96.28
/src/arkreactor/Compiler/AST/BaseParser.cpp
1
#include <Ark/Compiler/AST/BaseParser.hpp>
2
#include <Ark/Exceptions.hpp>
3

4
#include <utility>
5
#include <algorithm>
6

7
#include <fmt/core.h>
8

9
namespace Ark::internal
10
{
11
    void BaseParser::registerNewLine(std::string::iterator it, std::size_t row)
18,294✔
12
    {
18,294✔
13
        // search for an existing new line position
14
        if (std::ranges::find_if(m_it_to_row, [it](const auto& pair) {
2,429,989✔
15
                return pair.first == it;
2,411,695✔
16
            }) != m_it_to_row.end())
18,294✔
17
            return;
1,084✔
18

19
        // if the mapping is empty, the loop while never hit and we'll never insert anything
20
        if (m_it_to_row.empty())
17,210✔
21
        {
22
            m_it_to_row.emplace_back(it, row);
405✔
23
            return;
405✔
24
        }
25

26
        for (std::size_t i = 0, end = m_it_to_row.size(); i < end; ++i)
2,315,103✔
27
        {
28
            auto current_it = m_it_to_row[i].first;
2,298,298✔
29
            auto next_it = i + 1 < end ? m_it_to_row[i + 1].first : m_str.end();
2,298,298✔
30
            if (current_it < it && it < next_it)
2,298,298✔
31
            {
32
                m_it_to_row.insert(
33,610✔
33
                    m_it_to_row.begin() + static_cast<decltype(m_it_to_row)::difference_type>(i) + 1,
16,805✔
34
                    std::make_pair(it, row));
16,805✔
35
                break;
16,805✔
36
            }
37
        }
2,298,298✔
38
    }
18,294✔
39

40
    void BaseParser::next()
1,079,029✔
41
    {
1,079,029✔
42
        m_it = m_next_it;
1,079,029✔
43
        if (isEOF())
1,079,029✔
44
        {
45
            m_sym = utf8_char_t();  // reset sym to EOF
438✔
46
            return;
438✔
47
        }
48

49
        // getting a character from the stream
50
        auto [it, sym] = utf8_char_t::at(m_it, m_str.end());
1,078,591✔
51
        m_next_it = it;
1,078,591✔
52
        m_sym = sym;
1,078,591✔
53

54
        if (*m_it == '\n')
1,078,591✔
55
        {
56
            ++m_filepos.row;
18,294✔
57
            m_filepos.col = 0;
18,294✔
58
            registerNewLine(m_it, m_filepos.row);
18,294✔
59
        }
18,294✔
60
        else if (m_sym.isPrintable())
1,060,297✔
61
            m_filepos.col += m_sym.size();
1,060,297✔
62
    }
1,079,029✔
63

64
    void BaseParser::initParser(const std::string& filename, const std::string& code)
445✔
65
    {
445✔
66
        m_filename = filename;
445✔
67

68
        // if the input string is empty, raise an error
69
        if (code.empty())
445✔
70
        {
71
            m_sym = utf8_char_t();
1✔
72
            error("Expected symbol, got empty string", "");
1✔
73
        }
×
74

75
        m_str = code;
444✔
76
        m_it = m_next_it = m_str.begin();
444✔
77

78
        // otherwise, get the first symbol
79
        next();
444✔
80
    }
445✔
81

82
    void BaseParser::backtrack(const long n)
411,878✔
83
    {
411,878✔
84
        if (std::cmp_greater_equal(n, m_str.size()))
411,878✔
85
            return;
10✔
86

87
        m_it = m_str.begin() + n;
411,868✔
88
        auto [it, sym] = utf8_char_t::at(m_it, m_str.end());
54,333,466✔
89
        m_next_it = it;
411,868✔
90
        m_sym = sym;
411,868✔
91

92
        // search for the nearest it < m_it in the map to know the line number
93
        for (std::size_t i = 0, end = m_it_to_row.size(); i < end; ++i)
54,333,466✔
94
        {
95
            auto [at, line] = m_it_to_row[i];
53,922,890✔
96
            if (it <= at)
53,921,598✔
97
            {
98
                m_filepos.row = line - 1;
1,292✔
99
                break;
1,292✔
100
            }
101
        }
53,921,598✔
102
        // compute the position in the line
103
        std::string_view view = m_str;
411,868✔
104
        const auto it_pos = static_cast<std::size_t>(std::distance(m_str.begin(), m_it));
411,868✔
105
        view = view.substr(0, it_pos);
411,868✔
106
        const auto nearest_newline_index = view.find_last_of('\n');
411,868✔
107
        if (nearest_newline_index != std::string_view::npos)
411,868✔
108
            m_filepos.col = it_pos - nearest_newline_index;
403,139✔
109
        else
110
            m_filepos.col = it_pos + 1;
8,729✔
111
    }
411,878✔
112

113
    FilePosition BaseParser::getCursor() const
711,067✔
114
    {
711,067✔
115
        return m_filepos;
711,067✔
116
    }
117

118
    CodeErrorContext BaseParser::generateErrorContext(const std::string& expr)
217,470✔
119
    {
217,470✔
120
        const auto [row, col] = getCursor();
217,470✔
121

122
        return CodeErrorContext(
217,470✔
123
            m_filename,
217,470✔
124
            row,
217,470✔
125
            col,
217,470✔
126
            expr,
217,470✔
127
            m_sym);
217,470✔
128
    }
217,470✔
129

130
    void BaseParser::error(const std::string& error, std::string exp, const std::optional<CodeErrorContext>& additional_context)
42✔
131
    {
42✔
132
        const auto [row, col] = getCursor();
126✔
133
        throw CodeError(
126✔
134
            error,
42✔
135
            CodeErrorContext(m_filename, row, col, std::move(exp), m_sym),
42✔
136
            additional_context);
42✔
137
    }
84✔
138

139
    void BaseParser::errorWithNextToken(const std::string& message, const std::optional<CodeErrorContext>& additional_context)
34✔
140
    {
34✔
141
        const auto pos = getCount();
34✔
142
        std::string next_token;
34✔
143

144
        anyUntil(IsEither(IsInlineSpace, IsEither(IsChar('('), IsChar(')'))), &next_token);
34✔
145
        backtrack(pos);
34✔
146

147
        error(message, next_token, additional_context);
34✔
148
    }
34✔
149

150
    void BaseParser::expectSuffixOrError(const char suffix, const std::string& context, const std::optional<CodeErrorContext>& additional_context)
16,257✔
151
    {
16,257✔
152
        if (!accept(IsChar(suffix)))
16,265✔
153
            errorWithNextToken(fmt::format("Missing '{}' {}", suffix, context), additional_context);
8✔
154
    }
16,257✔
155

156
    bool BaseParser::accept(const CharPred& t, std::string* s)
2,396,531✔
157
    {
2,396,531✔
158
        if (isEOF())
2,396,531✔
159
            return false;
505✔
160

161
        // return false if the predicate couldn't consume the symbol
162
        if (!t(m_sym.codepoint()))
2,396,026✔
163
            return false;
1,318,736✔
164
        // otherwise, add it to the string and go to the next symbol
165
        if (s != nullptr)
1,077,290✔
166
            *s += m_sym.c_str();
819,673✔
167

168
        next();
1,077,290✔
169
        return true;
1,077,290✔
170
    }
2,396,531✔
171

172
    bool BaseParser::expect(const CharPred& t, std::string* s)
1,295✔
173
    {
1,295✔
174
        // throw an error if the predicate couldn't consume the symbol
175
        if (!t(m_sym.codepoint()))
1,295✔
UNCOV
176
            error("Expected " + t.name, m_sym.c_str());
×
177
        // otherwise, add it to the string and go to the next symbol
178
        if (s != nullptr)
1,295✔
UNCOV
179
            *s += m_sym.c_str();
×
180
        next();
1,295✔
181
        return true;
1,295✔
UNCOV
182
    }
×
183

184
    std::string BaseParser::peek() const
4✔
185
    {
4✔
186
        return m_sym.c_str();
4✔
187
    }
×
188

189
    bool BaseParser::space(std::string* s)
241,382✔
190
    {
241,382✔
191
        if (accept(IsSpace))
241,382✔
192
        {
193
            if (s != nullptr)
102,047✔
UNCOV
194
                s->push_back(' ');
×
195
            // loop while there are still ' ' to consume
196
            while (accept(IsSpace))
129,868✔
197
                ;
198
            return true;
102,047✔
199
        }
200
        return false;
139,335✔
201
    }
241,382✔
202

203
    bool BaseParser::inlineSpace(std::string* s)
8,170✔
204
    {
8,170✔
205
        if (accept(IsInlineSpace))
8,170✔
206
        {
207
            if (s != nullptr)
653✔
UNCOV
208
                s->push_back(' ');
×
209
            // loop while there are still ' ' to consume
210
            while (accept(IsInlineSpace))
664✔
211
                ;
212
            return true;
653✔
213
        }
214
        return false;
7,517✔
215
    }
8,170✔
216

217
    bool BaseParser::comment(std::string* s)
248,860✔
218
    {
248,860✔
219
        if (accept(IsChar('#'), s))
248,860✔
220
        {
221
            while (accept(IsNot(IsChar('\n')), s))
240,274✔
222
                ;
223
            accept(IsChar('\n'), s);
7,190✔
224
            return true;
7,190✔
225
        }
226
        return false;
241,670✔
227
    }
248,860✔
228

229
    bool BaseParser::spaceComment(std::string* s)
8,129✔
230
    {
8,129✔
231
        bool matched = false;
8,129✔
232

233
        inlineSpace();
8,129✔
234
        while (!isEOF() && comment(s))
8,170✔
235
        {
236
            inlineSpace();
41✔
237
            matched = true;
41✔
238
        }
239

240
        return matched;
8,129✔
241
    }
8,129✔
242

243
    bool BaseParser::newlineOrComment(std::string* s)
233,981✔
244
    {
233,981✔
245
        bool matched = false;
233,981✔
246

247
        space();
233,981✔
248
        while (!isEOF() && comment(s))
241,130✔
249
        {
250
            space();
7,149✔
251
            matched = true;
7,149✔
252
        }
253

254
        return matched;
233,981✔
255
    }
233,981✔
256

257
    bool BaseParser::prefix(const char c)
128,813✔
258
    {
128,813✔
259
        if (!accept(IsChar(c)))
128,813✔
260
            return false;
69,948✔
261
        return true;
58,865✔
262
    }
128,813✔
263

264
    bool BaseParser::number(std::string* s)
110,903✔
265
    {
110,903✔
266
        if (accept(IsDigit, s))
110,903✔
267
        {
268
            // consume all the digits available,
269
            // stop when the symbol isn't a digit anymore
270
            while (accept(IsDigit, s))
69,934✔
271
                ;
272
            return true;
69,394✔
273
        }
274
        return false;
41,509✔
275
    }
110,903✔
276

277
    bool BaseParser::signedNumber(std::string* s)
110,830✔
278
    {
110,830✔
279
        accept(IsMinus, s);
110,830✔
280
        if (!number(s))
110,830✔
281
            return false;
41,509✔
282

283
        // (optional) floating part
284
        accept(IsChar('.'), s) && number(s);
69,321✔
285
        // (optional) scientific part
286
        if (accept(IsEither(IsChar('e'), IsChar('E')), s))
69,321✔
287
        {
288
            accept(IsEither(IsMinus, IsChar('+')), s);
5✔
289
            number(s);
5✔
290
        }
5✔
291

292
        return true;
69,321✔
293
    }
110,830✔
294

295
    bool BaseParser::hexNumber(unsigned int length, std::string* s)
8✔
296
    {
8✔
297
        while (length != 0)
56✔
298
        {
299
            if (!accept(IsHex, s))
48✔
UNCOV
300
                return false;
×
301
            --length;
48✔
302
        }
303
        return true;
8✔
304
    }
8✔
305

306
    bool BaseParser::name(std::string* s)
171,585✔
307
    {
171,585✔
308
        const auto alpha_symbols = IsEither(IsAlpha, IsSymbol);
171,585✔
309
        const auto alnum_symbols = IsEither(IsAlnum, IsSymbol);
171,585✔
310

311
        if (accept(alpha_symbols, s))
171,585✔
312
        {
313
            while (accept(alnum_symbols, s))
484,759✔
314
                ;
315
            return true;
121,027✔
316
        }
317
        return false;
50,558✔
318
    }
171,585✔
319

320
    bool BaseParser::sequence(const std::string& s)
40,478✔
321
    {
40,478✔
322
        return std::ranges::all_of(s, [this](const char c) {
81,102✔
323
            return accept(IsChar(c));
40,624✔
UNCOV
324
        });
×
325
    }
326

327
    bool BaseParser::packageName(std::string* s)
280✔
328
    {
280✔
329
        if (accept(IsAlnum, s))
280✔
330
        {
331
            while (accept(IsEither(IsAlnum, IsEither(IsChar('_'), IsChar('-'))), s))
1,661✔
332
                ;
333
            return true;
277✔
334
        }
335
        return false;
3✔
336
    }
280✔
337

338
    bool BaseParser::anyUntil(const CharPred& delim, std::string* s)
34✔
339
    {
34✔
340
        if (accept(IsNot(delim), s))
34✔
341
        {
342
            while (accept(IsNot(delim), s))
1,224✔
343
                ;
344
            return true;
8✔
345
        }
346
        return false;
26✔
347
    }
34✔
348

349
    bool BaseParser::oneOf(const std::initializer_list<std::string> words, std::string* s)
83,131✔
350
    {
83,131✔
351
        std::string buffer;
83,131✔
352
        if (!name(&buffer))
83,131✔
353
            return false;
216✔
354

355
        if (s)
82,915✔
356
            *s = buffer;
14,294✔
357

358
        return std::ranges::any_of(words, [&buffer](const std::string& word) {
190,035✔
359
            return word == buffer;
107,120✔
360
        });
361
    }
83,131✔
362
}
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