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

ArkScript-lang / Ark / 12206460859

06 Dec 2024 09:32PM UTC coverage: 77.864% (+0.05%) from 77.815%
12206460859

push

github

SuperFola
refactor(name resolution): removing dead code and refactoring code when assigning names to symbols in namespaces

7 of 7 new or added lines in 2 files covered. (100.0%)

24 existing lines in 5 files now uncovered.

5438 of 6984 relevant lines covered (77.86%)

9338.42 hits per line

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

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

14
    void Parser::process(const std::string& filename, const std::string& code)
214✔
15
    {
214✔
16
        m_logger.traceStart("process");
253✔
17
        initParser(filename, code);
214✔
18

19
        while (!isEOF())
834✔
20
        {
21
            std::string comment;
796✔
22
            newlineOrComment(&comment);
796✔
23
            if (isEOF())
796✔
24
            {
25
                if (!comment.empty())
137✔
26
                    m_ast.list().back().attachCommentAfter(comment);
1✔
27
                break;
137✔
28
            }
29

30
            const auto pos = getCount();
659✔
31
            if (auto n = node())
1,318✔
32
            {
×
33
                m_ast.push_back(n->attachNearestCommentBefore(n->comment() + comment));
620✔
34
                comment.clear();
620✔
35
                if (spaceComment(&comment))
620✔
36
                    m_ast.list().back().attachCommentAfter(comment);
16✔
37
            }
620✔
38
            else
39
            {
40
                backtrack(pos);
2✔
41
                errorWithNextToken("invalid syntax, expected node");
2✔
42
            }
×
43
        }
796✔
44

45
        m_logger.traceEnd();
175✔
46
    }
214✔
47

48
    const Node& Parser::ast() const noexcept
190✔
49
    {
190✔
50
        return m_ast;
190✔
51
    }
52

×
53
    const std::vector<Import>& Parser::imports() const
124✔
54
    {
124✔
55
        return m_imports;
124✔
56
    }
57

58
    Node& Parser::setNodePosAndFilename(Node& node, const std::optional<FilePosition>& cursor) const
39,127✔
59
    {
39,127✔
60
        if (node.line() != 0 || node.col() != 0)
39,127✔
61
            return node;
6,178✔
62

×
63
        const auto [row, col] = cursor.value_or(getCursor());
32,949✔
64
        node.setPos(row, col);
32,949✔
65
        node.setFilename(m_filename);
32,949✔
66
        return node;
32,949✔
67
    }
39,127✔
68

69
    std::optional<Node> Parser::node()
4,638✔
70
    {
4,638✔
71
        // save current position in buffer to be able to go back if needed
72
        const auto position = getCount();
4,638✔
73

74
        if (auto result = wrapped(&Parser::letMutSet, "variable assignment or declaration"))
4,638✔
75
            return result;
477✔
76
        backtrack(position);
4,156✔
77

78
        if (auto result = wrapped(&Parser::function, "function"))
4,156✔
79
            return result;
195✔
80
        backtrack(position);
3,957✔
81

82
        if (auto result = wrapped(&Parser::condition, "condition"))
3,957✔
83
            return result;
90✔
84
        backtrack(position);
3,864✔
85

86
        if (auto result = wrapped(&Parser::loop, "loop"))
3,864✔
87
            return result;
47✔
88
        backtrack(position);
3,807✔
89

90
        if (auto result = import_(); result.has_value())
3,807✔
91
            return result;
59✔
92
        backtrack(position);
3,747✔
93

94
        if (auto result = block(); result.has_value())
3,747✔
95
            return result;
193✔
96
        backtrack(position);
3,554✔
97

98
        if (auto result = wrapped(&Parser::macroCondition, "$if"))
3,554✔
99
            return result;
31✔
100
        backtrack(position);
3,513✔
101

102
        if (auto result = macro(); result.has_value())
3,513✔
103
            return result;
100✔
104
        backtrack(position);
3,413✔
105

106
        if (auto result = wrapped(&Parser::del, "del"))
3,413✔
107
            return result;
12✔
108
        backtrack(position);
3,395✔
109

110
        if (auto result = functionCall(); result.has_value())
3,395✔
111
            return result;
1,443✔
112
        backtrack(position);
1,951✔
113

114
        if (auto result = list(); result.has_value())
1,951✔
115
            return result;
135✔
116
        backtrack(position);
1,816✔
117

118
        return std::nullopt;  // will never reach
1,816✔
119
    }
4,655✔
120

121
    std::optional<Node> Parser::letMutSet()
2,499✔
122
    {
2,499✔
123
        std::optional<Node> leaf { NodeType::List };
2,499✔
124
        setNodePosAndFilename(leaf.value());
2,499✔
125

126
        std::string token;
2,499✔
127
        if (!oneOf({ "let", "mut", "set" }, &token))
2,499✔
128
            return std::nullopt;
2,017✔
129
        std::string comment;
482✔
130
        newlineOrComment(&comment);
482✔
131
        leaf->attachNearestCommentBefore(comment);
482✔
132

133
        if (token == "let")
482✔
134
            leaf->push_back(Node(Keyword::Let));
313✔
135
        else if (token == "mut")
169✔
136
            leaf->push_back(Node(Keyword::Mut));
86✔
137
        else  // "set"
138
            leaf->push_back(Node(Keyword::Set));
83✔
139

140
        if (m_allow_macro_behavior > 0)
482✔
141
        {
142
            const auto position = getCount();
15✔
143
            if (const auto value = nodeOrValue(); value.has_value())
30✔
144
            {
145
                const auto sym = value.value();
15✔
146
                if (sym.nodeType() == NodeType::List || sym.nodeType() == NodeType::Symbol || sym.nodeType() == NodeType::Macro || sym.nodeType() == NodeType::Spread)
15✔
147
                    leaf->push_back(sym);
14✔
148
                else
149
                    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✔
150
            }
15✔
151
            else
152
                backtrack(position);
×
153
        }
15✔
154

155
        if (leaf->constList().size() == 1)
481✔
156
        {
157
            // we haven't parsed anything while in "macro state"
×
158
            std::string symbol;
467✔
159
            if (!name(&symbol))
468✔
160
                errorWithNextToken(token + " needs a symbol");
2✔
161

162
            leaf->push_back(Node(NodeType::Symbol, symbol));
465✔
163
        }
467✔
164

165
        comment.clear();
479✔
166
        newlineOrComment(&comment);
479✔
167

168
        if (auto value = nodeOrValue(); value.has_value())
958✔
169
            leaf->push_back(value.value().attachNearestCommentBefore(comment));
477✔
170
        else
171
            errorWithNextToken("Expected a value");
×
172

173
        return leaf;
477✔
174
    }
2,504✔
175

176
    std::optional<Node> Parser::del()
1,461✔
177
    {
1,461✔
178
        std::optional<Node> leaf { NodeType::List };
1,461✔
179
        setNodePosAndFilename(leaf.value());
1,461✔
180

181
        if (!oneOf({ "del" }))
1,461✔
182
            return std::nullopt;
1,448✔
183
        leaf->push_back(Node(Keyword::Del));
13✔
184

185
        std::string comment;
13✔
186
        newlineOrComment(&comment);
13✔
187

188
        std::string symbol;
13✔
189
        if (!name(&symbol))
13✔
190
            errorWithNextToken("del needs a symbol");
1✔
191

192
        leaf->push_back(Node(NodeType::Symbol, symbol));
12✔
193
        leaf->list().back().attachNearestCommentBefore(comment);
12✔
194
        setNodePosAndFilename(leaf->list().back());
12✔
195

196
        return leaf;
12✔
197
    }
1,462✔
198

199
    std::optional<Node> Parser::condition()
1,818✔
200
    {
1,818✔
201
        std::optional<Node> leaf { NodeType::List };
1,818✔
202
        setNodePosAndFilename(leaf.value());
1,818✔
203

204
        if (!oneOf({ "if" }))
1,818✔
205
            return std::nullopt;
1,725✔
206

207
        std::string comment;
93✔
208
        newlineOrComment(&comment);
93✔
209

210
        leaf->push_back(Node(Keyword::If));
93✔
211

212
        if (auto condition = nodeOrValue(); condition.has_value())
186✔
213
            leaf->push_back(condition.value().attachNearestCommentBefore(comment));
92✔
214
        else
215
            errorWithNextToken("`if' needs a valid condition");
1✔
216

217
        comment.clear();
92✔
218
        newlineOrComment(&comment);
92✔
219

220
        if (auto value_if_true = nodeOrValue(); value_if_true.has_value())
184✔
221
            leaf->push_back(value_if_true.value().attachNearestCommentBefore(comment));
91✔
222
        else
223
            errorWithNextToken("Expected a node or value after condition");
1✔
224

225
        comment.clear();
91✔
226
        newlineOrComment(&comment);
91✔
227

228
        if (auto value_if_false = nodeOrValue(); value_if_false.has_value())
182✔
229
        {
230
            leaf->push_back(value_if_false.value().attachNearestCommentBefore(comment));
65✔
231
            comment.clear();
65✔
232
            if (newlineOrComment(&comment))
65✔
233
                leaf->list().back().attachCommentAfter(comment);
2✔
234
        }
65✔
235
        else if (!comment.empty())
26✔
236
            leaf->attachCommentAfter(comment);
2✔
237

238
        setNodePosAndFilename(leaf->list().back());
91✔
239
        return leaf;
91✔
240
    }
1,820✔
241

242
    std::optional<Node> Parser::loop()
1,725✔
243
    {
1,725✔
244
        std::optional<Node> leaf { NodeType::List };
1,725✔
245
        setNodePosAndFilename(leaf.value());
1,725✔
246

247
        if (!oneOf({ "while" }))
1,725✔
248
            return std::nullopt;
1,676✔
249

250
        std::string comment;
49✔
251
        newlineOrComment(&comment);
49✔
252

253
        leaf->push_back(Node(Keyword::While));
49✔
254

255
        if (auto condition = nodeOrValue(); condition.has_value())
98✔
256
            leaf->push_back(condition.value().attachNearestCommentBefore(comment));
48✔
257
        else
258
            errorWithNextToken("`while' needs a valid condition");
1✔
259

260
        comment.clear();
48✔
261
        newlineOrComment(&comment);
48✔
262

263
        if (auto body = nodeOrValue(); body.has_value())
96✔
264
            leaf->push_back(body.value().attachNearestCommentBefore(comment));
47✔
265
        else
266
            errorWithNextToken("Expected a node or value after loop condition");
1✔
267

268
        setNodePosAndFilename(leaf->list().back());
47✔
269
        return leaf;
47✔
270
    }
1,727✔
271

272
    std::optional<Node> Parser::import_()
3,815✔
273
    {
3,815✔
274
        std::optional<Node> leaf { NodeType::List };
3,815✔
275
        setNodePosAndFilename(leaf.value());
3,815✔
276

277
        if (!accept(IsChar('(')))
3,815✔
278
            return std::nullopt;
2,139✔
279
        std::string comment;
1,676✔
280
        newlineOrComment(&comment);
1,676✔
281
        leaf->attachNearestCommentBefore(comment);
1,676✔
282

283
        if (!oneOf({ "import" }))
1,676✔
284
            return std::nullopt;
1,609✔
285
        comment.clear();
67✔
286
        newlineOrComment(&comment);
67✔
287
        leaf->push_back(Node(Keyword::Import));
67✔
288

289
        Import import_data;
67✔
290

291
        const auto pos = getCount();
67✔
292
        if (!packageName(&import_data.prefix))
67✔
293
            errorWithNextToken("Import expected a package name");
1✔
294

295
        if (import_data.prefix.size() > 255)
66✔
296
        {
297
            backtrack(pos);
1✔
298
            errorWithNextToken(fmt::format("Import name too long, expected at most 255 characters, got {}", import_data.prefix.size()));
1✔
299
        }
×
300
        import_data.package.push_back(import_data.prefix);
65✔
301

302
        const auto [row, col] = getCursor();
65✔
303
        import_data.col = col;
65✔
304
        import_data.line = row;
65✔
305

306
        Node packageNode(NodeType::List);
65✔
307
        setNodePosAndFilename(packageNode.attachNearestCommentBefore(comment));
65✔
308
        packageNode.push_back(Node(NodeType::Symbol, import_data.prefix));
65✔
309

310
        // first, parse the package name
311
        while (!isEOF())
108✔
312
        {
313
            // parsing package folder.foo.bar.yes
314
            if (accept(IsChar('.')))
108✔
315
            {
316
                std::string path;
46✔
317
                if (!packageName(&path))
46✔
318
                    errorWithNextToken("Package name expected after '.'");
2✔
319
                else
320
                {
321
                    packageNode.push_back(Node(NodeType::Symbol, path));
44✔
322
                    setNodePosAndFilename(packageNode.list().back());
44✔
323
                    import_data.package.push_back(path);
44✔
324
                    import_data.prefix = path;  // in the end we will store the last element of the package, which is what we want
44✔
325

326
                    if (path.size() > 255)
44✔
327
                    {
328
                        backtrack(pos);
1✔
329
                        errorWithNextToken(fmt::format("Import name too long, expected at most 255 characters, got {}", path.size()));
1✔
330
                    }
×
331
                }
332
            }
46✔
333
            else if (accept(IsChar(':')) && accept(IsChar('*')))  // parsing :*
62✔
334
            {
335
                leaf->push_back(packageNode);
7✔
336
                leaf->push_back(Node(NodeType::Symbol, "*"));
7✔
337
                setNodePosAndFilename(leaf->list().back());
7✔
338

339
                space();
7✔
340
                expect(IsChar(')'));
7✔
341

342
                // save the import data structure to know we encounter an import node, and retrieve its data more easily later on
343
                import_data.with_prefix = false;
7✔
344
                import_data.is_glob = true;
7✔
345
                m_imports.push_back(import_data);
7✔
346

347
                return leaf;
7✔
348
            }
349
            else
350
                break;
55✔
351
        }
352

353
        Node symbols(NodeType::List);
55✔
354
        setNodePosAndFilename(symbols);
55✔
355
        // then parse the symbols to import, if any
356
        if (space())  // FIXME: potential regression introduced here
55✔
357
        {
358
            comment.clear();
20✔
359
            newlineOrComment(&comment);
20✔
360

361
            while (!isEOF())
29✔
362
            {
363
                if (accept(IsChar(':')))  // parsing potential :a :b :c
29✔
364
                {
365
                    std::string symbol;
26✔
366
                    if (!name(&symbol))
26✔
367
                        errorWithNextToken("Expected a valid symbol to import");
1✔
368
                    if (symbol == "*")
25✔
369
                        error(fmt::format("Glob patterns can not be separated from the package, use (import {}:*) instead", import_data.toPackageString()), symbol);
1✔
370

371
                    if (symbol.size() >= 2 && symbol[symbol.size() - 2] == ':' && symbol.back() == '*')
24✔
372
                    {
373
                        backtrack(getCount() - 2);  // we can backtrack n-2 safely here because we know the previous chars were ":*"
1✔
374
                        error("Glob pattern can not follow a symbol to import", ":*");
1✔
375
                    }
×
376

377
                    symbols.push_back(Node(NodeType::Symbol, symbol).attachNearestCommentBefore(comment));
23✔
378
                    comment.clear();
23✔
379
                    setNodePosAndFilename(symbols.list().back());
23✔
380
                    import_data.symbols.push_back(symbol);
23✔
381
                    // we do not need the prefix when importing specific symbols
382
                    import_data.with_prefix = false;
23✔
383
                }
26✔
384

385
                if (!space())
26✔
386
                    break;
17✔
387
                comment.clear();
9✔
388
                newlineOrComment(&comment);
9✔
389
            }
390

391
            if (!comment.empty() && !symbols.list().empty())
17✔
392
                symbols.list().back().attachCommentAfter(comment);
2✔
393
        }
17✔
394

395
        leaf->push_back(packageNode);
52✔
396
        leaf->push_back(symbols);
52✔
397
        // save the import data
398
        m_imports.push_back(import_data);
52✔
399

400
        comment.clear();
52✔
401
        if (newlineOrComment(&comment))
52✔
402
            leaf->list().back().attachCommentAfter(comment);
1✔
403

404
        expect(IsChar(')'));
52✔
405
        return leaf;
52✔
406
    }
3,823✔
407

408
    std::optional<Node> Parser::block()
3,748✔
409
    {
3,748✔
410
        std::optional<Node> leaf { NodeType::List };
3,748✔
411
        setNodePosAndFilename(leaf.value());
3,748✔
412

413
        bool alt_syntax = false;
3,748✔
414
        std::string comment;
3,748✔
415
        if (accept(IsChar('(')))
3,748✔
416
        {
417
            newlineOrComment(&comment);
1,609✔
418
            if (!oneOf({ "begin" }))
1,609✔
419
                return std::nullopt;
1,602✔
420
        }
7✔
421
        else if (accept(IsChar('{')))
2,139✔
422
            alt_syntax = true;
187✔
423
        else
424
            return std::nullopt;
1,952✔
425

426
        leaf->push_back(Node(Keyword::Begin).attachNearestCommentBefore(comment));
194✔
427

428
        comment.clear();
194✔
429
        newlineOrComment(&comment);
194✔
430

431
        while (!isEOF())
923✔
432
        {
433
            if (auto value = nodeOrValue(); value.has_value())
1,844✔
434
            {
435
                leaf->push_back(value.value().attachNearestCommentBefore(comment));
729✔
436
                comment.clear();
729✔
437
                newlineOrComment(&comment);
729✔
438
            }
729✔
439
            else
440
                break;
193✔
441
        }
442

443
        newlineOrComment(&comment);
194✔
444
        expect(IsChar(!alt_syntax ? ')' : '}'));
194✔
445
        setNodePosAndFilename(leaf->list().back());
193✔
446
        leaf->list().back().attachCommentAfter(comment);
193✔
447
        return leaf;
193✔
448
    }
3,749✔
449

450
    std::optional<Node> Parser::functionArgs()
183✔
451
    {
183✔
452
        expect(IsChar('('));
184✔
453
        std::optional<Node> args { NodeType::List };
183✔
454
        setNodePosAndFilename(args.value());
183✔
455

456
        std::string comment;
183✔
457
        newlineOrComment(&comment);
183✔
458
        args->attachNearestCommentBefore(comment);
183✔
459

460
        bool has_captures = false;
183✔
461

462
        while (!isEOF())
474✔
463
        {
464
            const auto pos = getCursor();
473✔
465
            if (accept(IsChar('&')))  // captures
473✔
466
            {
467
                has_captures = true;
71✔
468
                std::string capture;
71✔
469
                if (!name(&capture))
71✔
UNCOV
470
                    break;
×
471
                Node node = Node(NodeType::Capture, capture).attachNearestCommentBefore(comment);
71✔
472
                setNodePosAndFilename(node, pos);
71✔
473
                args->push_back(node);
71✔
474
            }
71✔
475
            else
476
            {
477
                const auto count = getCount();
402✔
478
                std::string symbol;
402✔
479
                if (!name(&symbol))
402✔
480
                    break;
181✔
481
                if (has_captures)
221✔
482
                {
483
                    backtrack(count);
1✔
484
                    error("Captured variables should be at the end of the argument list", symbol);
1✔
UNCOV
485
                }
×
486

487
                Node node = Node(NodeType::Symbol, symbol).attachNearestCommentBefore(comment);
220✔
488
                setNodePosAndFilename(node, pos);
220✔
489
                args->push_back(node);
220✔
490
            }
402✔
491

492
            comment.clear();
291✔
493
            newlineOrComment(&comment);
291✔
494
        }
473✔
495

496
        if (accept(IsChar(')')))
182✔
497
            return args;
180✔
498
        return std::nullopt;
2✔
499
    }
184✔
500

501
    std::optional<Node> Parser::function()
2,017✔
502
    {
2,017✔
503
        std::optional<Node> leaf { NodeType::List };
2,017✔
504
        setNodePosAndFilename(leaf.value());
2,017✔
505

506
        if (!oneOf({ "fun" }))
2,017✔
507
            return std::nullopt;
1,818✔
508
        leaf->push_back(Node(Keyword::Fun));
199✔
509

510
        std::string comment_before_args;
199✔
511
        newlineOrComment(&comment_before_args);
199✔
512

513
        while (m_allow_macro_behavior > 0)
199✔
514
        {
515
            const auto position = getCount();
16✔
516

517
            // args
518
            if (const auto value = nodeOrValue(); value.has_value())
32✔
519
            {
520
                // if value is nil, just add an empty argument bloc to prevent bugs when
521
                // declaring functions inside macros
522
                Node args = value.value();
16✔
523
                setNodePosAndFilename(args);
16✔
524
                if (args.nodeType() == NodeType::Symbol && args.string() == "nil")
16✔
525
                    leaf->push_back(Node(NodeType::List));
4✔
526
                else
527
                    leaf->push_back(args);
12✔
528
            }
16✔
529
            else
530
            {
UNCOV
531
                backtrack(position);
×
UNCOV
532
                break;
×
533
            }
534

535
            std::string comment;
16✔
536
            newlineOrComment(&comment);
16✔
537
            // body
538
            if (auto value = nodeOrValue(); value.has_value())
32✔
539
                leaf->push_back(value.value().attachNearestCommentBefore(comment));
16✔
540
            else
UNCOV
541
                errorWithNextToken("Expected a body for the function");
×
542
            setNodePosAndFilename(leaf->list().back());
16✔
543
            return leaf;
16✔
544
        }
16✔
545

546
        const auto position = getCount();
183✔
547
        if (auto args = functionArgs(); args.has_value())
366✔
548
            leaf->push_back(args.value().attachNearestCommentBefore(comment_before_args));
180✔
549
        else
550
        {
551
            backtrack(position);
2✔
552

553
            if (auto value = nodeOrValue(); value.has_value())
4✔
554
                leaf->push_back(value.value().attachNearestCommentBefore(comment_before_args));
1✔
555
            else
UNCOV
556
                errorWithNextToken("Expected an argument list");
×
557
        }
558

559
        std::string comment;
181✔
560
        newlineOrComment(&comment);
181✔
561

562
        if (auto value = nodeOrValue(); value.has_value())
362✔
563
            leaf->push_back(value.value().attachNearestCommentBefore(comment));
179✔
564
        else
565
            errorWithNextToken("Expected a body for the function");
2✔
566

567
        setNodePosAndFilename(leaf->list().back());
179✔
568
        return leaf;
179✔
569
    }
2,021✔
570

571
    std::optional<Node> Parser::macroCondition()
1,602✔
572
    {
1,602✔
573
        std::optional<Node> leaf { NodeType::Macro };
1,602✔
574
        setNodePosAndFilename(leaf.value());
1,602✔
575

576
        if (!oneOf({ "$if" }))
1,602✔
577
            return std::nullopt;
1,569✔
578
        leaf->push_back(Node(Keyword::If));
33✔
579

580
        std::string comment;
33✔
581
        newlineOrComment(&comment);
33✔
582
        leaf->attachNearestCommentBefore(comment);
33✔
583

584
        if (const auto condition = nodeOrValue(); condition.has_value())
66✔
585
            leaf->push_back(condition.value());
32✔
586
        else
587
            errorWithNextToken("$if need a valid condition");
1✔
588

589
        comment.clear();
32✔
590
        newlineOrComment(&comment);
32✔
591

592
        if (auto value_if_true = nodeOrValue(); value_if_true.has_value())
64✔
593
            leaf->push_back(value_if_true.value().attachNearestCommentBefore(comment));
31✔
594
        else
595
            errorWithNextToken("Expected a node or value after condition");
1✔
596

597
        comment.clear();
31✔
598
        newlineOrComment(&comment);
31✔
599

600
        if (auto value_if_false = nodeOrValue(); value_if_false.has_value())
55✔
601
        {
602
            leaf->push_back(value_if_false.value().attachNearestCommentBefore(comment));
24✔
603
            comment.clear();
24✔
604
            newlineOrComment(&comment);
24✔
605
            leaf->list().back().attachCommentAfter(comment);
24✔
606
        }
24✔
607

608
        setNodePosAndFilename(leaf->list().back());
31✔
609
        return leaf;
31✔
610
    }
1,604✔
611

612
    std::optional<Node> Parser::macroArgs()
107✔
613
    {
107✔
614
        if (!accept(IsChar('(')))
110✔
615
            return std::nullopt;
18✔
616

617
        std::optional<Node> args { NodeType::List };
89✔
618
        setNodePosAndFilename(args.value());
89✔
619

620
        std::string comment;
89✔
621
        newlineOrComment(&comment);
89✔
622
        args->attachNearestCommentBefore(comment);
89✔
623

624
        std::vector<std::string> names;
89✔
625
        while (!isEOF())
210✔
626
        {
627
            const auto pos = getCount();
209✔
628

629
            std::string arg_name;
209✔
630
            if (!name(&arg_name))
209✔
631
                break;
87✔
632
            comment.clear();
122✔
633
            newlineOrComment(&comment);
122✔
634
            args->push_back(Node(NodeType::Symbol, arg_name).attachNearestCommentBefore(comment));
122✔
635

636
            if (std::ranges::find(names, arg_name) != names.end())
122✔
637
            {
638
                backtrack(pos);
1✔
639
                errorWithNextToken(fmt::format("Argument names must be unique, can not reuse `{}'", arg_name));
1✔
UNCOV
640
            }
×
641
            names.push_back(arg_name);
121✔
642
        }
209✔
643

644
        const auto pos = getCount();
88✔
645
        if (sequence("..."))
88✔
646
        {
647
            std::string spread_name;
30✔
648
            if (!name(&spread_name))
30✔
649
                errorWithNextToken("Expected a name for the variadic arguments list");
1✔
650
            args->push_back(Node(NodeType::Spread, spread_name));
29✔
651

652
            comment.clear();
29✔
653
            if (newlineOrComment(&comment))
29✔
654
                args->list().back().attachCommentAfter(comment);
2✔
655

656
            if (std::ranges::find(names, spread_name) != names.end())
29✔
657
            {
658
                backtrack(pos);
1✔
659
                errorWithNextToken(fmt::format("Argument names must be unique, can not reuse `{}'", spread_name));
1✔
UNCOV
660
            }
×
661
        }
30✔
662

663
        if (!accept(IsChar(')')))
86✔
664
            return std::nullopt;
20✔
665
        comment.clear();
66✔
666
        if (newlineOrComment(&comment))
66✔
667
        {
668
            if (args->list().empty())
2✔
669
                args->attachCommentAfter(comment);
2✔
670
            else
UNCOV
671
                args->list().back().attachCommentAfter(comment);
×
672
        }
2✔
673

674
        return args;
66✔
675
    }
110✔
676

677
    std::optional<Node> Parser::macro()
3,521✔
678
    {
3,521✔
679
        std::optional<Node> leaf { NodeType::Macro };
3,521✔
680
        setNodePosAndFilename(leaf.value());
3,521✔
681

682
        if (!accept(IsChar('(')))
3,521✔
683
            return std::nullopt;
1,952✔
684
        std::string comment;
1,569✔
685
        newlineOrComment(&comment);
1,569✔
686

687
        if (!oneOf({ "$" }))
1,569✔
688
            return std::nullopt;
1,461✔
689
        newlineOrComment(&comment);
108✔
690
        leaf->attachNearestCommentBefore(comment);
108✔
691

692
        std::string symbol;
108✔
693
        if (!name(&symbol))
108✔
694
            errorWithNextToken("$ needs a symbol to declare a macro");
1✔
695
        comment.clear();
107✔
696
        newlineOrComment(&comment);
107✔
697

698
        leaf->push_back(Node(NodeType::Symbol, symbol).attachNearestCommentBefore(comment));
107✔
699

700
        const auto position = getCount();
107✔
701
        if (const auto args = macroArgs(); args.has_value())
214✔
702
            leaf->push_back(args.value());
66✔
703
        else
704
        {
705
            backtrack(position);
38✔
706

707
            ++m_allow_macro_behavior;
38✔
708
            const auto value = nodeOrValue();
38✔
709
            --m_allow_macro_behavior;
37✔
710

711
            if (value.has_value())
37✔
712
                leaf->push_back(value.value());
36✔
713
            else
714
                errorWithNextToken(fmt::format("Expected an argument list, atom or node while defining macro `{}'", symbol));
1✔
715

716
            setNodePosAndFilename(leaf->list().back());
36✔
717
            if (accept(IsChar(')')))
36✔
718
                return leaf;
36✔
719
        }
38✔
720

721
        ++m_allow_macro_behavior;
66✔
722
        const auto value = nodeOrValue();
66✔
723
        --m_allow_macro_behavior;
65✔
724

725
        if (value.has_value())
65✔
726
            leaf->push_back(value.value());
64✔
727
        else
728
        {
729
            backtrack(position);
1✔
730

731
            if (leaf->list().size() == 2)
1✔
732
                errorWithNextToken(
2✔
733
                    fmt::format(
2✔
734
                        "Expected a body while defined macro `{0}'. If you were trying to define a macro based on a function call, try wrapping it inside a begin node: ($ {0} {{ {1} }})."
1✔
735
                        "\nWithout the begin node, '{1}' is seen as an argument list.",
736
                        symbol,
737
                        leaf->list().back().repr()));
1✔
738
            else
UNCOV
739
                errorWithNextToken(fmt::format("Expected a value while defining macro `{}'", symbol));
×
740
        }
741

742
        setNodePosAndFilename(leaf->list().back());
64✔
743
        comment.clear();
64✔
744
        if (newlineOrComment(&comment))
64✔
745
            leaf->list().back().attachCommentAfter(comment);
3✔
746

747
        expect(IsChar(')'));
64✔
748
        return leaf;
64✔
749
    }
3,528✔
750

751
    std::optional<Node> Parser::functionCall()
3,400✔
752
    {
3,400✔
753
        if (!accept(IsChar('(')))
3,405✔
754
            return std::nullopt;
1,952✔
755
        auto cursor = getCursor();
1,448✔
756
        std::string comment;
1,448✔
757
        newlineOrComment(&comment);
1,448✔
758

759
        std::optional<Node> func;
1,448✔
760
        if (auto atom = anyAtomOf({ NodeType::Symbol, NodeType::Field }); atom.has_value())
2,896✔
761
            func = atom->attachNearestCommentBefore(comment);
1,435✔
762
        else if (auto nested = node(); nested.has_value())
26✔
763
            func = nested->attachNearestCommentBefore(comment);
13✔
764
        else
UNCOV
765
            return std::nullopt;
×
766
        comment.clear();
1,448✔
767
        newlineOrComment(&comment);
1,448✔
768

769
        std::optional<Node> leaf { NodeType::List };
1,448✔
770
        setNodePosAndFilename(leaf.value(), cursor);
1,448✔
771
        setNodePosAndFilename(func.value(), cursor);
1,448✔
772
        leaf->push_back(func.value());
1,448✔
773

774
        while (!isEOF())
3,958✔
775
        {
776
            if (auto arg = nodeOrValue(); arg.has_value())
7,910✔
777
            {
778
                leaf->push_back(arg.value().attachNearestCommentBefore(comment));
2,510✔
779
                comment.clear();
2,510✔
780
                newlineOrComment(&comment);
2,510✔
781
            }
2,510✔
782
            else
783
                break;
1,443✔
784
        }
785

786
        leaf->list().back().attachCommentAfter(comment);
1,446✔
787
        setNodePosAndFilename(leaf->list().back());
1,446✔
788

789
        comment.clear();
1,446✔
790
        if (newlineOrComment(&comment))
1,446✔
UNCOV
791
            leaf->list().back().attachCommentAfter(comment);
×
792

793
        expect(IsChar(')'));
1,446✔
794
        return leaf;
1,443✔
795
    }
4,848✔
796

797
    std::optional<Node> Parser::list()
1,952✔
798
    {
1,952✔
799
        std::optional<Node> leaf { NodeType::List };
1,952✔
800
        setNodePosAndFilename(leaf.value());
1,952✔
801

802
        if (!accept(IsChar('[')))
1,952✔
803
            return std::nullopt;
1,816✔
804
        leaf->push_back(Node(NodeType::Symbol, "list"));
136✔
805

806
        std::string comment;
136✔
807
        newlineOrComment(&comment);
136✔
808
        leaf->attachNearestCommentBefore(comment);
136✔
809

810
        comment.clear();
136✔
811
        while (!isEOF())
427✔
812
        {
813
            if (auto value = nodeOrValue(); value.has_value())
852✔
814
            {
815
                leaf->push_back(value.value().attachNearestCommentBefore(comment));
291✔
816
                comment.clear();
291✔
817
                newlineOrComment(&comment);
291✔
818
            }
291✔
819
            else
820
                break;
135✔
821
        }
822
        leaf->list().back().attachCommentAfter(comment);
136✔
823

824
        setNodePosAndFilename(leaf->list().back());
136✔
825

826
        expect(IsChar(']'));
136✔
827
        return leaf;
135✔
828
    }
1,953✔
829

830
    std::optional<Node> Parser::atom()
8,032✔
831
    {
8,032✔
832
        const auto pos = getCount();
8,032✔
833

834
        if (auto res = Parser::number(); res.has_value())
8,032✔
835
            return res;
1,020✔
836
        backtrack(pos);
7,009✔
837

838
        if (auto res = Parser::string(); res.has_value())
7,009✔
839
            return res;
399✔
840
        backtrack(pos);
6,610✔
841

842
        if (auto res = Parser::spread(); m_allow_macro_behavior > 0 && res.has_value())
6,610✔
843
            return res;
13✔
844
        backtrack(pos);
6,597✔
845

846
        if (auto res = Parser::field(); res.has_value())
6,597✔
847
            return res;
109✔
848
        backtrack(pos);
6,488✔
849

850
        if (auto res = Parser::symbol(); res.has_value())
6,488✔
851
            return res;
2,470✔
852
        backtrack(pos);
4,018✔
853

854
        if (auto res = Parser::nil(); res.has_value())
4,018✔
855
            return res;
39✔
856
        backtrack(pos);
3,979✔
857

858
        return std::nullopt;
3,979✔
859
    }
8,032✔
860

861
    std::optional<Node> Parser::anyAtomOf(const std::initializer_list<NodeType> types)
1,448✔
862
    {
1,448✔
863
        if (auto value = atom(); value.has_value())
2,883✔
864
        {
865
            for (const auto type : types)
2,898✔
866
            {
867
                if (value->nodeType() == type)
1,463✔
868
                    return value;
1,435✔
869
            }
1,463✔
UNCOV
870
        }
×
871
        return std::nullopt;
13✔
872
    }
1,448✔
873

874
    std::optional<Node> Parser::nodeOrValue()
6,581✔
875
    {
6,581✔
876
        if (auto value = atom(); value.has_value())
9,196✔
877
        {
878
            setNodePosAndFilename(value.value());
2,615✔
879
            return value;
2,615✔
880
        }
881
        if (auto sub_node = node(); sub_node.has_value())
6,112✔
882
        {
883
            setNodePosAndFilename(sub_node.value());
2,149✔
884
            return sub_node;
2,149✔
885
        }
886

887
        return std::nullopt;
1,814✔
888
    }
6,581✔
889

890
    std::optional<Node> Parser::wrapped(std::optional<Node> (Parser::*parser)(), const std::string& name)
23,582✔
891
    {
23,582✔
892
        auto cursor = getCursor();
23,582✔
893
        if (!prefix('('))
23,582✔
894
            return std::nullopt;
12,460✔
895
        std::string comment;
11,122✔
896
        newlineOrComment(&comment);
11,122✔
897

898
        if (auto result = (this->*parser)(); result.has_value())
11,975✔
899
        {
900
            result->attachNearestCommentBefore(result->comment() + comment);
853✔
901
            setNodePosAndFilename(result.value(), cursor);
853✔
902

903
            comment.clear();
853✔
904
            if (newlineOrComment(&comment))
853✔
905
                result.value().attachCommentAfter(comment);
9✔
906

907
            if (result->isListLike())
853✔
908
                setNodePosAndFilename(result->list().back());
853✔
909
            if (!suffix(')'))
853✔
910
                errorMissingSuffix(')', name);
1✔
911

912
            comment.clear();
852✔
913
            if (spaceComment(&comment))
852✔
914
                result.value().attachCommentAfter(comment);
14✔
915

916
            return result;
852✔
917
        }
918

919
        return std::nullopt;
10,253✔
920
    }
23,583✔
921
}
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