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

ArkScript-lang / Ark / 27218257428

09 Jun 2026 03:48PM UTC coverage: 94.338% (-0.005%) from 94.343%
27218257428

push

github

SuperFola
chore: ArkScript 4.7.0 prerelease 1

10281 of 10898 relevant lines covered (94.34%)

925814.04 hits per line

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

95.92
/src/arkreactor/Compiler/AST/BaseParser.cpp
1
#include <Ark/Compiler/AST/BaseParser.hpp>
2
#include <Ark/Error/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)
111,587✔
12
    {
111,587✔
13
        // search for an existing new line position
14
        if (std::ranges::find_if(m_it_to_row, [it](const auto& pair) {
53,055,240✔
15
                return pair.first == it;
52,943,653✔
16
            }) != m_it_to_row.end())
111,587✔
17
            return;
6,208✔
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())
105,379✔
21
        {
22
            m_it_to_row.emplace_back(it, row);
813✔
23
            return;
813✔
24
        }
25

26
        for (std::size_t i = 0, end = m_it_to_row.size(); i < end; ++i)
48,968,846✔
27
        {
28
            auto current_it = m_it_to_row[i].first;
48,864,280✔
29
            auto next_it = i + 1 < end ? m_it_to_row[i + 1].first : m_str.end();
48,864,280✔
30
            if (current_it < it && it < next_it)
48,864,280✔
31
            {
32
                m_it_to_row.insert(
209,132✔
33
                    m_it_to_row.begin() + static_cast<decltype(m_it_to_row)::difference_type>(i) + 1,
104,566✔
34
                    std::make_pair(it, row));
104,566✔
35
                break;
104,566✔
36
            }
37
        }
48,864,280✔
38
    }
111,587✔
39

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

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

54
        if (*m_it == '\n')
6,101,802✔
55
        {
56
            ++m_filepos.row;
111,587✔
57
            m_filepos.col = 0;
111,587✔
58
            registerNewLine(m_it, m_filepos.row);
111,587✔
59
        }
111,587✔
60
        else if (m_sym.isPrintable())
5,990,215✔
61
            m_filepos.col += m_sym.size();
5,990,019✔
62
    }
6,102,643✔
63

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

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

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

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

82
    void BaseParser::backtrack(const long n)
2,313,500✔
83
    {
2,313,500✔
84
        if (std::cmp_less(n, m_str.size()))
2,313,500✔
85
            m_it = m_str.begin() + n;
2,313,500✔
86
        else
87
            return;
×
88

89
        auto [it, sym] = utf8_char_t::at(m_it, m_str.end());
1,186,160,224✔
90
        m_next_it = it;
2,313,500✔
91
        m_sym = sym;
2,313,500✔
92

93
        // search for the nearest it < m_it in the map to know the line number
94
        for (const auto& [at, line] : m_it_to_row)
1,186,167,781✔
95
        {
96
            if (it <= at)
1,183,846,724✔
97
            {
98
                m_filepos.row = line - 1;
7,557✔
99
                break;
7,557✔
100
            }
101
        }
1,183,846,724✔
102
        // compute the position in the line
103
        const auto it_pos = static_cast<std::size_t>(std::distance(m_str.begin(), m_it));
2,313,500✔
104
        const std::string_view view { m_str.begin(), m_it };
2,313,500✔
105
        const auto nearest_newline_index = view.find_last_of('\n');
2,313,500✔
106
        if (nearest_newline_index != std::string_view::npos)
2,313,500✔
107
            m_filepos.col = it_pos - nearest_newline_index;
2,291,585✔
108
        else
109
            m_filepos.col = it_pos + 1;
21,915✔
110
    }
2,313,500✔
111

112
    FilePosition BaseParser::getCursor() const
2,935,480✔
113
    {
2,935,480✔
114
        return m_filepos;
2,935,480✔
115
    }
116

117
    CodeErrorContext BaseParser::generateErrorContextAtCurrentPosition() const
1,198,424✔
118
    {
1,198,424✔
119
        const auto [row, col] = getCursor();
1,198,424✔
120

121
        return CodeErrorContext(
1,198,424✔
122
            m_filename,
1,198,424✔
123
            // for additional contexts, the end position is useless
124
            FileSpan { .start = FilePos { .line = row, .column = col }, .end = std::nullopt });
3,595,272✔
125
    }
1,198,424✔
126

127
    void BaseParser::error(const std::string& error, const FilePosition start_at, const std::optional<CodeErrorContext>& additional_context) const
60✔
128
    {
60✔
129
        const auto [row, col] = getCursor();
180✔
130
        throw CodeError(
180✔
131
            error,
60✔
132
            CodeErrorContext(
60✔
133
                m_filename,
60✔
134
                FileSpan {
60✔
135
                    .start = FilePos { .line = start_at.row, .column = start_at.col },
60✔
136
                    .end = FilePos { .line = row, .column = col } }),
180✔
137
            additional_context);
60✔
138
    }
120✔
139

140
    void BaseParser::errorWithNextToken(const std::string& message, const std::optional<CodeErrorContext>& additional_context)
43✔
141
    {
43✔
142
        const auto filepos = getCursor();
43✔
143

144
        anyUntil(IsEither(IsInlineSpace, IsEither(IsChar('('), IsChar(')'))));
43✔
145
        error(message, filepos, additional_context);
43✔
146
    }
43✔
147

148
    void BaseParser::expectSuffixOrError(const char suffix, const std::string& context, const std::optional<CodeErrorContext>& additional_context)
89,590✔
149
    {
89,590✔
150
        if (!accept(IsChar(suffix)))
89,600✔
151
            errorWithNextToken(fmt::format("Missing '{}' {}", suffix, context), additional_context);
10✔
152
    }
89,590✔
153

154
    bool BaseParser::accept(const CharPred& t, std::string* s)
11,689,898✔
155
    {
11,689,898✔
156
        if (isEOF())
11,689,898✔
157
            return false;
907✔
158

159
        // return false if the predicate couldn't consume the symbol
160
        if (!t(m_sym.codepoint()))
11,688,991✔
161
            return false;
5,596,934✔
162
        // otherwise, add it to the string and go to the next symbol
163
        if (s != nullptr)
6,092,057✔
164
            *s += m_sym.c_str();
4,908,918✔
165

166
        next();
6,092,057✔
167
        return true;
6,092,057✔
168
    }
11,689,898✔
169

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

182
    std::string BaseParser::peek() const
6✔
183
    {
6✔
184
        return m_sym.c_str();
6✔
185
    }
×
186

187
    bool BaseParser::space(std::string* s)
1,031,239✔
188
    {
1,031,239✔
189
        if (accept(IsSpace))
1,031,239✔
190
        {
191
            if (s != nullptr)
274,343✔
192
                s->push_back(' ');
×
193
            // loop while there are still ' ' to consume
194
            while (accept(IsSpace))
462,084✔
195
                ;
196
            return true;
274,343✔
197
        }
198
        return false;
756,896✔
199
    }
1,031,239✔
200

201
    bool BaseParser::inlineSpace(std::string* s)
43,792✔
202
    {
43,792✔
203
        if (accept(IsInlineSpace))
43,792✔
204
        {
205
            if (s != nullptr)
4,182✔
206
                s->push_back(' ');
×
207
            // loop while there are still ' ' to consume
208
            while (accept(IsInlineSpace))
4,191✔
209
                ;
210
            return true;
4,182✔
211
        }
212
        return false;
39,610✔
213
    }
43,792✔
214

215
    bool BaseParser::comment(std::string* s)
1,073,757✔
216
    {
1,073,757✔
217
        if (accept(IsChar('#'), s))
1,073,757✔
218
        {
219
            while (accept(IsNot(IsChar('\n')), s))
1,776,517✔
220
                ;
221
            accept(IsChar('\n'), s);
50,790✔
222
            return true;
50,790✔
223
        }
224
        return false;
1,022,967✔
225
    }
1,073,757✔
226

227
    std::string BaseParser::spaceComment()
43,751✔
228
    {
43,751✔
229
        std::string s;
43,751✔
230

231
        inlineSpace();
43,751✔
232
        while (!isEOF() && comment(&s))
43,792✔
233
            inlineSpace();
41✔
234

235
        return s;
43,751✔
236
    }
43,751✔
237

238
    std::string BaseParser::newlineOrComment()
980,077✔
239
    {
980,077✔
240
        std::string s;
980,077✔
241

242
        space();
980,077✔
243
        while (!isEOF() && comment(&s))
1,030,826✔
244
            space();
50,749✔
245

246
        return s;
980,077✔
247
    }
980,077✔
248

249
    bool BaseParser::prefix(const char c)
710,591✔
250
    {
710,591✔
251
        if (!accept(IsChar(c)))
710,591✔
252
            return false;
377,534✔
253
        return true;
333,057✔
254
    }
710,591✔
255

256
    bool BaseParser::number(std::string* s)
327,753✔
257
    {
327,753✔
258
        if (accept(IsDigit, s))
327,753✔
259
        {
260
            // consume all the digits available,
261
            // stop when the symbol isn't a digit anymore
262
            while (accept(IsDigit, s))
95,124✔
263
                ;
264
            return true;
89,376✔
265
        }
266
        return false;
238,377✔
267
    }
327,753✔
268

269
    bool BaseParser::signedNumber(std::string* s)
327,540✔
270
    {
327,540✔
271
        accept(IsMinus, s);
327,540✔
272
        if (!number(s))
327,540✔
273
            return false;
238,377✔
274

275
        // (optional) floating part
276
        accept(IsChar('.'), s) && number(s);
89,163✔
277
        // (optional) scientific part
278
        if (accept(IsEither(IsChar('e'), IsChar('E')), s))
89,163✔
279
        {
280
            accept(IsEither(IsMinus, IsChar('+')), s);
42✔
281
            number(s);
42✔
282
        }
42✔
283

284
        return true;
89,163✔
285
    }
327,540✔
286

287
    bool BaseParser::hexNumber(unsigned int length, std::string* s)
19✔
288
    {
19✔
289
        while (length != 0)
105✔
290
        {
291
            if (!accept(IsHex, s))
89✔
292
                return false;
3✔
293
            --length;
86✔
294
        }
295
        return true;
16✔
296
    }
19✔
297

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

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

314
        if (accept(alpha_symbols, s))
976,760✔
315
        {
316
            while (accept(alnum_symbols, s))
2,892,027✔
317
                ;
318
            return true;
684,212✔
319
        }
320
        return false;
292,548✔
321
    }
976,760✔
322

323
    bool BaseParser::sequence(const std::string& s)
231,490✔
324
    {
231,490✔
325
        return std::ranges::all_of(s, [this](const char c) {
463,308✔
326
            return accept(IsChar(c));
231,818✔
327
        });
×
328
    }
329

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

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

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

358
        if (s)
462,824✔
359
            *s = buffer;
82,156✔
360

361
        return std::ranges::any_of(words, [&buffer](const std::string& word) {
1,061,342✔
362
            return word == buffer;
598,518✔
363
        });
364
    }
472,704✔
365
}
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