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

ArkScript-lang / Ark / 13344738465

15 Feb 2025 11:50AM UTC coverage: 77.022% (-1.9%) from 78.929%
13344738465

Pull #510

github

web-flow
Merge 70d135e0e into dd2f0e5fc
Pull Request #510: wip

5695 of 7394 relevant lines covered (77.02%)

44192.48 hits per line

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

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

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

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

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

45
        m_logger.traceEnd();
270✔
46
    }
308✔
47

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

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

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

×
63
        const auto [row, col] = cursor.value_or(getCursor());
151,631✔
64
        node.setPos(row, col);
151,631✔
65
        node.setFilename(m_filename);
151,631✔
66
        return node;
151,631✔
67
    }
170,001✔
68

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

74
        if (auto result = wrapped(&Parser::letMutSet, "variable assignment or declaration"))
12,746✔
75
            return result;
1,691✔
76
        backtrack(position);
11,050✔
77

78
        if (auto result = wrapped(&Parser::function, "function"))
11,050✔
79
            return result;
639✔
80
        backtrack(position);
10,407✔
81

82
        if (auto result = wrapped(&Parser::condition, "condition"))
10,407✔
83
            return result;
274✔
84
        backtrack(position);
10,130✔
85

86
        if (auto result = wrapped(&Parser::loop, "loop"))
10,130✔
87
            return result;
215✔
88
        backtrack(position);
9,905✔
89

90
        if (auto result = import_(); result.has_value())
9,905✔
91
            return result;
110✔
92
        backtrack(position);
9,794✔
93

94
        if (auto result = block(); result.has_value())
9,794✔
95
            return result;
599✔
96
        backtrack(position);
9,195✔
97

98
        if (auto result = wrapped(&Parser::macroCondition, "$if"))
9,195✔
99
            return result;
28✔
100
        backtrack(position);
9,158✔
101

102
        if (auto result = macro(); result.has_value())
9,158✔
103
            return result;
101✔
104
        backtrack(position);
9,057✔
105

106
        if (auto result = wrapped(&Parser::del, "del"))
9,057✔
107
            return result;
10✔
108
        backtrack(position);
9,041✔
109

110
        if (auto result = functionCall(); result.has_value())
9,041✔
111
            return result;
3,782✔
112
        backtrack(position);
5,258✔
113

114
        if (auto result = list(); result.has_value())
5,258✔
115
            return result;
388✔
116
        backtrack(position);
4,870✔
117

118
        return std::nullopt;  // will never reach
4,870✔
119
    }
12,763✔
120

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

126
        std::string token;
6,894✔
127
        if (!oneOf({ "let", "mut", "set" }, &token))
6,894✔
128
            return std::nullopt;
5,198✔
129
        std::string comment;
1,696✔
130
        newlineOrComment(&comment);
1,696✔
131
        leaf->attachNearestCommentBefore(comment);
1,696✔
132

133
        if (token == "let")
1,696✔
134
            leaf->push_back(Node(Keyword::Let));
804✔
135
        else if (token == "mut")
892✔
136
            leaf->push_back(Node(Keyword::Mut));
491✔
137
        else  // "set"
138
            leaf->push_back(Node(Keyword::Set));
401✔
139

140
        if (m_allow_macro_behavior > 0)
1,696✔
141
        {
142
            const auto position = getCount();
17✔
143
            if (const auto value = nodeOrValue(); value.has_value())
34✔
144
            {
145
                const auto sym = value.value();
17✔
146
                if (sym.nodeType() == NodeType::List || sym.nodeType() == NodeType::Symbol || sym.nodeType() == NodeType::Macro || sym.nodeType() == NodeType::Spread)
17✔
147
                    leaf->push_back(sym);
16✔
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
            }
17✔
151
            else
152
                backtrack(position);
×
153
        }
17✔
154

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

162
            leaf->push_back(Node(NodeType::Symbol, symbol));
1,677✔
163
        }
1,679✔
164

165
        comment.clear();
1,693✔
166
        newlineOrComment(&comment);
1,693✔
167

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

173
        return leaf;
1,691✔
174
    }
6,899✔
175

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

181
        if (!oneOf({ "del" }))
3,798✔
182
            return std::nullopt;
3,787✔
183
        leaf->push_back(Node(Keyword::Del));
11✔
184

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

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

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

196
        return leaf;
10✔
197
    }
3,799✔
198

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

204
        if (!oneOf({ "if" }))
4,555✔
205
            return std::nullopt;
4,278✔
206

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

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

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

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

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

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

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

238
        setNodePosAndFilename(leaf->list().back());
275✔
239
        return leaf;
275✔
240
    }
4,557✔
241

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

247
        if (!oneOf({ "while" }))
4,278✔
248
            return std::nullopt;
4,061✔
249

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

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

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

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

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

268
        setNodePosAndFilename(leaf->list().back());
215✔
269
        return leaf;
215✔
270
    }
4,280✔
271

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

277
        if (!accept(IsChar('(')))
9,913✔
278
            return std::nullopt;
5,852✔
279
        std::string comment;
4,061✔
280
        newlineOrComment(&comment);
4,061✔
281
        leaf->attachNearestCommentBefore(comment);
4,061✔
282

283
        if (!oneOf({ "import" }))
4,061✔
284
            return std::nullopt;
3,943✔
285
        comment.clear();
118✔
286
        newlineOrComment(&comment);
118✔
287
        leaf->push_back(Node(Keyword::Import));
118✔
288

289
        Import import_data;
118✔
290

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

295
        if (import_data.prefix.size() > 255)
117✔
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);
116✔
301

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

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

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

326
                    if (path.size() > 255)
82✔
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
            }
84✔
333
            else if (accept(IsChar(':')) && accept(IsChar('*')))  // parsing :*
113✔
334
            {
335
                leaf->push_back(packageNode);
10✔
336
                leaf->push_back(Node(NodeType::Symbol, "*"));
10✔
337
                setNodePosAndFilename(leaf->list().back());
10✔
338

339
                space();
10✔
340
                expect(IsChar(')'));
10✔
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;
10✔
344
                import_data.is_glob = true;
10✔
345
                m_imports.push_back(import_data);
10✔
346

347
                return leaf;
10✔
348
            }
349
            else
350
                break;
103✔
351
        }
352

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

361
            while (!isEOF())
64✔
362
            {
363
                if (accept(IsChar(':')))  // parsing potential :a :b :c
64✔
364
                {
365
                    std::string symbol;
61✔
366
                    if (!name(&symbol))
61✔
367
                        errorWithNextToken("Expected a valid symbol to import");
1✔
368
                    if (symbol == "*")
60✔
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() == '*')
59✔
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));
58✔
378
                    comment.clear();
58✔
379
                    setNodePosAndFilename(symbols.list().back());
58✔
380
                    import_data.symbols.push_back(symbol);
58✔
381
                    // we do not need the prefix when importing specific symbols
382
                    import_data.with_prefix = false;
58✔
383
                }
61✔
384

385
                if (!space())
61✔
386
                    break;
41✔
387
                comment.clear();
20✔
388
                newlineOrComment(&comment);
20✔
389
            }
390

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

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

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

404
        expect(IsChar(')'));
100✔
405
        return leaf;
100✔
406
    }
9,921✔
407

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

413
        bool alt_syntax = false;
9,795✔
414
        std::string comment;
9,795✔
415
        if (accept(IsChar('(')))
9,795✔
416
        {
417
            newlineOrComment(&comment);
3,943✔
418
            if (!oneOf({ "begin" }))
3,943✔
419
                return std::nullopt;
3,936✔
420
        }
7✔
421
        else if (accept(IsChar('{')))
5,852✔
422
            alt_syntax = true;
593✔
423
        else
424
            return std::nullopt;
5,259✔
425

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

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

431
        while (!isEOF())
2,740✔
432
        {
433
            if (auto value = nodeOrValue(); value.has_value())
5,478✔
434
            {
435
                leaf->push_back(value.value().attachNearestCommentBefore(comment));
2,140✔
436
                comment.clear();
2,140✔
437
                newlineOrComment(&comment);
2,140✔
438
            }
2,140✔
439
            else
440
                break;
599✔
441
        }
442

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

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

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

460
        bool has_captures = false;
627✔
461

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

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

492
            comment.clear();
1,017✔
493
            newlineOrComment(&comment);
1,017✔
494
        }
1,643✔
495

496
        if (accept(IsChar(')')))
626✔
497
            return args;
625✔
498
        return std::nullopt;
1✔
499
    }
628✔
500

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

506
        if (!oneOf({ "fun" }))
5,198✔
507
            return std::nullopt;
4,555✔
508
        leaf->push_back(Node(Keyword::Fun));
643✔
509

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

513
        while (m_allow_macro_behavior > 0)
643✔
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));
2✔
526
                else
527
                    leaf->push_back(args);
14✔
528
            }
16✔
529
            else
530
            {
531
                backtrack(position);
×
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
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();
627✔
547
        if (auto args = functionArgs(); args.has_value())
1,254✔
548
            leaf->push_back(args.value().attachNearestCommentBefore(comment_before_args));
625✔
549
        else
550
        {
551
            backtrack(position);
1✔
552

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

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

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

567
        setNodePosAndFilename(leaf->list().back());
623✔
568
        return leaf;
623✔
569
    }
5,202✔
570

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

576
        if (!oneOf({ "$if" }))
3,936✔
577
            return std::nullopt;
3,906✔
578
        leaf->push_back(Node(Keyword::If));
30✔
579

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

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

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

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

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

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

608
        setNodePosAndFilename(leaf->list().back());
28✔
609
        return leaf;
28✔
610
    }
3,938✔
611

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

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

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

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

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

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

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

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

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

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

674
        return args;
64✔
675
    }
110✔
676

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

682
        if (!accept(IsChar('(')))
9,165✔
683
            return std::nullopt;
5,259✔
684
        std::string comment;
3,906✔
685
        newlineOrComment(&comment);
3,906✔
686

687
        if (!oneOf({ "$" }))
3,906✔
688
            return std::nullopt;
3,798✔
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());
64✔
703
        else
704
        {
705
            backtrack(position);
40✔
706

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

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

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

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

725
        if (value.has_value())
63✔
726
            leaf->push_back(value.value());
62✔
727
        else if (leaf->list().size() == 2)
1✔
728
        {
729
            setNodePosAndFilename(leaf->list().back());
1✔
730
            comment.clear();
1✔
731
            if (newlineOrComment(&comment))
1✔
732
                leaf->list().back().attachCommentAfter(comment);
×
733

734
            expect(IsChar(')'));
1✔
735
            return leaf;
1✔
736
        }
737
        else
738
        {
739
            backtrack(position);
×
740
            errorWithNextToken(fmt::format("Expected a value while defining macro `{}'", symbol));
×
741
        }
742

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

748
        expect(IsChar(')'));
62✔
749
        return leaf;
62✔
750
    }
9,171✔
751

752
    std::optional<Node> Parser::functionCall()
9,046✔
753
    {
9,046✔
754
        if (!accept(IsChar('(')))
9,051✔
755
            return std::nullopt;
5,259✔
756
        std::string comment;
3,787✔
757
        newlineOrComment(&comment);
3,787✔
758
        auto cursor = getCursor();
3,787✔
759

760
        std::optional<Node> func;
3,787✔
761
        if (auto atom = anyAtomOf({ NodeType::Symbol, NodeType::Field }); atom.has_value())
7,574✔
762
            func = atom->attachNearestCommentBefore(comment);
3,768✔
763
        else if (auto nested = node(); nested.has_value())
38✔
764
            func = nested->attachNearestCommentBefore(comment);
19✔
765
        else
766
            return std::nullopt;
×
767
        comment.clear();
3,787✔
768
        newlineOrComment(&comment);
3,787✔
769

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

775
        while (!isEOF())
75,931✔
776
        {
777
            if (auto arg = nodeOrValue(); arg.has_value())
151,856✔
778
            {
779
                leaf->push_back(arg.value().attachNearestCommentBefore(comment));
72,144✔
780
                comment.clear();
72,144✔
781
                newlineOrComment(&comment);
72,144✔
782
            }
72,144✔
783
            else
784
                break;
3,782✔
785
        }
786

787
        leaf->list().back().attachCommentAfter(comment);
3,785✔
788

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

793
        expect(IsChar(')'));
3,785✔
794
        return leaf;
3,782✔
795
    }
12,833✔
796

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

802
        if (!accept(IsChar('[')))
5,259✔
803
            return std::nullopt;
4,870✔
804
        leaf->push_back(Node(NodeType::Symbol, "list"));
389✔
805

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

810
        comment.clear();
389✔
811
        while (!isEOF())
1,113✔
812
        {
813
            if (auto value = nodeOrValue(); value.has_value())
2,224✔
814
            {
815
                leaf->push_back(value.value().attachNearestCommentBefore(comment));
724✔
816
                comment.clear();
724✔
817
                newlineOrComment(&comment);
724✔
818
            }
724✔
819
            else
820
                break;
388✔
821
        }
822
        leaf->list().back().attachCommentAfter(comment);
389✔
823

824
        expect(IsChar(']'));
389✔
825
        return leaf;
388✔
826
    }
5,260✔
827

828
    std::optional<Node> Parser::atom()
87,385✔
829
    {
87,385✔
830
        const auto pos = getCount();
87,385✔
831

832
        if (auto res = Parser::number(); res.has_value())
87,385✔
833
            return res;
67,674✔
834
        backtrack(pos);
19,708✔
835

836
        if (auto res = Parser::string(); res.has_value())
19,708✔
837
            return res;
658✔
838
        backtrack(pos);
19,050✔
839

840
        if (auto res = Parser::spread(); m_allow_macro_behavior > 0 && res.has_value())
19,050✔
841
            return res;
22✔
842
        backtrack(pos);
19,028✔
843

844
        if (auto res = Parser::field(); res.has_value())
19,028✔
845
            return res;
357✔
846
        backtrack(pos);
18,671✔
847

848
        if (auto res = Parser::symbol(); res.has_value())
18,671✔
849
            return res;
7,084✔
850
        backtrack(pos);
11,587✔
851

852
        if (auto res = Parser::nil(); res.has_value())
11,587✔
853
            return res;
47✔
854
        backtrack(pos);
11,540✔
855

856
        return std::nullopt;
11,540✔
857
    }
87,385✔
858

859
    std::optional<Node> Parser::anyAtomOf(const std::initializer_list<NodeType> types)
3,787✔
860
    {
3,787✔
861
        auto cursor = getCursor();
3,787✔
862
        if (auto value = atom(); value.has_value())
7,555✔
863
        {
864
            setNodePosAndFilename(value.value(), cursor);
3,768✔
865
            for (const auto type : types)
7,599✔
866
            {
867
                if (value->nodeType() == type)
3,831✔
868
                    return value;
3,768✔
869
            }
3,831✔
870
        }
×
871
        return std::nullopt;
19✔
872
    }
3,787✔
873

874
    std::optional<Node> Parser::nodeOrValue()
83,595✔
875
    {
83,595✔
876
        auto cursor = getCursor();
83,595✔
877
        if (auto value = atom(); value.has_value())
155,669✔
878
        {
879
            setNodePosAndFilename(value.value(), cursor);
72,074✔
880
            return value;
72,074✔
881
        }
882
        if (auto sub_node = node(); sub_node.has_value())
18,168✔
883
        {
884
            setNodePosAndFilename(sub_node.value(), cursor);
6,650✔
885
            return sub_node;
6,650✔
886
        }
887

888
        return std::nullopt;
4,868✔
889
    }
83,595✔
890

891
    std::optional<Node> Parser::wrapped(std::optional<Node> (Parser::*parser)(), const std::string& name)
62,585✔
892
    {
62,585✔
893
        auto cursor = getCursor();
62,585✔
894
        if (!prefix('('))
62,585✔
895
            return std::nullopt;
33,926✔
896
        std::string comment;
28,659✔
897
        newlineOrComment(&comment);
28,659✔
898

899
        if (auto result = (this->*parser)(); result.has_value())
31,517✔
900
        {
901
            result->attachNearestCommentBefore(result->comment() + comment);
2,858✔
902
            setNodePosAndFilename(result.value(), cursor);
2,858✔
903

904
            comment.clear();
2,858✔
905
            if (newlineOrComment(&comment))
2,858✔
906
                result.value().attachCommentAfter(comment);
9✔
907

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

913
            comment.clear();
2,857✔
914
            if (spaceComment(&comment))
2,857✔
915
                result.value().attachCommentAfter(comment);
15✔
916

917
            return result;
2,857✔
918
        }
919

920
        return std::nullopt;
25,785✔
921
    }
62,586✔
922
}
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