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

ArkScript-lang / Ark / 15686604562

16 Jun 2025 04:37PM UTC coverage: 86.69% (-0.01%) from 86.7%
15686604562

push

github

SuperFola
fix: add a check on the position when backtracking in the parser

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

7275 of 8392 relevant lines covered (86.69%)

107769.86 hits per line

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

95.88
/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)
27,654✔
12
    {
27,654✔
13
        // search for an existing new line position
14
        if (std::ranges::find_if(m_it_to_row, [it](const auto& pair) {
5,053,192✔
15
                return pair.first == it;
5,025,538✔
16
            }) != m_it_to_row.end())
27,654✔
17
            return;
1,280✔
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())
26,374✔
21
        {
22
            m_it_to_row.emplace_back(it, row);
442✔
23
            return;
442✔
24
        }
25

26
        for (std::size_t i = 0, end = m_it_to_row.size(); i < end; ++i)
4,880,233✔
27
        {
28
            auto current_it = m_it_to_row[i].first;
4,854,301✔
29
            auto next_it = i + 1 < end ? m_it_to_row[i + 1].first : m_str.end();
4,854,301✔
30
            if (current_it < it && it < next_it)
4,854,301✔
31
            {
32
                m_it_to_row.insert(
51,864✔
33
                    m_it_to_row.begin() + static_cast<decltype(m_it_to_row)::difference_type>(i) + 1,
25,932✔
34
                    std::make_pair(it, row));
25,932✔
35
                break;
25,932✔
36
            }
37
        }
4,854,301✔
38
    }
27,654✔
39

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

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

54
        if (*m_it == '\n')
1,579,656✔
55
        {
56
            ++m_filepos.row;
27,654✔
57
            m_filepos.col = 0;
27,654✔
58
            registerNewLine(m_it, m_filepos.row);
27,654✔
59
        }
27,654✔
60
        else if (m_sym.isPrintable())
1,552,002✔
61
            m_filepos.col += m_sym.size();
1,552,002✔
62
    }
1,580,122✔
63

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

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

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

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

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

87
        if (std::cmp_less(n, m_str.size()))
548,082✔
88
            m_it = m_str.begin() + n;
548,082✔
89
        else
NEW
90
            m_it = m_str.begin();
×
91

92
        auto [it, sym] = utf8_char_t::at(m_it, m_str.end());
105,189,231✔
93
        m_next_it = it;
548,082✔
94
        m_sym = sym;
548,082✔
95

96
        // search for the nearest it < m_it in the map to know the line number
97
        for (const auto& [at, line] : m_it_to_row)
105,190,741✔
98
        {
99
            if (it <= at)
104,641,149✔
100
            {
101
                m_filepos.row = line - 1;
1,510✔
102
                break;
1,510✔
103
            }
104
        }
104,641,149✔
105
        // compute the position in the line
106
        std::string_view view = m_str;
548,082✔
107
        const auto it_pos = static_cast<std::size_t>(std::distance(m_str.begin(), m_it));
548,082✔
108
        view = view.substr(0, it_pos);
548,082✔
109
        const auto nearest_newline_index = view.find_last_of('\n');
548,082✔
110
        if (nearest_newline_index != std::string_view::npos)
548,082✔
111
            m_filepos.col = it_pos - nearest_newline_index;
537,081✔
112
        else
113
            m_filepos.col = it_pos + 1;
11,001✔
114
    }
548,092✔
115

116
    FilePosition BaseParser::getCursor() const
935,543✔
117
    {
935,543✔
118
        return m_filepos;
935,543✔
119
    }
120

121
    CodeErrorContext BaseParser::generateErrorContext(const std::string& expr)
289,595✔
122
    {
289,595✔
123
        const auto [row, col] = getCursor();
289,595✔
124

125
        return CodeErrorContext(
289,595✔
126
            m_filename,
289,595✔
127
            row,
289,595✔
128
            col,
289,595✔
129
            expr,
289,595✔
130
            m_sym);
289,595✔
131
    }
289,595✔
132

133
    void BaseParser::error(const std::string& error, std::string exp, const std::optional<CodeErrorContext>& additional_context)
45✔
134
    {
45✔
135
        const auto [row, col] = getCursor();
135✔
136
        throw CodeError(
135✔
137
            error,
45✔
138
            CodeErrorContext(m_filename, row, col, std::move(exp), m_sym),
45✔
139
            additional_context);
45✔
140
    }
90✔
141

142
    void BaseParser::errorWithNextToken(const std::string& message, const std::optional<CodeErrorContext>& additional_context)
37✔
143
    {
37✔
144
        const auto pos = getCount();
37✔
145
        std::string next_token;
37✔
146

147
        anyUntil(IsEither(IsInlineSpace, IsEither(IsChar('('), IsChar(')'))), &next_token);
37✔
148
        backtrack(pos);
37✔
149

150
        error(message, next_token, additional_context);
37✔
151
    }
37✔
152

153
    void BaseParser::expectSuffixOrError(const char suffix, const std::string& context, const std::optional<CodeErrorContext>& additional_context)
21,778✔
154
    {
21,778✔
155
        if (!accept(IsChar(suffix)))
21,787✔
156
            errorWithNextToken(fmt::format("Missing '{}' {}", suffix, context), additional_context);
9✔
157
    }
21,778✔
158

159
    bool BaseParser::accept(const CharPred& t, std::string* s)
3,223,903✔
160
    {
3,223,903✔
161
        if (isEOF())
3,223,903✔
162
            return false;
534✔
163

164
        // return false if the predicate couldn't consume the symbol
165
        if (!t(m_sym.codepoint()))
3,223,369✔
166
            return false;
1,645,872✔
167
        // otherwise, add it to the string and go to the next symbol
168
        if (s != nullptr)
1,577,497✔
169
            *s += m_sym.c_str();
1,249,260✔
170

171
        next();
1,577,497✔
172
        return true;
1,577,497✔
173
    }
3,223,903✔
174

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

187
    std::string BaseParser::peek() const
5✔
188
    {
5✔
189
        return m_sym.c_str();
5✔
190
    }
×
191

192
    bool BaseParser::space(std::string* s)
311,892✔
193
    {
311,892✔
194
        if (accept(IsSpace))
311,892✔
195
        {
196
            if (s != nullptr)
114,274✔
197
                s->push_back(' ');
×
198
            // loop while there are still ' ' to consume
199
            while (accept(IsSpace))
150,293✔
200
                ;
201
            return true;
114,274✔
202
        }
203
        return false;
197,618✔
204
    }
311,892✔
205

206
    bool BaseParser::inlineSpace(std::string* s)
11,870✔
207
    {
11,870✔
208
        if (accept(IsInlineSpace))
11,870✔
209
        {
210
            if (s != nullptr)
787✔
211
                s->push_back(' ');
×
212
            // loop while there are still ' ' to consume
213
            while (accept(IsInlineSpace))
795✔
214
                ;
215
            return true;
787✔
216
        }
217
        return false;
11,083✔
218
    }
11,870✔
219

220
    bool BaseParser::comment(std::string* s)
322,994✔
221
    {
322,994✔
222
        if (accept(IsChar('#'), s))
322,994✔
223
        {
224
            while (accept(IsNot(IsChar('\n')), s))
415,395✔
225
                ;
226
            accept(IsChar('\n'), s);
12,766✔
227
            return true;
12,766✔
228
        }
229
        return false;
310,228✔
230
    }
322,994✔
231

232
    bool BaseParser::spaceComment(std::string* s)
11,832✔
233
    {
11,832✔
234
        bool matched = false;
11,832✔
235

236
        inlineSpace();
11,832✔
237
        while (!isEOF() && comment(s))
11,870✔
238
        {
239
            inlineSpace();
38✔
240
            matched = true;
38✔
241
        }
242

243
        return matched;
11,832✔
244
    }
11,832✔
245

246
    bool BaseParser::newlineOrComment(std::string* s)
298,878✔
247
    {
298,878✔
248
        bool matched = false;
298,878✔
249

250
        space();
298,878✔
251
        while (!isEOF() && comment(s))
311,606✔
252
        {
253
            space();
12,728✔
254
            matched = true;
12,728✔
255
        }
256

257
        return matched;
298,878✔
258
    }
298,878✔
259

260
    bool BaseParser::prefix(const char c)
172,148✔
261
    {
172,148✔
262
        if (!accept(IsChar(c)))
172,148✔
263
            return false;
88,884✔
264
        return true;
83,264✔
265
    }
172,148✔
266

267
    bool BaseParser::number(std::string* s)
125,284✔
268
    {
125,284✔
269
        if (accept(IsDigit, s))
125,284✔
270
        {
271
            // consume all the digits available,
272
            // stop when the symbol isn't a digit anymore
273
            while (accept(IsDigit, s))
70,734✔
274
                ;
275
            return true;
70,140✔
276
        }
277
        return false;
55,144✔
278
    }
125,284✔
279

280
    bool BaseParser::signedNumber(std::string* s)
125,200✔
281
    {
125,200✔
282
        accept(IsMinus, s);
125,200✔
283
        if (!number(s))
125,200✔
284
            return false;
55,144✔
285

286
        // (optional) floating part
287
        accept(IsChar('.'), s) && number(s);
70,056✔
288
        // (optional) scientific part
289
        if (accept(IsEither(IsChar('e'), IsChar('E')), s))
70,056✔
290
        {
291
            accept(IsEither(IsMinus, IsChar('+')), s);
5✔
292
            number(s);
5✔
293
        }
5✔
294

295
        return true;
70,056✔
296
    }
125,200✔
297

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

309
    bool BaseParser::name(std::string* s)
236,582✔
310
    {
236,582✔
311
        const auto alpha_symbols = IsEither(IsAlpha, IsSymbol);
236,582✔
312
        const auto alnum_symbols = IsEither(IsAlnum, IsSymbol);
236,582✔
313

314
        if (accept(alpha_symbols, s))
236,582✔
315
        {
316
            while (accept(alnum_symbols, s))
730,562✔
317
                ;
318
            return true;
159,345✔
319
        }
320
        return false;
77,237✔
321
    }
236,582✔
322

323
    bool BaseParser::sequence(const std::string& s)
53,952✔
324
    {
53,952✔
325
        return std::ranges::all_of(s, [this](const char c) {
108,050✔
326
            return accept(IsChar(c));
54,098✔
327
        });
×
328
    }
329

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

341
    bool BaseParser::anyUntil(const CharPred& delim, std::string* s)
37✔
342
    {
37✔
343
        if (accept(IsNot(delim), s))
37✔
344
        {
345
            while (accept(IsNot(delim), s))
1,267✔
346
                ;
347
            return true;
9✔
348
        }
349
        return false;
28✔
350
    }
37✔
351

352
    bool BaseParser::oneOf(const std::initializer_list<std::string> words, std::string* s)
117,556✔
353
    {
117,556✔
354
        std::string buffer;
117,556✔
355
        if (!name(&buffer))
117,556✔
356
            return false;
9,432✔
357

358
        if (s)
108,124✔
359
            *s = buffer;
19,334✔
360

361
        return std::ranges::any_of(words, [&buffer](const std::string& word) {
248,139✔
362
            return word == buffer;
140,015✔
363
        });
364
    }
117,556✔
365
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc