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

ArkScript-lang / Ark / 15214772429

23 May 2025 04:19PM UTC coverage: 86.895% (+0.2%) from 86.726%
15214772429

push

github

SuperFola
feat(compiler, vm): adding new AT_SYM_SYM and AT_SYM_INDEX_SYM_INDEX super instructions to get elements from list in a single instruction

40 of 44 new or added lines in 2 files covered. (90.91%)

178 existing lines in 8 files now uncovered.

7095 of 8165 relevant lines covered (86.9%)

86688.13 hits per line

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

97.01
/src/arkreactor/Compiler/AST/Parser.cpp
1
#include <Ark/Compiler/AST/Parser.hpp>
2

3
#include <fmt/core.h>
4

5
namespace Ark::internal
6
{
7
    Parser::Parser(const unsigned debug, const bool interpret) :
894✔
8
        BaseParser(), m_interpret(interpret), m_logger("Parser", debug),
447✔
9
        m_ast(NodeType::List), m_imports({}), m_allow_macro_behavior(0),
447✔
10
        m_nested_nodes(0)
447✔
11
    {
447✔
12
        m_ast.push_back(Node(Keyword::Begin));
447✔
13

14
        m_parsers = {
894✔
15
            [this]() {
29,507✔
16
                return wrapped(&Parser::letMutSet, "variable assignment or declaration");
29,060✔
17
            },
5✔
18
            [this]() {
25,617✔
19
                return wrapped(&Parser::function, "function");
25,170✔
20
            },
4✔
21
            [this]() {
24,242✔
22
                return wrapped(&Parser::condition, "condition");
23,795✔
23
            },
3✔
24
            [this]() {
23,601✔
25
                return wrapped(&Parser::loop, "loop");
23,154✔
26
            },
2✔
27
            [this]() {
23,062✔
28
                return import_();
22,615✔
29
            },
30
            [this]() {
22,902✔
31
                return block();
22,455✔
32
            },
33
            [this]() {
21,516✔
34
                return wrapped(&Parser::macroCondition, "$if");
21,069✔
35
            },
2✔
36
            [this]() {
21,465✔
37
                return macro();
21,018✔
38
            },
39
            [this]() {
21,317✔
40
                return wrapped(&Parser::del, "del");
20,870✔
41
            },
1✔
42
            [this]() {
21,306✔
43
                return functionCall();
20,859✔
44
            },
45
            [this]() {
12,000✔
46
                return list();
11,553✔
47
            }
48
        };
49
    }
447✔
50

51
    void Parser::process(const std::string& filename, const std::string& code)
447✔
52
    {
447✔
53
        m_logger.traceStart("process");
489✔
54
        initParser(filename, code);
447✔
55

56
        while (!isEOF())
2,472✔
57
        {
58
            std::string comment;
2,433✔
59
            newlineOrComment(&comment);
2,433✔
60
            if (isEOF())
2,433✔
61
            {
62
                if (!comment.empty())
366✔
63
                    m_ast.list().back().attachCommentAfter(comment);
1✔
64
                break;
366✔
65
            }
66

67
            const auto pos = getCount();
2,067✔
68
            if (auto n = node())
4,134✔
69
            {
70
                m_ast.push_back(n->attachNearestCommentBefore(n->comment() + comment));
2,025✔
71
                comment.clear();
2,025✔
72
                if (spaceComment(&comment))
2,025✔
73
                    m_ast.list().back().attachCommentAfter(comment);
21✔
74
            }
2,025✔
75
            else
76
            {
77
                backtrack(pos);
4✔
78
                std::string out = peek();
4✔
79
                std::string message;
4✔
80
                if (out == ")")
4✔
81
                    message = "Unexpected closing paren";
1✔
82
                else if (out == "}")
3✔
83
                    message = "Unexpected closing bracket";
1✔
84
                else if (out == "]")
2✔
85
                    message = "Unexpected closing square bracket";
1✔
86
                else
87
                    errorWithNextToken("invalid syntax, expected node");
1✔
88
                errorWithNextToken(message);
3✔
89
            }
4✔
90
        }
2,433✔
91

92
        m_logger.traceEnd();
405✔
93
    }
447✔
94

95
    const Node& Parser::ast() const noexcept
419✔
96
    {
419✔
97
        return m_ast;
419✔
98
    }
99

100
    const std::vector<Import>& Parser::imports() const
351✔
101
    {
351✔
102
        return m_imports;
351✔
103
    }
104

105
    Node& Parser::setNodePosAndFilename(Node& node, const std::optional<FilePosition>& cursor) const
303,781✔
106
    {
303,781✔
107
        if (node.line() != 0 || node.col() != 0)
303,781✔
108
            return node;
41,340✔
109

110
        const auto [row, col] = cursor.value_or(getCursor());
262,441✔
111
        node.setPos(row, col);
262,441✔
112
        node.setFilename(m_filename);
262,441✔
113
        return node;
262,441✔
114
    }
303,781✔
115

116
    std::optional<Node> Parser::node()
29,061✔
117
    {
29,061✔
118
        ++m_nested_nodes;
29,061✔
119

120
        if (m_nested_nodes > MaxNestedNodes)
29,061✔
121
            errorWithNextToken(fmt::format("Too many nested node while parsing, exceeds limit of {}. Consider rewriting your code by breaking it in functions and macros.", MaxNestedNodes));
1,065✔
122

123
        // save current position in buffer to be able to go back if needed
124
        const auto position = getCount();
29,060✔
125
        std::optional<Node> result = std::nullopt;
29,060✔
126

127
        for (auto&& parser : m_parsers)
270,678✔
128
        {
129
            result = parser();
241,618✔
130

131
            if (result)
240,554✔
132
                break;
17,275✔
133
            backtrack(position);
223,279✔
134
        }
241,618✔
135

136
        // return std::nullopt only on parsing error, nothing matched, the user provided terrible code
137
        --m_nested_nodes;
27,996✔
138
        return result;
27,996✔
139
    }
29,061✔
140

141
    std::optional<Node> Parser::letMutSet()
16,129✔
142
    {
16,129✔
143
        std::optional<Node> leaf { NodeType::List };
16,129✔
144
        setNodePosAndFilename(leaf.value());
16,129✔
145

146
        std::string token;
16,129✔
147
        if (!oneOf({ "let", "mut", "set" }, &token))
16,129✔
148
            return std::nullopt;
12,239✔
149
        std::string comment;
3,890✔
150
        newlineOrComment(&comment);
3,890✔
151
        leaf->attachNearestCommentBefore(comment);
3,890✔
152

153
        if (token == "let")
3,890✔
154
            leaf->push_back(Node(Keyword::Let));
1,696✔
155
        else if (token == "mut")
2,194✔
156
            leaf->push_back(Node(Keyword::Mut));
1,238✔
157
        else  // "set"
158
            leaf->push_back(Node(Keyword::Set));
956✔
159

160
        if (m_allow_macro_behavior > 0)
3,890✔
161
        {
162
            const auto position = getCount();
22✔
163
            if (const auto value = nodeOrValue(); value.has_value())
44✔
164
            {
165
                const auto sym = value.value();
22✔
166
                if (sym.nodeType() == NodeType::List || sym.nodeType() == NodeType::Symbol || sym.nodeType() == NodeType::Macro || sym.nodeType() == NodeType::Spread)
22✔
167
                    leaf->push_back(sym);
21✔
168
                else
169
                    error(fmt::format("Can not use a {} as a symbol name, even in a macro", nodeTypes[static_cast<std::size_t>(sym.nodeType())]), sym.repr());
1✔
170
            }
22✔
171
            else
UNCOV
172
                backtrack(position);
×
173
        }
22✔
174

175
        if (leaf->constList().size() == 1)
3,889✔
176
        {
177
            // we haven't parsed anything while in "macro state"
178
            std::string symbol_name;
3,868✔
179
            if (!name(&symbol_name))
3,868✔
180
                errorWithNextToken(token + " needs a symbol");
2✔
181

182
            leaf->push_back(Node(NodeType::Symbol, symbol_name));
3,866✔
183
        }
3,868✔
184

185
        comment.clear();
3,887✔
186
        newlineOrComment(&comment);
3,887✔
187

188
        if (auto value = nodeOrValue(); value.has_value())
7,774✔
189
            leaf->push_back(value.value().attachNearestCommentBefore(comment));
3,885✔
190
        else
UNCOV
191
            errorWithNextToken("Expected a value");
×
192

193
        return leaf;
3,885✔
194
    }
16,134✔
195

196
    std::optional<Node> Parser::del()
9,317✔
197
    {
9,317✔
198
        std::optional<Node> leaf { NodeType::List };
9,317✔
199
        setNodePosAndFilename(leaf.value());
9,317✔
200

201
        if (!oneOf({ "del" }))
9,317✔
202
            return std::nullopt;
9,306✔
203
        leaf->push_back(Node(Keyword::Del));
11✔
204

205
        std::string comment;
11✔
206
        newlineOrComment(&comment);
11✔
207

208
        std::string symbol_name;
11✔
209
        if (!name(&symbol_name))
11✔
210
            errorWithNextToken("del needs a symbol");
1✔
211

212
        leaf->push_back(Node(NodeType::Symbol, symbol_name));
10✔
213
        leaf->list().back().attachNearestCommentBefore(comment);
10✔
214
        setNodePosAndFilename(leaf->list().back());
10✔
215

216
        return leaf;
10✔
217
    }
9,318✔
218

219
    std::optional<Node> Parser::condition()
10,864✔
220
    {
10,864✔
221
        std::optional<Node> leaf { NodeType::List };
10,864✔
222
        setNodePosAndFilename(leaf.value());
10,864✔
223

224
        if (!oneOf({ "if" }))
10,864✔
225
            return std::nullopt;
10,223✔
226

227
        std::string comment;
641✔
228
        newlineOrComment(&comment);
641✔
229

230
        leaf->push_back(Node(Keyword::If));
641✔
231

232
        if (auto cond_expr = nodeOrValue(); cond_expr.has_value())
1,282✔
233
            leaf->push_back(cond_expr.value().attachNearestCommentBefore(comment));
640✔
234
        else
235
            errorWithNextToken("`if' needs a valid condition");
1✔
236

237
        comment.clear();
640✔
238
        newlineOrComment(&comment);
640✔
239

240
        if (auto value_if_true = nodeOrValue(); value_if_true.has_value())
1,280✔
241
            leaf->push_back(value_if_true.value().attachNearestCommentBefore(comment));
639✔
242
        else
243
            errorWithNextToken("Expected a node or value after condition");
1✔
244

245
        comment.clear();
639✔
246
        newlineOrComment(&comment);
639✔
247

248
        if (auto value_if_false = nodeOrValue(); value_if_false.has_value())
1,278✔
249
        {
250
            leaf->push_back(value_if_false.value().attachNearestCommentBefore(comment));
435✔
251
            comment.clear();
435✔
252
            if (newlineOrComment(&comment))
435✔
253
                leaf->list().back().attachCommentAfter(comment);
2✔
254
        }
435✔
255
        else if (!comment.empty())
204✔
256
            leaf->attachCommentAfter(comment);
2✔
257

258
        setNodePosAndFilename(leaf->list().back());
639✔
259
        return leaf;
639✔
260
    }
10,866✔
261

262
    std::optional<Node> Parser::loop()
10,223✔
263
    {
10,223✔
264
        std::optional<Node> leaf { NodeType::List };
10,223✔
265
        setNodePosAndFilename(leaf.value());
10,223✔
266

267
        if (!oneOf({ "while" }))
10,223✔
268
            return std::nullopt;
9,684✔
269

270
        std::string comment;
539✔
271
        newlineOrComment(&comment);
539✔
272

273
        leaf->push_back(Node(Keyword::While));
539✔
274

275
        if (auto cond_expr = nodeOrValue(); cond_expr.has_value())
1,078✔
276
            leaf->push_back(cond_expr.value().attachNearestCommentBefore(comment));
538✔
277
        else
278
            errorWithNextToken("`while' needs a valid condition");
1✔
279

280
        comment.clear();
538✔
281
        newlineOrComment(&comment);
538✔
282

283
        if (auto body = nodeOrValue(); body.has_value())
1,076✔
284
            leaf->push_back(body.value().attachNearestCommentBefore(comment));
537✔
285
        else
286
            errorWithNextToken("Expected a node or value after loop condition");
1✔
287

288
        setNodePosAndFilename(leaf->list().back());
537✔
289
        return leaf;
537✔
290
    }
10,225✔
291

292
    std::optional<Node> Parser::import_()
22,615✔
293
    {
22,615✔
294
        std::optional<Node> leaf { NodeType::List };
22,615✔
295
        setNodePosAndFilename(leaf.value());
22,615✔
296

297
        auto context = generateErrorContext("(");
22,615✔
298
        if (!accept(IsChar('(')))
22,615✔
299
            return std::nullopt;
12,931✔
300

301
        std::string comment;
9,684✔
302
        newlineOrComment(&comment);
9,684✔
303
        leaf->attachNearestCommentBefore(comment);
9,684✔
304

305
        if (!oneOf({ "import" }))
9,684✔
306
            return std::nullopt;
9,524✔
307
        comment.clear();
160✔
308
        newlineOrComment(&comment);
160✔
309
        leaf->push_back(Node(Keyword::Import));
160✔
310

311
        Import import_data;
160✔
312

313
        const auto pos = getCount();
160✔
314
        if (!packageName(&import_data.prefix))
160✔
315
            errorWithNextToken("Import expected a package name");
1✔
316

317
        if (import_data.prefix.size() > 255)
159✔
318
        {
319
            backtrack(pos);
1✔
320
            errorWithNextToken(fmt::format("Import name too long, expected at most 255 characters, got {}", import_data.prefix.size()));
1✔
UNCOV
321
        }
×
322
        import_data.package.push_back(import_data.prefix);
158✔
323

324
        const auto [row, col] = getCursor();
158✔
325
        import_data.col = col;
158✔
326
        import_data.line = row;
158✔
327

328
        Node packageNode(NodeType::List);
158✔
329
        setNodePosAndFilename(packageNode.attachNearestCommentBefore(comment));
158✔
330
        packageNode.push_back(Node(NodeType::Symbol, import_data.prefix));
158✔
331

332
        // first, parse the package name
333
        while (!isEOF())
275✔
334
        {
335
            // parsing package folder.foo.bar.yes
336
            if (accept(IsChar('.')))
275✔
337
            {
338
                std::string path;
120✔
339
                if (!packageName(&path))
120✔
340
                    errorWithNextToken("Package name expected after '.'");
2✔
341
                else
342
                {
343
                    packageNode.push_back(Node(NodeType::Symbol, path));
118✔
344
                    setNodePosAndFilename(packageNode.list().back());
118✔
345
                    import_data.package.push_back(path);
118✔
346
                    import_data.prefix = path;  // in the end we will store the last element of the package, which is what we want
118✔
347

348
                    if (path.size() > 255)
118✔
349
                    {
350
                        backtrack(pos);
1✔
351
                        errorWithNextToken(fmt::format("Import name too long, expected at most 255 characters, got {}", path.size()));
1✔
UNCOV
352
                    }
×
353
                }
354
            }
120✔
355
            else if (accept(IsChar(':')) && accept(IsChar('*')))  // parsing :*
155✔
356
            {
357
                leaf->push_back(packageNode);
10✔
358
                leaf->push_back(Node(NodeType::Symbol, "*"));
10✔
359
                setNodePosAndFilename(leaf->list().back());
10✔
360

361
                space();
10✔
362
                expectSuffixOrError(')', fmt::format("in import `{}'", import_data.toPackageString()), context);
10✔
363

364
                // save the import data structure to know we encounter an import node, and retrieve its data more easily later on
365
                import_data.with_prefix = false;
10✔
366
                import_data.is_glob = true;
10✔
367
                m_imports.push_back(import_data);
10✔
368

369
                return leaf;
10✔
370
            }
371
            else
372
                break;
145✔
373
        }
374

375
        Node symbols(NodeType::List);
145✔
376
        setNodePosAndFilename(symbols);
145✔
377
        // then parse the symbols to import, if any
378
        if (space())
145✔
379
        {
380
            comment.clear();
64✔
381
            newlineOrComment(&comment);
64✔
382

383
            while (!isEOF())
100✔
384
            {
385
                if (accept(IsChar(':')))  // parsing potential :a :b :c
100✔
386
                {
387
                    std::string symbol_name;
97✔
388
                    if (!name(&symbol_name))
97✔
389
                        errorWithNextToken("Expected a valid symbol to import");
1✔
390
                    if (symbol_name == "*")
96✔
391
                        error(fmt::format("Glob patterns can not be separated from the package, use (import {}:*) instead", import_data.toPackageString()), symbol_name);
1✔
392

393
                    if (symbol_name.size() >= 2 && symbol_name[symbol_name.size() - 2] == ':' && symbol_name.back() == '*')
95✔
394
                    {
395
                        backtrack(getCount() - 2);  // we can backtrack n-2 safely here because we know the previous chars were ":*"
1✔
396
                        error("Glob pattern can not follow a symbol to import", ":*");
1✔
UNCOV
397
                    }
×
398

399
                    symbols.push_back(Node(NodeType::Symbol, symbol_name).attachNearestCommentBefore(comment));
94✔
400
                    comment.clear();
94✔
401
                    setNodePosAndFilename(symbols.list().back());
94✔
402
                    import_data.symbols.push_back(symbol_name);
94✔
403
                    // we do not need the prefix when importing specific symbols
404
                    import_data.with_prefix = false;
94✔
405
                }
97✔
406

407
                if (!space())
97✔
408
                    break;
61✔
409
                comment.clear();
36✔
410
                newlineOrComment(&comment);
36✔
411
            }
412

413
            if (!comment.empty() && !symbols.list().empty())
61✔
414
                symbols.list().back().attachCommentAfter(comment);
2✔
415
        }
61✔
416

417
        leaf->push_back(packageNode);
142✔
418
        leaf->push_back(symbols);
142✔
419
        // save the import data
420
        m_imports.push_back(import_data);
142✔
421

422
        comment.clear();
142✔
423
        if (newlineOrComment(&comment))
142✔
424
            leaf->list().back().attachCommentAfter(comment);
1✔
425

426
        expectSuffixOrError(')', fmt::format("in import `{}'", import_data.toPackageString()), context);
142✔
427
        return leaf;
142✔
428
    }
22,623✔
429

430
    std::optional<Node> Parser::block()
22,455✔
431
    {
22,455✔
432
        std::optional<Node> leaf { NodeType::List };
22,455✔
433
        setNodePosAndFilename(leaf.value());
22,455✔
434

435
        auto context = generateErrorContext("(");
22,455✔
436
        bool alt_syntax = false;
22,455✔
437
        std::string comment;
22,455✔
438
        if (accept(IsChar('(')))
22,455✔
439
        {
440
            newlineOrComment(&comment);
9,524✔
441
            if (!oneOf({ "begin" }))
9,524✔
442
                return std::nullopt;
9,516✔
443
        }
8✔
444
        else if (accept(IsChar('{')))
12,931✔
445
            alt_syntax = true;
1,378✔
446
        else
447
            return std::nullopt;
11,553✔
448

449
        leaf->push_back(Node(Keyword::Begin).attachNearestCommentBefore(comment));
1,386✔
450

451
        comment.clear();
1,386✔
452
        newlineOrComment(&comment);
1,386✔
453

454
        while (!isEOF())
6,383✔
455
        {
456
            if (auto value = nodeOrValue(); value.has_value())
12,764✔
457
            {
458
                leaf->push_back(value.value().attachNearestCommentBefore(comment));
4,997✔
459
                comment.clear();
4,997✔
460
                newlineOrComment(&comment);
4,997✔
461
            }
4,997✔
462
            else
463
                break;
1,385✔
464
        }
465

466
        newlineOrComment(&comment);
1,386✔
467
        expectSuffixOrError(alt_syntax ? '}' : ')', "to close block", context);
1,386✔
468
        setNodePosAndFilename(leaf->list().back());
1,385✔
469
        leaf->list().back().attachCommentAfter(comment);
1,385✔
470
        return leaf;
1,385✔
471
    }
22,456✔
472

473
    std::optional<Node> Parser::functionArgs()
1,354✔
474
    {
1,354✔
475
        expect(IsChar('('));
1,355✔
476
        std::optional<Node> args { NodeType::List };
1,354✔
477
        setNodePosAndFilename(args.value());
1,354✔
478

479
        std::string comment;
1,354✔
480
        newlineOrComment(&comment);
1,354✔
481
        args->attachNearestCommentBefore(comment);
1,354✔
482

483
        bool has_captures = false;
1,354✔
484

485
        while (!isEOF())
3,673✔
486
        {
487
            const auto pos = getCursor();
3,672✔
488
            if (accept(IsChar('&')))  // captures
3,672✔
489
            {
490
                has_captures = true;
202✔
491
                std::string capture;
202✔
492
                if (!name(&capture))
202✔
UNCOV
493
                    break;
×
494
                Node capture_node = Node(NodeType::Capture, capture).attachNearestCommentBefore(comment);
202✔
495
                setNodePosAndFilename(capture_node, pos);
202✔
496
                args->push_back(capture_node);
202✔
497
            }
202✔
498
            else
499
            {
500
                const auto count = getCount();
3,470✔
501
                std::string symbol_name;
3,470✔
502
                if (!name(&symbol_name))
3,470✔
503
                    break;
1,352✔
504
                if (has_captures)
2,118✔
505
                {
506
                    backtrack(count);
1✔
507
                    error("Captured variables should be at the end of the argument list", symbol_name);
1✔
UNCOV
508
                }
×
509

510
                Node arg_node = Node(NodeType::Symbol, symbol_name).attachNearestCommentBefore(comment);
2,117✔
511
                setNodePosAndFilename(arg_node, pos);
2,117✔
512
                args->push_back(arg_node);
2,117✔
513
            }
3,470✔
514

515
            comment.clear();
2,319✔
516
            newlineOrComment(&comment);
2,319✔
517
        }
3,672✔
518

519
        if (accept(IsChar(')')))
1,353✔
520
            return args;
1,351✔
521
        return std::nullopt;
2✔
522
    }
1,355✔
523

524
    std::optional<Node> Parser::function()
12,239✔
525
    {
12,239✔
526
        std::optional<Node> leaf { NodeType::List };
12,239✔
527
        setNodePosAndFilename(leaf.value());
12,239✔
528

529
        if (!oneOf({ "fun" }))
12,239✔
530
            return std::nullopt;
10,864✔
531
        leaf->push_back(Node(Keyword::Fun));
1,375✔
532

533
        std::string comment_before_args;
1,375✔
534
        newlineOrComment(&comment_before_args);
1,375✔
535

536
        while (m_allow_macro_behavior > 0)
1,375✔
537
        {
538
            const auto position = getCount();
21✔
539

540
            // args
541
            if (const auto value = nodeOrValue(); value.has_value())
42✔
542
            {
543
                // if value is nil, just add an empty argument bloc to prevent bugs when
544
                // declaring functions inside macros
545
                Node args = value.value();
21✔
546
                setNodePosAndFilename(args);
21✔
547
                if (args.nodeType() == NodeType::Symbol && args.string() == "nil")
21✔
548
                    leaf->push_back(Node(NodeType::List));
5✔
549
                else
550
                    leaf->push_back(args);
16✔
551
            }
21✔
552
            else
553
            {
554
                backtrack(position);
×
UNCOV
555
                break;
×
556
            }
557

558
            std::string comment;
21✔
559
            newlineOrComment(&comment);
21✔
560
            // body
561
            if (auto value = nodeOrValue(); value.has_value())
42✔
562
                leaf->push_back(value.value().attachNearestCommentBefore(comment));
21✔
563
            else
UNCOV
564
                errorWithNextToken("Expected a body for the function");
×
565
            setNodePosAndFilename(leaf->list().back());
21✔
566
            return leaf;
21✔
567
        }
21✔
568

569
        const auto position = getCount();
1,354✔
570
        if (auto args = functionArgs(); args.has_value())
2,708✔
571
            leaf->push_back(args.value().attachNearestCommentBefore(comment_before_args));
1,351✔
572
        else
573
        {
574
            backtrack(position);
2✔
575

576
            if (auto value = nodeOrValue(); value.has_value())
4✔
577
                leaf->push_back(value.value().attachNearestCommentBefore(comment_before_args));
1✔
578
            else
UNCOV
579
                errorWithNextToken("Expected an argument list");
×
580
        }
581

582
        std::string comment;
1,352✔
583
        newlineOrComment(&comment);
1,352✔
584

585
        if (auto value = nodeOrValue(); value.has_value())
2,704✔
586
            leaf->push_back(value.value().attachNearestCommentBefore(comment));
1,350✔
587
        else
588
            errorWithNextToken("Expected a body for the function");
2✔
589

590
        setNodePosAndFilename(leaf->list().back());
1,350✔
591
        return leaf;
1,350✔
592
    }
12,243✔
593

594
    std::optional<Node> Parser::macroCondition()
9,516✔
595
    {
9,516✔
596
        std::optional<Node> leaf { NodeType::Macro };
9,516✔
597
        setNodePosAndFilename(leaf.value());
9,516✔
598

599
        if (!oneOf({ "$if" }))
9,516✔
600
            return std::nullopt;
9,465✔
601
        leaf->push_back(Node(Keyword::If));
51✔
602

603
        std::string comment;
51✔
604
        newlineOrComment(&comment);
51✔
605
        leaf->attachNearestCommentBefore(comment);
51✔
606

607
        if (const auto cond_expr = nodeOrValue(); cond_expr.has_value())
102✔
608
            leaf->push_back(cond_expr.value());
50✔
609
        else
610
            errorWithNextToken("$if need a valid condition");
1✔
611

612
        comment.clear();
50✔
613
        newlineOrComment(&comment);
50✔
614

615
        if (auto value_if_true = nodeOrValue(); value_if_true.has_value())
100✔
616
            leaf->push_back(value_if_true.value().attachNearestCommentBefore(comment));
49✔
617
        else
618
            errorWithNextToken("Expected a node or value after condition");
1✔
619

620
        comment.clear();
49✔
621
        newlineOrComment(&comment);
49✔
622

623
        if (auto value_if_false = nodeOrValue(); value_if_false.has_value())
88✔
624
        {
625
            leaf->push_back(value_if_false.value().attachNearestCommentBefore(comment));
39✔
626
            comment.clear();
39✔
627
            newlineOrComment(&comment);
39✔
628
            leaf->list().back().attachCommentAfter(comment);
39✔
629
        }
39✔
630

631
        setNodePosAndFilename(leaf->list().back());
49✔
632
        return leaf;
49✔
633
    }
9,518✔
634

635
    std::optional<Node> Parser::macroArgs()
147✔
636
    {
147✔
637
        if (!accept(IsChar('(')))
150✔
638
            return std::nullopt;
22✔
639

640
        std::optional<Node> args { NodeType::List };
125✔
641
        setNodePosAndFilename(args.value());
125✔
642

643
        std::string comment;
125✔
644
        newlineOrComment(&comment);
125✔
645
        args->attachNearestCommentBefore(comment);
125✔
646

647
        std::vector<std::string> names;
125✔
648
        while (!isEOF())
304✔
649
        {
650
            const auto pos = getCount();
303✔
651

652
            std::string arg_name;
303✔
653
            if (!name(&arg_name))
303✔
654
                break;
123✔
655
            comment.clear();
180✔
656
            newlineOrComment(&comment);
180✔
657
            args->push_back(Node(NodeType::Symbol, arg_name).attachNearestCommentBefore(comment));
180✔
658

659
            if (std::ranges::find(names, arg_name) != names.end())
180✔
660
            {
661
                backtrack(pos);
1✔
662
                errorWithNextToken(fmt::format("Argument names must be unique, can not reuse `{}'", arg_name));
1✔
UNCOV
663
            }
×
664
            names.push_back(arg_name);
179✔
665
        }
303✔
666

667
        const auto pos = getCount();
124✔
668
        if (sequence("..."))
124✔
669
        {
670
            std::string spread_name;
45✔
671
            if (!name(&spread_name))
45✔
672
                errorWithNextToken("Expected a name for the variadic arguments list");
1✔
673
            args->push_back(Node(NodeType::Spread, spread_name));
44✔
674

675
            comment.clear();
44✔
676
            if (newlineOrComment(&comment))
44✔
677
                args->list().back().attachCommentAfter(comment);
2✔
678

679
            if (std::ranges::find(names, spread_name) != names.end())
44✔
680
            {
681
                backtrack(pos);
1✔
682
                errorWithNextToken(fmt::format("Argument names must be unique, can not reuse `{}'", spread_name));
1✔
UNCOV
683
            }
×
684
        }
45✔
685

686
        if (!accept(IsChar(')')))
122✔
687
            return std::nullopt;
32✔
688
        comment.clear();
90✔
689
        if (newlineOrComment(&comment))
90✔
690
        {
691
            if (args->list().empty())
2✔
692
                args->attachCommentAfter(comment);
2✔
693
            else
UNCOV
694
                args->list().back().attachCommentAfter(comment);
×
695
        }
2✔
696

697
        return args;
90✔
698
    }
150✔
699

700
    std::optional<Node> Parser::macro()
21,018✔
701
    {
21,018✔
702
        std::optional<Node> leaf { NodeType::Macro };
21,018✔
703
        setNodePosAndFilename(leaf.value());
21,018✔
704

705
        auto context = generateErrorContext("(");
21,018✔
706
        if (!accept(IsChar('(')))
21,018✔
707
            return std::nullopt;
11,553✔
708
        std::string comment;
9,465✔
709
        newlineOrComment(&comment);
9,465✔
710

711
        if (!oneOf({ "macro" }))
9,465✔
712
            return std::nullopt;
9,317✔
713
        newlineOrComment(&comment);
148✔
714
        leaf->attachNearestCommentBefore(comment);
148✔
715

716
        std::string symbol_name;
148✔
717
        if (!name(&symbol_name))
148✔
718
            errorWithNextToken("Expected a symbol to declare a macro");
1✔
719
        comment.clear();
147✔
720
        newlineOrComment(&comment);
147✔
721

722
        leaf->push_back(Node(NodeType::Symbol, symbol_name).attachNearestCommentBefore(comment));
147✔
723

724
        const auto position = getCount();
147✔
725
        if (const auto args = macroArgs(); args.has_value())
294✔
726
            leaf->push_back(args.value());
90✔
727
        else
728
        {
729
            backtrack(position);
54✔
730

731
            ++m_allow_macro_behavior;
54✔
732
            const auto value = nodeOrValue();
54✔
733
            --m_allow_macro_behavior;
53✔
734

735
            if (value.has_value())
53✔
736
                leaf->push_back(value.value());
52✔
737
            else
738
                errorWithNextToken(fmt::format("Expected an argument list, atom or node while defining macro `{}'", symbol_name));
1✔
739

740
            setNodePosAndFilename(leaf->list().back());
52✔
741
            if (accept(IsChar(')')))
52✔
742
                return leaf;
52✔
743
        }
54✔
744

745
        ++m_allow_macro_behavior;
90✔
746
        const auto value = nodeOrValue();
90✔
747
        --m_allow_macro_behavior;
89✔
748

749
        if (value.has_value())
89✔
750
            leaf->push_back(value.value());
87✔
751
        else if (leaf->list().size() == 2)
2✔
752
        {
753
            setNodePosAndFilename(leaf->list().back());
2✔
754
            comment.clear();
2✔
755
            if (newlineOrComment(&comment))
2✔
UNCOV
756
                leaf->list().back().attachCommentAfter(comment);
×
757

758
            expectSuffixOrError(')', fmt::format("to close macro `{}'", symbol_name), context);
2✔
759
            return leaf;
2✔
760
        }
761
        else
762
        {
UNCOV
763
            backtrack(position);
×
UNCOV
764
            errorWithNextToken(fmt::format("Expected a value while defining macro `{}'", symbol_name), context);
×
765
        }
766

767
        setNodePosAndFilename(leaf->list().back());
87✔
768
        comment.clear();
87✔
769
        if (newlineOrComment(&comment))
87✔
770
            leaf->list().back().attachCommentAfter(comment);
3✔
771

772
        expectSuffixOrError(')', fmt::format("to close macro `{}'", symbol_name), context);
87✔
773
        return leaf;
87✔
774
    }
21,024✔
775

776
    std::optional<Node> Parser::functionCall()
20,859✔
777
    {
20,859✔
778
        auto context = generateErrorContext("(");
20,859✔
779
        if (!accept(IsChar('(')))
20,859✔
780
            return std::nullopt;
11,553✔
781
        std::string comment;
9,306✔
782
        newlineOrComment(&comment);
9,306✔
783
        auto cursor = getCursor();
9,306✔
784

785
        std::optional<Node> func;
9,306✔
786
        if (auto sym_or_field = anyAtomOf({ NodeType::Symbol, NodeType::Field }); sym_or_field.has_value())
18,612✔
787
            func = sym_or_field->attachNearestCommentBefore(comment);
8,260✔
788
        else if (auto nested = node(); nested.has_value())
2,092✔
789
            func = nested->attachNearestCommentBefore(comment);
22✔
790
        else
UNCOV
791
            return std::nullopt;
×
792
        comment.clear();
8,282✔
793
        newlineOrComment(&comment);
8,282✔
794

795
        std::optional<Node> leaf { NodeType::List };
8,282✔
796
        setNodePosAndFilename(leaf.value(), cursor);
8,282✔
797
        setNodePosAndFilename(func.value(), cursor);
8,282✔
798
        leaf->push_back(func.value());
8,282✔
799

800
        while (!isEOF())
88,447✔
801
        {
802
            if (auto arg = nodeOrValue(); arg.has_value())
176,886✔
803
            {
804
                leaf->push_back(arg.value().attachNearestCommentBefore(comment));
80,165✔
805
                comment.clear();
80,165✔
806
                newlineOrComment(&comment);
80,165✔
807
            }
80,165✔
808
            else
809
                break;
8,276✔
810
        }
811

812
        leaf->list().back().attachCommentAfter(comment);
8,280✔
813

814
        comment.clear();
8,280✔
815
        if (newlineOrComment(&comment))
8,280✔
UNCOV
816
            leaf->list().back().attachCommentAfter(comment);
×
817

818
        expectSuffixOrError(')', fmt::format("in function call to `{}'", func.value().repr()), context);
8,280✔
819
        return leaf;
8,276✔
820
    }
30,165✔
821

822
    std::optional<Node> Parser::list()
11,553✔
823
    {
11,553✔
824
        std::optional<Node> leaf { NodeType::List };
11,553✔
825
        setNodePosAndFilename(leaf.value());
11,553✔
826

827
        auto context = generateErrorContext("[");
11,553✔
828
        if (!accept(IsChar('[')))
11,553✔
829
            return std::nullopt;
10,721✔
830
        leaf->push_back(Node(NodeType::Symbol, "list"));
832✔
831

832
        std::string comment;
832✔
833
        newlineOrComment(&comment);
832✔
834
        leaf->attachNearestCommentBefore(comment);
832✔
835

836
        comment.clear();
832✔
837
        while (!isEOF())
1,977✔
838
        {
839
            if (auto value = nodeOrValue(); value.has_value())
3,952✔
840
            {
841
                leaf->push_back(value.value().attachNearestCommentBefore(comment));
1,145✔
842
                comment.clear();
1,145✔
843
                newlineOrComment(&comment);
1,145✔
844
            }
1,145✔
845
            else
846
                break;
831✔
847
        }
848
        leaf->list().back().attachCommentAfter(comment);
832✔
849

850
        expectSuffixOrError(']', "to end list definition", context);
832✔
851
        return leaf;
831✔
852
    }
11,554✔
853

854
    std::optional<Node> Parser::atom()
114,702✔
855
    {
114,702✔
856
        const auto pos = getCount();
114,702✔
857

858
        if (auto res = Parser::number(); res.has_value())
114,702✔
859
            return res;
69,515✔
860
        backtrack(pos);
45,184✔
861

862
        if (auto res = Parser::string(); res.has_value())
45,184✔
863
            return res;
1,193✔
864
        backtrack(pos);
43,991✔
865

866
        if (auto res = Parser::spread(); m_allow_macro_behavior > 0 && res.has_value())
43,991✔
867
            return res;
28✔
868
        backtrack(pos);
43,963✔
869

870
        if (auto res = Parser::field(); res.has_value())
43,963✔
871
            return res;
718✔
872
        backtrack(pos);
43,245✔
873

874
        if (auto res = Parser::symbol(); res.has_value())
43,245✔
875
            return res;
16,179✔
876
        backtrack(pos);
27,066✔
877

878
        if (auto res = Parser::nil(); res.has_value())
27,066✔
879
            return res;
72✔
880
        backtrack(pos);
26,994✔
881

882
        return std::nullopt;
26,994✔
883
    }
114,702✔
884

885
    std::optional<Node> Parser::anyAtomOf(const std::initializer_list<NodeType> types)
9,306✔
886
    {
9,306✔
887
        auto cursor = getCursor();
9,306✔
888
        if (auto value = atom(); value.has_value())
17,566✔
889
        {
890
            setNodePosAndFilename(value.value(), cursor);
8,260✔
891
            for (const auto type : types)
16,598✔
892
            {
893
                if (value->nodeType() == type)
8,338✔
894
                    return value;
8,260✔
895
            }
8,338✔
UNCOV
896
        }
×
897
        return std::nullopt;
1,046✔
898
    }
9,306✔
899

900
    std::optional<Node> Parser::nodeOrValue()
105,393✔
901
    {
105,393✔
902
        auto cursor = getCursor();
105,393✔
903
        if (auto value = atom(); value.has_value())
184,838✔
904
        {
905
            setNodePosAndFilename(value.value(), cursor);
79,445✔
906
            return value;
79,445✔
907
        }
908
        if (auto sub_node = node(); sub_node.has_value())
41,173✔
909
        {
910
            setNodePosAndFilename(sub_node.value(), cursor);
15,228✔
911
            return sub_node;
15,228✔
912
        }
913

914
        return std::nullopt;
10,717✔
915
    }
105,393✔
916

917
    std::optional<Node> Parser::wrapped(std::optional<Node> (Parser::*parser)(), const std::string& name)
143,118✔
918
    {
143,118✔
919
        auto cursor = getCursor();
143,118✔
920
        auto context = generateErrorContext("(");
143,118✔
921
        if (!prefix('('))
143,118✔
922
            return std::nullopt;
74,830✔
923
        std::string comment;
68,288✔
924
        newlineOrComment(&comment);
68,288✔
925

926
        if (auto result = (this->*parser)(); result.has_value())
74,779✔
927
        {
928
            result->attachNearestCommentBefore(result->comment() + comment);
6,491✔
929
            setNodePosAndFilename(result.value(), cursor);
6,491✔
930

931
            comment.clear();
6,491✔
932
            if (newlineOrComment(&comment))
6,491✔
933
                result.value().attachCommentAfter(comment);
9✔
934

935
            if (result->isListLike())
6,491✔
936
                setNodePosAndFilename(result->list().back());
6,491✔
937
            expectSuffixOrError(')', "after " + name, context);
6,491✔
938

939
            comment.clear();
6,490✔
940
            if (spaceComment(&comment))
6,490✔
941
                result.value().attachCommentAfter(comment);
15✔
942

943
            return result;
6,490✔
944
        }
945

946
        return std::nullopt;
61,781✔
947
    }
143,119✔
948
}
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