• 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

96.88
/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) :
928✔
8
        BaseParser(), m_interpret(interpret), m_logger("Parser", debug),
464✔
9
        m_ast(NodeType::List), m_imports({}), m_allow_macro_behavior(0),
464✔
10
        m_nested_nodes(0)
464✔
11
    {
464✔
12
        m_ast.push_back(Node(Keyword::Begin));
464✔
13

14
        m_parsers = {
928✔
15
            [this]() {
32,003✔
16
                return wrapped(&Parser::letMutSet, "variable assignment or declaration");
31,539✔
17
            },
5✔
18
            [this]() {
27,722✔
19
                return wrapped(&Parser::function, "function");
27,258✔
20
            },
4✔
21
            [this]() {
26,253✔
22
                return wrapped(&Parser::condition, "condition");
25,789✔
23
            },
3✔
24
            [this]() {
25,507✔
25
                return wrapped(&Parser::loop, "loop");
25,043✔
26
            },
2✔
27
            [this]() {
24,907✔
28
                return import_();
24,443✔
29
            },
30
            [this]() {
24,740✔
31
                return block();
24,276✔
32
            },
33
            [this]() {
23,226✔
34
                return wrapped(&Parser::macroCondition, "$if");
22,762✔
35
            },
2✔
36
            [this]() {
23,175✔
37
                return macro();
22,711✔
38
            },
39
            [this]() {
23,026✔
40
                return wrapped(&Parser::del, "del");
22,562✔
41
            },
1✔
42
            [this]() {
23,015✔
43
                return functionCall();
22,551✔
44
            },
45
            [this]() {
12,993✔
46
                return list();
12,529✔
47
            }
48
        };
49
    }
464✔
50

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

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

67
            const auto pos = getCount();
2,188✔
68
            if (auto n = node())
4,376✔
69
            {
70
                m_ast.push_back(n->attachNearestCommentBefore(n->comment() + comment));
2,145✔
71
                comment.clear();
2,145✔
72
                if (spaceComment(&comment))
2,145✔
73
                    m_ast.list().back().attachCommentAfter(comment);
21✔
74
            }
2,145✔
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,570✔
91

92
        m_logger.traceEnd();
421✔
93
    }
464✔
94

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

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

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

110
        const auto [row, col] = cursor.value_or(getCursor());
278,676✔
111
        node.setPos(row, col);
278,676✔
112
        node.setFilename(m_filename);
278,676✔
113
        return node;
278,676✔
114
    }
323,888✔
115

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

120
        if (m_nested_nodes > MaxNestedNodes)
31,540✔
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,066✔
122

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

127
        for (auto&& parser : m_parsers)
293,002✔
128
        {
129
            result = parser();
261,463✔
130

131
            if (result)
260,398✔
132
                break;
18,822✔
133
            backtrack(position);
241,576✔
134
        }
261,463✔
135

136
        // return std::nullopt only on parsing error, nothing matched, the user provided terrible code
137
        --m_nested_nodes;
30,474✔
138
        return result;
30,474✔
139
    }
31,540✔
140

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

146
        std::string token;
17,504✔
147
        if (!oneOf({ "let", "mut", "set" }, &token))
17,504✔
148
            return std::nullopt;
13,223✔
149
        std::string comment;
4,281✔
150
        newlineOrComment(&comment);
4,281✔
151
        leaf->attachNearestCommentBefore(comment);
4,281✔
152

153
        if (token == "let")
4,281✔
154
            leaf->push_back(Node(Keyword::Let));
1,833✔
155
        else if (token == "mut")
2,448✔
156
            leaf->push_back(Node(Keyword::Mut));
1,373✔
157
        else  // "set"
158
            leaf->push_back(Node(Keyword::Set));
1,075✔
159

160
        if (m_allow_macro_behavior > 0)
4,281✔
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
172
                backtrack(position);
×
173
        }
22✔
174

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

182
            leaf->push_back(Node(NodeType::Symbol, symbol_name));
4,257✔
183
        }
4,259✔
184

185
        comment.clear();
4,278✔
186
        newlineOrComment(&comment);
4,278✔
187

188
        if (auto value = nodeOrValue(); value.has_value())
8,556✔
189
            leaf->push_back(value.value().attachNearestCommentBefore(comment));
4,276✔
190
        else
191
            errorWithNextToken("Expected a value");
×
192

193
        return leaf;
4,276✔
194
    }
17,509✔
195

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

201
        if (!oneOf({ "del" }))
10,033✔
202
            return std::nullopt;
10,022✔
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
    }
10,034✔
218

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

224
        if (!oneOf({ "if" }))
11,754✔
225
            return std::nullopt;
11,008✔
226

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

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

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

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

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

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

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

258
        setNodePosAndFilename(leaf->list().back());
744✔
259
        return leaf;
744✔
260
    }
11,756✔
261

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

267
        if (!oneOf({ "while" }))
11,008✔
268
            return std::nullopt;
10,408✔
269

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

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

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

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

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

288
        setNodePosAndFilename(leaf->list().back());
598✔
289
        return leaf;
598✔
290
    }
11,010✔
291

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

297
        const auto [row, col] = getCursor();
24,610✔
298
        auto context = generateErrorContext("(");
24,443✔
299
        if (!accept(IsChar('(')))
24,443✔
300
            return std::nullopt;
14,035✔
301

302
        std::string comment;
10,408✔
303
        newlineOrComment(&comment);
10,408✔
304
        leaf->attachNearestCommentBefore(comment);
10,408✔
305

306
        if (!oneOf({ "import" }))
10,408✔
307
            return std::nullopt;
10,241✔
308
        comment.clear();
167✔
309
        newlineOrComment(&comment);
167✔
310
        leaf->push_back(Node(Keyword::Import));
167✔
311

312
        Import import_data;
167✔
313
        import_data.col = col;
167✔
314
        import_data.line = row;
167✔
315

316
        const auto pos = getCount();
167✔
317
        if (!packageName(&import_data.prefix))
167✔
318
            errorWithNextToken("Import expected a package name");
1✔
319

320
        if (import_data.prefix.size() > 255)
166✔
321
        {
322
            backtrack(pos);
1✔
323
            errorWithNextToken(fmt::format("Import name too long, expected at most 255 characters, got {}", import_data.prefix.size()));
1✔
UNCOV
324
        }
×
325
        import_data.package.push_back(import_data.prefix);
165✔
326

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

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

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

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

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

368
                return leaf;
10✔
369
            }
370
            else
371
                break;
152✔
372
        }
373

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

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

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

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

406
                if (!space())
105✔
407
                    break;
67✔
408
                comment.clear();
38✔
409
                newlineOrComment(&comment);
38✔
410
            }
411

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

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

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

425
        expectSuffixOrError(')', fmt::format("in import `{}'", import_data.toPackageString()), context);
149✔
426
        return leaf;
149✔
427
    }
24,451✔
428

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

434
        auto context = generateErrorContext("(");
24,276✔
435
        bool alt_syntax = false;
24,276✔
436
        std::string comment;
24,276✔
437
        if (accept(IsChar('(')))
24,276✔
438
        {
439
            newlineOrComment(&comment);
10,241✔
440
            if (!oneOf({ "begin" }))
10,241✔
441
                return std::nullopt;
10,233✔
442
        }
8✔
443
        else if (accept(IsChar('{')))
14,035✔
444
            alt_syntax = true;
1,506✔
445
        else
446
            return std::nullopt;
12,529✔
447

448
        leaf->setAltSyntax(alt_syntax);
1,514✔
449
        leaf->push_back(Node(Keyword::Begin).attachNearestCommentBefore(comment));
1,514✔
450

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

454
        while (!isEOF())
6,969✔
455
        {
456
            if (auto value = nodeOrValue(); value.has_value())
13,936✔
457
            {
458
                leaf->push_back(value.value().attachNearestCommentBefore(comment));
5,455✔
459
                comment.clear();
5,455✔
460
                newlineOrComment(&comment);
5,455✔
461
            }
5,455✔
462
            else
463
                break;
1,513✔
464
        }
465

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

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

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

483
        bool has_captures = false;
1,448✔
484

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

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

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

519
        if (accept(IsChar(')')))
1,447✔
520
            return args;
1,445✔
521
        return std::nullopt;
2✔
522
    }
1,449✔
523

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

529
        if (!oneOf({ "fun" }))
13,223✔
530
            return std::nullopt;
11,754✔
531
        leaf->push_back(Node(Keyword::Fun));
1,469✔
532

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

536
        while (m_allow_macro_behavior > 0)
1,469✔
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);
×
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
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,448✔
570
        if (auto args = functionArgs(); args.has_value())
2,896✔
571
            leaf->push_back(args.value().attachNearestCommentBefore(comment_before_args));
1,445✔
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
579
                errorWithNextToken("Expected an argument list");
×
580
        }
581

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

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

590
        setNodePosAndFilename(leaf->list().back());
1,444✔
591
        return leaf;
1,444✔
592
    }
13,227✔
593

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

599
        if (!oneOf({ "$if" }))
10,233✔
600
            return std::nullopt;
10,182✔
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
    }
10,235✔
634

635
    std::optional<Node> Parser::macroArgs()
148✔
636
    {
148✔
637
        if (!accept(IsChar('(')))
151✔
638
            return std::nullopt;
23✔
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✔
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✔
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
694
                args->list().back().attachCommentAfter(comment);
×
695
        }
2✔
696

697
        return args;
90✔
698
    }
151✔
699

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

705
        auto context = generateErrorContext("(");
22,711✔
706
        if (!accept(IsChar('(')))
22,711✔
707
            return std::nullopt;
12,529✔
708
        std::string comment;
10,182✔
709
        newlineOrComment(&comment);
10,182✔
710

711
        if (!oneOf({ "macro" }))
10,182✔
712
            return std::nullopt;
10,033✔
713
        newlineOrComment(&comment);
149✔
714
        leaf->attachNearestCommentBefore(comment);
149✔
715

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

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

724
        const auto position = getCount();
148✔
725
        if (const auto args = macroArgs(); args.has_value())
296✔
726
            leaf->push_back(args.value());
90✔
727
        else
728
        {
729
            // if we couldn't parse arguments, then we have a value
730
            backtrack(position);
55✔
731

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

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

741
            setNodePosAndFilename(leaf->list().back());
53✔
742
            comment.clear();
53✔
743
            if (newlineOrComment(&comment))
53✔
744
                leaf->list().back().attachCommentAfter(comment);
×
745
            expectSuffixOrError(')', fmt::format("to close macro `{}'", symbol_name), context);
53✔
746
            return leaf;
52✔
747
        }
55✔
748

749
        ++m_allow_macro_behavior;
90✔
750
        const auto value = nodeOrValue();
90✔
751
        --m_allow_macro_behavior;
89✔
752

753
        if (value.has_value())
89✔
754
            leaf->push_back(value.value());
87✔
755
        else if (leaf->list().size() == 2)  // the argument list is actually a function call and it's okay
2✔
756
        {
757
            setNodePosAndFilename(leaf->list().back());
2✔
758
            comment.clear();
2✔
759
            if (newlineOrComment(&comment))
2✔
760
                leaf->list().back().attachCommentAfter(comment);
×
761

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

771
        setNodePosAndFilename(leaf->list().back());
87✔
772
        comment.clear();
87✔
773
        if (newlineOrComment(&comment))
87✔
774
            leaf->list().back().attachCommentAfter(comment);
3✔
775

776
        expectSuffixOrError(')', fmt::format("to close macro `{}'", symbol_name), context);
87✔
777
        return leaf;
87✔
778
    }
22,718✔
779

780
    std::optional<Node> Parser::functionCall()
22,551✔
781
    {
22,551✔
782
        auto context = generateErrorContext("(");
22,551✔
783
        if (!accept(IsChar('(')))
22,551✔
784
            return std::nullopt;
12,529✔
785
        std::string comment;
10,022✔
786
        newlineOrComment(&comment);
10,022✔
787
        auto cursor = getCursor();
10,022✔
788

789
        std::optional<Node> func;
10,022✔
790
        if (auto sym_or_field = anyAtomOf({ NodeType::Symbol, NodeType::Field }); sym_or_field.has_value())
20,044✔
791
            func = sym_or_field->attachNearestCommentBefore(comment);
8,976✔
792
        else if (auto nested = node(); nested.has_value())
2,092✔
793
            func = nested->attachNearestCommentBefore(comment);
22✔
794
        else
795
            return std::nullopt;
×
796
        comment.clear();
8,998✔
797
        newlineOrComment(&comment);
8,998✔
798

799
        std::optional<Node> leaf { NodeType::List };
8,998✔
800
        setNodePosAndFilename(leaf.value(), cursor);
8,998✔
801
        setNodePosAndFilename(func.value(), cursor);
8,998✔
802
        leaf->push_back(func.value());
8,998✔
803

804
        while (!isEOF())
90,428✔
805
        {
806
            if (auto arg = nodeOrValue(); arg.has_value())
180,848✔
807
            {
808
                leaf->push_back(arg.value().attachNearestCommentBefore(comment));
81,430✔
809
                comment.clear();
81,430✔
810
                newlineOrComment(&comment);
81,430✔
811
            }
81,430✔
812
            else
813
                break;
8,992✔
814
        }
815

816
        leaf->list().back().attachCommentAfter(comment);
8,996✔
817

818
        comment.clear();
8,996✔
819
        if (newlineOrComment(&comment))
8,996✔
820
            leaf->list().back().attachCommentAfter(comment);
×
821

822
        expectSuffixOrError(')', fmt::format("in function call to `{}'", func.value().repr()), context);
8,996✔
823
        return leaf;
8,992✔
824
    }
32,573✔
825

826
    std::optional<Node> Parser::list()
12,529✔
827
    {
12,529✔
828
        std::optional<Node> leaf { NodeType::List };
12,529✔
829
        setNodePosAndFilename(leaf.value());
12,529✔
830

831
        auto context = generateErrorContext("[");
12,529✔
832
        if (!accept(IsChar('[')))
12,529✔
833
            return std::nullopt;
11,652✔
834
        leaf->setAltSyntax(true);
877✔
835
        leaf->push_back(Node(NodeType::Symbol, "list"));
877✔
836

837
        std::string comment;
877✔
838
        newlineOrComment(&comment);
877✔
839
        leaf->attachNearestCommentBefore(comment);
877✔
840

841
        comment.clear();
877✔
842
        while (!isEOF())
2,069✔
843
        {
844
            if (auto value = nodeOrValue(); value.has_value())
4,136✔
845
            {
846
                leaf->push_back(value.value().attachNearestCommentBefore(comment));
1,192✔
847
                comment.clear();
1,192✔
848
                newlineOrComment(&comment);
1,192✔
849
            }
1,192✔
850
            else
851
                break;
876✔
852
        }
853
        leaf->list().back().attachCommentAfter(comment);
877✔
854

855
        expectSuffixOrError(']', "to end list definition", context);
877✔
856
        return leaf;
876✔
857
    }
12,530✔
858

859
    std::optional<Node> Parser::atom()
119,000✔
860
    {
119,000✔
861
        const auto pos = getCount();
119,000✔
862

863
        if (auto res = Parser::number(); res.has_value())
119,000✔
864
            return res;
69,852✔
865
        backtrack(pos);
49,145✔
866

867
        if (auto res = Parser::string(); res.has_value())
49,145✔
868
            return res;
1,236✔
869
        backtrack(pos);
47,909✔
870

871
        if (auto res = Parser::spread(); m_allow_macro_behavior > 0 && res.has_value())
47,909✔
872
            return res;
28✔
873
        backtrack(pos);
47,881✔
874

875
        if (auto res = Parser::field(); res.has_value())
47,881✔
876
            return res;
756✔
877
        backtrack(pos);
47,125✔
878

879
        if (auto res = Parser::symbol(); res.has_value())
47,125✔
880
            return res;
17,700✔
881
        backtrack(pos);
29,425✔
882

883
        if (auto res = Parser::nil(); res.has_value())
29,425✔
884
            return res;
73✔
885
        backtrack(pos);
29,352✔
886

887
        return std::nullopt;
29,352✔
888
    }
119,000✔
889

890
    std::optional<Node> Parser::anyAtomOf(const std::initializer_list<NodeType> types)
10,022✔
891
    {
10,022✔
892
        auto cursor = getCursor();
10,022✔
893
        if (auto value = atom(); value.has_value())
18,998✔
894
        {
895
            setNodePosAndFilename(value.value(), cursor);
8,976✔
896
            for (const auto type : types)
18,038✔
897
            {
898
                if (value->nodeType() == type)
9,062✔
899
                    return value;
8,976✔
900
            }
9,062✔
UNCOV
901
        }
×
902
        return std::nullopt;
1,046✔
903
    }
10,022✔
904

905
    std::optional<Node> Parser::nodeOrValue()
108,975✔
906
    {
108,975✔
907
        auto cursor = getCursor();
108,975✔
908
        if (auto value = atom(); value.has_value())
189,644✔
909
        {
910
            setNodePosAndFilename(value.value(), cursor);
80,669✔
911
            return value;
80,669✔
912
        }
913
        if (auto sub_node = node(); sub_node.has_value())
44,958✔
914
        {
915
            setNodePosAndFilename(sub_node.value(), cursor);
16,655✔
916
            return sub_node;
16,655✔
917
        }
918

919
        return std::nullopt;
11,648✔
920
    }
108,975✔
921

922
    std::optional<Node> Parser::wrapped(std::optional<Node> (Parser::*parser)(), const std::string& name)
154,953✔
923
    {
154,953✔
924
        auto cursor = getCursor();
154,953✔
925
        auto context = generateErrorContext("(");
154,953✔
926
        if (!prefix('('))
154,953✔
927
            return std::nullopt;
81,198✔
928
        std::string comment;
73,755✔
929
        newlineOrComment(&comment);
73,755✔
930

931
        if (auto result = (this->*parser)(); result.has_value())
80,897✔
932
        {
933
            result->attachNearestCommentBefore(result->comment() + comment);
7,142✔
934
            setNodePosAndFilename(result.value(), cursor);
7,142✔
935

936
            comment.clear();
7,142✔
937
            if (newlineOrComment(&comment))
7,142✔
938
                result.value().attachCommentAfter(comment);
9✔
939

940
            if (result->isListLike())
7,142✔
941
                setNodePosAndFilename(result->list().back());
7,142✔
942
            expectSuffixOrError(')', "after " + name, context);
7,142✔
943

944
            comment.clear();
7,141✔
945
            if (spaceComment(&comment))
7,141✔
946
                result.value().attachCommentAfter(comment);
15✔
947

948
            return result;
7,141✔
949
        }
950

951
        return std::nullopt;
66,597✔
952
    }
154,954✔
953
}
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