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

ArkScript-lang / Ark / 11445793250

21 Oct 2024 05:51PM UTC coverage: 76.877% (+0.4%) from 76.499%
11445793250

push

github

SuperFola
refactor(macro): removing useless constructor

5130 of 6673 relevant lines covered (76.88%)

9379.81 hits per line

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

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

14
    void Parser::process(const std::string& filename, const std::string& code)
176✔
15
    {
176✔
16
        initParser(filename, code);
176✔
17

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

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

45
    const Node& Parser::ast() const noexcept
163✔
46
    {
163✔
47
        return m_ast;
163✔
48
    }
49

50
    const std::vector<Import>& Parser::imports() const
97✔
51
    {
97✔
52
        return m_imports;
97✔
53
    }
54

55
    Node& Parser::setNodePosAndFilename(Node& node, const std::optional<FilePosition>& cursor) const
36,363✔
56
    {
36,363✔
57
        if (node.line() != 0 || node.col() != 0)
36,363✔
58
            return node;
5,889✔
59

60
        const auto [row, col] = cursor.value_or(getCursor());
30,474✔
61
        node.setPos(row, col);
30,474✔
62
        node.setFilename(m_filename);
30,474✔
63
        return node;
30,474✔
64
    }
36,363✔
65

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

×
71
        if (auto result = wrapped(&Parser::letMutSet, "variable assignment or declaration"))
4,481✔
72
            return result;
442✔
73
        backtrack(position);
4,034✔
74

75
        if (auto result = wrapped(&Parser::function, "function"))
4,034✔
76
            return result;
173✔
77
        backtrack(position);
3,858✔
78

79
        if (auto result = wrapped(&Parser::condition, "condition"))
3,858✔
80
            return result;
87✔
81
        backtrack(position);
3,770✔
82

83
        if (auto result = wrapped(&Parser::loop, "loop"))
3,770✔
84
            return result;
50✔
85
        backtrack(position);
3,714✔
86

87
        if (auto result = import_(); result.has_value())
3,714✔
88
            return result;
49✔
89
        backtrack(position);
3,664✔
90

91
        if (auto result = block(); result.has_value())
3,664✔
92
            return result;
192✔
93
        backtrack(position);
3,472✔
94

95
        if (auto result = wrapped(&Parser::macroCondition, "$if"))
3,472✔
96
            return result;
31✔
97
        backtrack(position);
3,435✔
98

99
        if (auto result = macro(); result.has_value())
3,435✔
100
            return result;
100✔
101
        backtrack(position);
3,335✔
102

103
        if (auto result = wrapped(&Parser::del, "del"))
3,335✔
104
            return result;
12✔
105
        backtrack(position);
3,317✔
106

107
        if (auto result = functionCall(); result.has_value())
3,317✔
108
            return result;
1,410✔
109
        backtrack(position);
1,906✔
110

111
        if (auto result = list(); result.has_value())
1,906✔
112
            return result;
132✔
113
        backtrack(position);
1,774✔
114

115
        return std::nullopt;  // will never reach
1,774✔
116
    }
4,491✔
117

118
    std::optional<Node> Parser::letMutSet()
2,388✔
119
    {
2,388✔
120
        std::optional<Node> leaf { NodeType::List };
2,388✔
121
        setNodePosAndFilename(leaf.value());
2,388✔
122

123
        std::string token;
2,388✔
124
        if (!oneOf({ "let", "mut", "set" }, &token))
2,388✔
125
            return std::nullopt;
1,941✔
126
        std::string comment;
447✔
127
        newlineOrComment(&comment);
447✔
128
        leaf->attachNearestCommentBefore(comment);
447✔
129

130
        if (token == "let")
447✔
131
            leaf->push_back(Node(Keyword::Let));
261✔
132
        else if (token == "mut")
186✔
133
            leaf->push_back(Node(Keyword::Mut));
96✔
134
        else  // "set"
135
            leaf->push_back(Node(Keyword::Set));
90✔
136

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

152
        if (leaf->constList().size() == 1)
446✔
153
        {
154
            // we haven't parsed anything while in "macro state"
155
            std::string symbol;
432✔
156
            if (!name(&symbol))
432✔
157
                errorWithNextToken(token + " needs a symbol");
2✔
158

159
            leaf->push_back(Node(NodeType::Symbol, symbol));
431✔
160
        }
432✔
161

162
        comment.clear();
444✔
163
        newlineOrComment(&comment);
444✔
164

165
        if (auto value = nodeOrValue(); value.has_value())
888✔
166
            leaf->push_back(value.value().attachNearestCommentBefore(comment));
442✔
167
        else
168
            errorWithNextToken("Expected a value");
×
169

170
        return leaf;
442✔
171
    }
2,393✔
172

173
    std::optional<Node> Parser::del()
1,428✔
174
    {
1,428✔
175
        std::optional<Node> leaf { NodeType::List };
1,428✔
176
        setNodePosAndFilename(leaf.value());
1,428✔
177

178
        if (!oneOf({ "del" }))
1,428✔
179
            return std::nullopt;
1,415✔
180
        leaf->push_back(Node(Keyword::Del));
13✔
181

182
        std::string comment;
13✔
183
        newlineOrComment(&comment);
13✔
184

185
        std::string symbol;
13✔
186
        if (!name(&symbol))
13✔
187
            errorWithNextToken("del needs a symbol");
1✔
188

189
        leaf->push_back(Node(NodeType::Symbol, symbol));
12✔
190
        leaf->list().back().attachNearestCommentBefore(comment);
12✔
191
        setNodePosAndFilename(leaf->list().back());
12✔
192

193
        return leaf;
12✔
194
    }
1,429✔
195

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

201
        if (!oneOf({ "if" }))
1,765✔
202
            return std::nullopt;
1,677✔
203

204
        std::string comment;
88✔
205
        newlineOrComment(&comment);
88✔
206

207
        leaf->push_back(Node(Keyword::If));
88✔
208

209
        if (auto condition = nodeOrValue(); condition.has_value())
176✔
210
            leaf->push_back(condition.value().attachNearestCommentBefore(comment));
88✔
211
        else
212
            errorWithNextToken("If need a valid condition");
×
213

214
        comment.clear();
88✔
215
        newlineOrComment(&comment);
88✔
216

217
        if (auto value_if_true = nodeOrValue(); value_if_true.has_value())
176✔
218
            leaf->push_back(value_if_true.value().attachNearestCommentBefore(comment));
88✔
219
        else
220
            errorWithNextToken("Expected a value");
×
221

222
        comment.clear();
88✔
223
        newlineOrComment(&comment);
88✔
224

225
        if (auto value_if_false = nodeOrValue(); value_if_false.has_value())
176✔
226
        {
227
            leaf->push_back(value_if_false.value().attachNearestCommentBefore(comment));
59✔
228
            comment.clear();
59✔
229
            if (newlineOrComment(&comment))
59✔
230
                leaf->list().back().attachCommentAfter(comment);
2✔
231
        }
59✔
232
        else if (!comment.empty())
29✔
233
            leaf->attachCommentAfter(comment);
2✔
234

235
        setNodePosAndFilename(leaf->list().back());
88✔
236
        return leaf;
88✔
237
    }
1,765✔
238

239
    std::optional<Node> Parser::loop()
1,677✔
240
    {
1,677✔
241
        std::optional<Node> leaf { NodeType::List };
1,677✔
242
        setNodePosAndFilename(leaf.value());
1,677✔
243

244
        if (!oneOf({ "while" }))
1,677✔
245
            return std::nullopt;
1,627✔
246

247
        std::string comment;
50✔
248
        newlineOrComment(&comment);
50✔
249

250
        leaf->push_back(Node(Keyword::While));
50✔
251

252
        if (auto condition = nodeOrValue(); condition.has_value())
100✔
253
            leaf->push_back(condition.value().attachNearestCommentBefore(comment));
50✔
254
        else
255
            errorWithNextToken("While need a valid condition");
×
256

257
        comment.clear();
50✔
258
        newlineOrComment(&comment);
50✔
259

260
        if (auto body = nodeOrValue(); body.has_value())
100✔
261
            leaf->push_back(body.value().attachNearestCommentBefore(comment));
50✔
262
        else
263
            errorWithNextToken("Expected a value");
×
264

265
        setNodePosAndFilename(leaf->list().back());
50✔
266
        return leaf;
50✔
267
    }
1,677✔
268

269
    std::optional<Node> Parser::import_()
3,720✔
270
    {
3,720✔
271
        std::optional<Node> leaf { NodeType::List };
3,720✔
272
        setNodePosAndFilename(leaf.value());
3,720✔
273

274
        if (!accept(IsChar('(')))
3,720✔
275
            return std::nullopt;
2,093✔
276
        std::string comment;
1,627✔
277
        newlineOrComment(&comment);
1,627✔
278
        leaf->attachNearestCommentBefore(comment);
1,627✔
279

280
        if (!oneOf({ "import" }))
1,627✔
281
            return std::nullopt;
1,572✔
282
        comment.clear();
55✔
283
        newlineOrComment(&comment);
55✔
284
        leaf->push_back(Node(Keyword::Import));
55✔
285

286
        Import import_data;
55✔
287

288
        const auto pos = getCount();
55✔
289
        if (!packageName(&import_data.prefix))
55✔
290
            errorWithNextToken("Import expected a package name");
1✔
291

292
        if (import_data.prefix.size() > 255)
54✔
293
        {
294
            backtrack(pos);
1✔
295
            errorWithNextToken(fmt::format("Import name too long, expected at most 255 characters, got {}", import_data.prefix.size()));
1✔
296
        }
×
297
        import_data.package.push_back(import_data.prefix);
53✔
298

299
        const auto [row, col] = getCursor();
53✔
300
        import_data.col = col;
53✔
301
        import_data.line = row;
53✔
302

303
        Node packageNode(NodeType::List);
53✔
304
        setNodePosAndFilename(packageNode.attachNearestCommentBefore(comment));
53✔
305
        packageNode.push_back(Node(NodeType::Symbol, import_data.prefix));
53✔
306

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

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

336
                space();
6✔
337
                expect(IsChar(')'));
6✔
338

339
                // save the import data structure to know we encounter an import node, and retrieve its data more easily later on
340
                import_data.with_prefix = false;
6✔
341
                m_imports.push_back(import_data);
6✔
342

343
                return leaf;
6✔
344
            }
345
            else
346
                break;
44✔
347
        }
348

349
        Node symbols(NodeType::List);
44✔
350
        setNodePosAndFilename(symbols);
44✔
351
        // then parse the symbols to import, if any
352
        if (space())  // FIXME: potential regression introduced here
44✔
353
        {
354
            comment.clear();
16✔
355
            newlineOrComment(&comment);
16✔
356

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

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

373
                    symbols.push_back(Node(NodeType::Symbol, symbol).attachNearestCommentBefore(comment));
21✔
374
                    comment.clear();
21✔
375
                    setNodePosAndFilename(symbols.list().back());
21✔
376
                    import_data.symbols.push_back(symbol);
21✔
377
                }
22✔
378

379
                if (!space())
24✔
380
                    break;
15✔
381
                comment.clear();
9✔
382
                newlineOrComment(&comment);
9✔
383
            }
384

385
            if (!comment.empty() && !symbols.list().empty())
15✔
386
                symbols.list().back().attachCommentAfter(comment);
2✔
387
        }
15✔
388

389
        leaf->push_back(packageNode);
43✔
390
        leaf->push_back(symbols);
43✔
391
        // save the import data
392
        m_imports.push_back(import_data);
43✔
393

394
        comment.clear();
43✔
395
        if (newlineOrComment(&comment))
43✔
396
            leaf->list().back().attachCommentAfter(comment);
1✔
397

398
        expect(IsChar(')'));
43✔
399
        return leaf;
43✔
400
    }
3,726✔
401

402
    std::optional<Node> Parser::block()
3,665✔
403
    {
3,665✔
404
        std::optional<Node> leaf { NodeType::List };
3,665✔
405
        setNodePosAndFilename(leaf.value());
3,665✔
406

407
        bool alt_syntax = false;
3,665✔
408
        std::string comment;
3,665✔
409
        if (accept(IsChar('(')))
3,665✔
410
        {
411
            newlineOrComment(&comment);
1,572✔
412
            if (!oneOf({ "begin" }))
1,572✔
413
                return std::nullopt;
1,565✔
414
        }
7✔
415
        else if (accept(IsChar('{')))
2,093✔
416
            alt_syntax = true;
186✔
417
        else
418
            return std::nullopt;
1,907✔
419

420
        leaf->push_back(Node(Keyword::Begin).attachNearestCommentBefore(comment));
193✔
421

422
        comment.clear();
193✔
423
        newlineOrComment(&comment);
193✔
424

425
        while (!isEOF())
949✔
426
        {
427
            if (auto value = nodeOrValue(); value.has_value())
1,896✔
428
            {
429
                leaf->push_back(value.value().attachNearestCommentBefore(comment));
756✔
430
                comment.clear();
756✔
431
                newlineOrComment(&comment);
756✔
432
            }
756✔
433
            else
434
                break;
192✔
435
        }
436

437
        newlineOrComment(&comment);
193✔
438
        expect(IsChar(!alt_syntax ? ')' : '}'));
193✔
439
        setNodePosAndFilename(leaf->list().back());
192✔
440
        leaf->list().back().attachCommentAfter(comment);
192✔
441
        return leaf;
192✔
442
    }
3,666✔
443

444
    std::optional<Node> Parser::functionArgs()
160✔
445
    {
160✔
446
        expect(IsChar('('));
161✔
447
        std::optional<Node> args { NodeType::List };
160✔
448
        setNodePosAndFilename(args.value());
160✔
449

450
        std::string comment;
160✔
451
        newlineOrComment(&comment);
160✔
452
        args->attachNearestCommentBefore(comment);
160✔
453

454
        bool has_captures = false;
160✔
455

456
        while (!isEOF())
417✔
457
        {
458
            if (accept(IsChar('&')))  // captures
416✔
459
            {
460
                has_captures = true;
53✔
461
                std::string capture;
53✔
462
                if (!name(&capture))
53✔
463
                    break;
×
464
                args->push_back(Node(NodeType::Capture, capture).attachNearestCommentBefore(comment));
53✔
465
                comment.clear();
53✔
466
                newlineOrComment(&comment);
53✔
467
            }
53✔
468
            else
469
            {
470
                const auto pos = getCount();
363✔
471
                std::string symbol;
363✔
472
                if (!name(&symbol))
363✔
473
                    break;
158✔
474
                if (has_captures)
205✔
475
                {
476
                    backtrack(pos);
1✔
477
                    error("Captured variables should be at the end of the argument list", symbol);
1✔
478
                }
×
479

480
                args->push_back(Node(NodeType::Symbol, symbol).attachNearestCommentBefore(comment));
204✔
481
                comment.clear();
204✔
482
                newlineOrComment(&comment);
204✔
483
            }
363✔
484
        }
485

486
        if (accept(IsChar(')')))
159✔
487
            return args;
157✔
488
        return std::nullopt;
2✔
489
    }
161✔
490

491
    std::optional<Node> Parser::function()
1,941✔
492
    {
1,941✔
493
        std::optional<Node> leaf { NodeType::List };
1,941✔
494
        setNodePosAndFilename(leaf.value());
1,941✔
495

496
        if (!oneOf({ "fun" }))
1,941✔
497
            return std::nullopt;
1,765✔
498
        leaf->push_back(Node(Keyword::Fun));
176✔
499

500
        std::string comment_before_args;
176✔
501
        newlineOrComment(&comment_before_args);
176✔
502

503
        while (m_allow_macro_behavior > 0)
176✔
504
        {
505
            const auto position = getCount();
16✔
506

507
            // args
508
            if (const auto value = nodeOrValue(); value.has_value())
32✔
509
            {
510
                // if value is nil, just add an empty argument bloc to prevent bugs when
511
                // declaring functions inside macros
512
                Node args = value.value();
16✔
513
                setNodePosAndFilename(args);
16✔
514
                if (args.nodeType() == NodeType::Symbol && args.string() == "nil")
16✔
515
                    leaf->push_back(Node(NodeType::List));
4✔
516
                else
517
                    leaf->push_back(args);
12✔
518
            }
16✔
519
            else
520
            {
521
                backtrack(position);
×
522
                break;
×
523
            }
524

525
            std::string comment;
16✔
526
            newlineOrComment(&comment);
16✔
527
            // body
528
            if (auto value = nodeOrValue(); value.has_value())
32✔
529
                leaf->push_back(value.value().attachNearestCommentBefore(comment));
16✔
530
            else
531
                errorWithNextToken("Expected a body for the function");
×
532
            setNodePosAndFilename(leaf->list().back());
16✔
533
            return leaf;
16✔
534
        }
16✔
535

536
        const auto position = getCount();
160✔
537
        if (auto args = functionArgs(); args.has_value())
320✔
538
            leaf->push_back(args.value().attachNearestCommentBefore(comment_before_args));
157✔
539
        else
540
        {
541
            backtrack(position);
2✔
542

543
            if (auto value = nodeOrValue(); value.has_value())
4✔
544
                leaf->push_back(value.value().attachNearestCommentBefore(comment_before_args));
1✔
545
            else
546
                errorWithNextToken("Expected an argument list");
×
547
        }
548

549
        std::string comment;
158✔
550
        newlineOrComment(&comment);
158✔
551

552
        if (auto value = nodeOrValue(); value.has_value())
316✔
553
            leaf->push_back(value.value().attachNearestCommentBefore(comment));
157✔
554
        else
555
            errorWithNextToken("Expected a body for the function");
1✔
556

557
        setNodePosAndFilename(leaf->list().back());
157✔
558
        return leaf;
157✔
559
    }
1,944✔
560

561
    std::optional<Node> Parser::macroCondition()
1,565✔
562
    {
1,565✔
563
        std::optional<Node> leaf { NodeType::Macro };
1,565✔
564
        setNodePosAndFilename(leaf.value());
1,565✔
565

566
        if (!oneOf({ "$if" }))
1,565✔
567
            return std::nullopt;
1,534✔
568
        leaf->push_back(Node(Keyword::If));
31✔
569

570
        std::string comment;
31✔
571
        newlineOrComment(&comment);
31✔
572
        leaf->attachNearestCommentBefore(comment);
31✔
573

574
        if (const auto condition = nodeOrValue(); condition.has_value())
62✔
575
            leaf->push_back(condition.value());
31✔
576
        else
577
            errorWithNextToken("$if need a valid condition");
×
578

579
        comment.clear();
31✔
580
        newlineOrComment(&comment);
31✔
581

582
        if (auto value_if_true = nodeOrValue(); value_if_true.has_value())
62✔
583
            leaf->push_back(value_if_true.value().attachNearestCommentBefore(comment));
31✔
584
        else
585
            errorWithNextToken("Expected a value");
×
586

587
        comment.clear();
31✔
588
        newlineOrComment(&comment);
31✔
589

590
        if (auto value_if_false = nodeOrValue(); value_if_false.has_value())
55✔
591
        {
592
            leaf->push_back(value_if_false.value().attachNearestCommentBefore(comment));
24✔
593
            comment.clear();
24✔
594
            newlineOrComment(&comment);
24✔
595
            leaf->list().back().attachCommentAfter(comment);
24✔
596
        }
24✔
597

598
        setNodePosAndFilename(leaf->list().back());
31✔
599
        return leaf;
31✔
600
    }
1,565✔
601

602
    std::optional<Node> Parser::macroArgs()
105✔
603
    {
105✔
604
        if (!accept(IsChar('(')))
107✔
605
            return std::nullopt;
17✔
606

607
        std::optional<Node> args { NodeType::List };
88✔
608
        setNodePosAndFilename(args.value());
88✔
609

610
        std::string comment;
88✔
611
        newlineOrComment(&comment);
88✔
612
        args->attachNearestCommentBefore(comment);
88✔
613

614
        std::vector<std::string> names;
88✔
615
        while (!isEOF())
208✔
616
        {
617
            const auto pos = getCount();
207✔
618

619
            std::string arg_name;
207✔
620
            if (!name(&arg_name))
207✔
621
                break;
87✔
622
            comment.clear();
120✔
623
            newlineOrComment(&comment);
120✔
624
            args->push_back(Node(NodeType::Symbol, arg_name).attachNearestCommentBefore(comment));
120✔
625

626
            if (std::ranges::find(names, arg_name) != names.end())
120✔
627
            {
628
                backtrack(pos);
×
629
                errorWithNextToken(fmt::format("Argument names must be unique, can not reuse `{}'", arg_name));
×
630
            }
×
631
            names.push_back(arg_name);
120✔
632
        }
207✔
633

634
        const auto pos = getCount();
88✔
635
        if (sequence("..."))
88✔
636
        {
637
            std::string spread_name;
30✔
638
            if (!name(&spread_name))
30✔
639
                errorWithNextToken("Expected a name for the variadic arguments list");
1✔
640
            args->push_back(Node(NodeType::Spread, spread_name));
29✔
641

642
            comment.clear();
29✔
643
            if (newlineOrComment(&comment))
29✔
644
                args->list().back().attachCommentAfter(comment);
2✔
645

646
            if (std::ranges::find(names, spread_name) != names.end())
29✔
647
            {
648
                backtrack(pos);
1✔
649
                errorWithNextToken(fmt::format("Argument names must be unique, can not reuse `{}'", spread_name));
1✔
650
            }
×
651
        }
30✔
652

653
        if (!accept(IsChar(')')))
86✔
654
            return std::nullopt;
20✔
655
        comment.clear();
66✔
656
        if (newlineOrComment(&comment))
66✔
657
        {
658
            if (args->list().empty())
2✔
659
                args->attachCommentAfter(comment);
2✔
660
            else
661
                args->list().back().attachCommentAfter(comment);
×
662
        }
2✔
663

664
        return args;
66✔
665
    }
107✔
666

667
    std::optional<Node> Parser::macro()
3,441✔
668
    {
3,441✔
669
        std::optional<Node> leaf { NodeType::Macro };
3,441✔
670
        setNodePosAndFilename(leaf.value());
3,441✔
671

672
        if (!accept(IsChar('(')))
3,441✔
673
            return std::nullopt;
1,907✔
674
        std::string comment;
1,534✔
675
        newlineOrComment(&comment);
1,534✔
676

677
        if (!oneOf({ "$" }))
1,534✔
678
            return std::nullopt;
1,428✔
679
        newlineOrComment(&comment);
106✔
680
        leaf->attachNearestCommentBefore(comment);
106✔
681

682
        std::string symbol;
106✔
683
        if (!name(&symbol))
106✔
684
            errorWithNextToken("$ needs a symbol to declare a macro");
1✔
685
        comment.clear();
105✔
686
        newlineOrComment(&comment);
105✔
687

688
        leaf->push_back(Node(NodeType::Symbol, symbol).attachNearestCommentBefore(comment));
105✔
689

690
        const auto position = getCount();
105✔
691
        if (const auto args = macroArgs(); args.has_value())
210✔
692
            leaf->push_back(args.value());
66✔
693
        else
694
        {
695
            backtrack(position);
37✔
696

697
            ++m_allow_macro_behavior;
37✔
698
            const auto value = nodeOrValue();
37✔
699
            --m_allow_macro_behavior;
36✔
700

701
            if (value.has_value())
36✔
702
                leaf->push_back(value.value());
36✔
703
            else
704
                errorWithNextToken(fmt::format("Expected an argument list, atom or node while defining macro `{}'", symbol));
×
705

706
            setNodePosAndFilename(leaf->list().back());
36✔
707
            if (accept(IsChar(')')))
36✔
708
                return leaf;
36✔
709
        }
37✔
710

711
        ++m_allow_macro_behavior;
66✔
712
        const auto value = nodeOrValue();
66✔
713
        --m_allow_macro_behavior;
65✔
714

715
        if (value.has_value())
65✔
716
            leaf->push_back(value.value());
64✔
717
        else
718
        {
719
            backtrack(position);
1✔
720

721
            if (leaf->list().size() == 2)
1✔
722
                errorWithNextToken(
2✔
723
                    fmt::format(
2✔
724
                        "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✔
725
                        "\nWithout the begin node, '{1}' is seen as an argument list.",
726
                        symbol,
727
                        leaf->list().back().repr()));
1✔
728
            else
729
                errorWithNextToken(fmt::format("Expected a value while defining macro `{}'", symbol));
×
730
        }
731

732
        setNodePosAndFilename(leaf->list().back());
64✔
733
        comment.clear();
64✔
734
        if (newlineOrComment(&comment))
64✔
735
            leaf->list().back().attachCommentAfter(comment);
3✔
736

737
        expect(IsChar(')'));
64✔
738
        return leaf;
64✔
739
    }
3,446✔
740

741
    std::optional<Node> Parser::functionCall()
3,322✔
742
    {
3,322✔
743
        if (!accept(IsChar('(')))
3,327✔
744
            return std::nullopt;
1,907✔
745
        auto cursor = getCursor();
1,415✔
746
        std::string comment;
1,415✔
747
        newlineOrComment(&comment);
1,415✔
748

749
        std::optional<Node> func;
1,415✔
750
        if (auto atom = anyAtomOf({ NodeType::Symbol, NodeType::Field }); atom.has_value())
2,830✔
751
            func = atom->attachNearestCommentBefore(comment);
1,403✔
752
        else if (auto nested = node(); nested.has_value())
24✔
753
            func = nested->attachNearestCommentBefore(comment);
12✔
754
        else
755
            return std::nullopt;
×
756
        comment.clear();
1,415✔
757
        newlineOrComment(&comment);
1,415✔
758

759
        std::optional<Node> leaf { NodeType::List };
1,415✔
760
        setNodePosAndFilename(leaf.value(), cursor);
1,415✔
761
        leaf->push_back(func.value());
1,415✔
762

763
        while (!isEOF())
3,885✔
764
        {
765
            if (auto arg = nodeOrValue(); arg.has_value())
7,764✔
766
            {
767
                leaf->push_back(arg.value().attachNearestCommentBefore(comment));
2,470✔
768
                comment.clear();
2,470✔
769
                newlineOrComment(&comment);
2,470✔
770
            }
2,470✔
771
            else
772
                break;
1,410✔
773
        }
774

775
        leaf->list().back().attachCommentAfter(comment);
1,413✔
776
        setNodePosAndFilename(leaf->list().back());
1,413✔
777

778
        comment.clear();
1,413✔
779
        if (newlineOrComment(&comment))
1,413✔
780
            leaf->list().back().attachCommentAfter(comment);
×
781

782
        expect(IsChar(')'));
1,413✔
783
        return leaf;
1,410✔
784
    }
4,737✔
785

786
    std::optional<Node> Parser::list()
1,907✔
787
    {
1,907✔
788
        std::optional<Node> leaf { NodeType::List };
1,907✔
789
        setNodePosAndFilename(leaf.value());
1,907✔
790

791
        if (!accept(IsChar('[')))
1,907✔
792
            return std::nullopt;
1,774✔
793
        leaf->push_back(Node(NodeType::Symbol, "list"));
133✔
794

795
        std::string comment;
133✔
796
        newlineOrComment(&comment);
133✔
797
        leaf->attachNearestCommentBefore(comment);
133✔
798

799
        comment.clear();
133✔
800
        while (!isEOF())
416✔
801
        {
802
            if (auto value = nodeOrValue(); value.has_value())
830✔
803
            {
804
                leaf->push_back(value.value().attachNearestCommentBefore(comment));
283✔
805
                comment.clear();
283✔
806
                newlineOrComment(&comment);
283✔
807
            }
283✔
808
            else
809
                break;
132✔
810
        }
811
        leaf->list().back().attachCommentAfter(comment);
133✔
812

813
        setNodePosAndFilename(leaf->list().back());
133✔
814

815
        expect(IsChar(']'));
133✔
816
        return leaf;
132✔
817
    }
1,908✔
818

819
    std::optional<Node> Parser::atom()
7,870✔
820
    {
7,870✔
821
        const auto pos = getCount();
7,870✔
822

823
        if (auto res = Parser::number(); res.has_value())
7,870✔
824
            return res;
973✔
825
        backtrack(pos);
6,894✔
826

827
        if (auto res = Parser::string(); res.has_value())
6,894✔
828
            return res;
361✔
829
        backtrack(pos);
6,533✔
830

831
        if (auto res = Parser::spread(); m_allow_macro_behavior > 0 && res.has_value())
6,533✔
832
            return res;
13✔
833
        backtrack(pos);
6,520✔
834

835
        if (auto res = Parser::field(); res.has_value())
6,520✔
836
            return res;
96✔
837
        backtrack(pos);
6,424✔
838

839
        if (auto res = Parser::symbol(); res.has_value())
6,424✔
840
            return res;
2,464✔
841
        backtrack(pos);
3,960✔
842

843
        if (auto res = Parser::nil(); res.has_value())
3,960✔
844
            return res;
33✔
845
        backtrack(pos);
3,927✔
846

847
        return std::nullopt;
3,927✔
848
    }
7,870✔
849

850
    std::optional<Node> Parser::anyAtomOf(const std::initializer_list<NodeType> types)
1,415✔
851
    {
1,415✔
852
        if (auto value = atom(); value.has_value())
2,818✔
853
        {
854
            for (const auto type : types)
2,829✔
855
            {
856
                if (value->nodeType() == type)
1,426✔
857
                    return value;
1,403✔
858
            }
1,426✔
859
        }
×
860
        return std::nullopt;
12✔
861
    }
1,415✔
862

863
    std::optional<Node> Parser::nodeOrValue()
6,452✔
864
    {
6,452✔
865
        if (auto value = atom(); value.has_value())
8,989✔
866
        {
867
            setNodePosAndFilename(value.value());
2,537✔
868
            return value;
2,537✔
869
        }
870
        if (auto sub_node = node(); sub_node.has_value())
6,052✔
871
        {
872
            setNodePosAndFilename(sub_node.value());
2,140✔
873
            return sub_node;
2,140✔
874
        }
875

876
        return std::nullopt;
1,772✔
877
    }
6,452✔
878

879
    std::optional<Node> Parser::wrapped(std::optional<Node> (Parser::*parser)(), const std::string& name)
22,950✔
880
    {
22,950✔
881
        auto cursor = getCursor();
22,950✔
882
        if (!prefix('('))
22,950✔
883
            return std::nullopt;
12,186✔
884
        std::string comment;
10,764✔
885
        newlineOrComment(&comment);
10,764✔
886

887
        if (auto result = (this->*parser)(); result.has_value())
11,560✔
888
        {
889
            result->attachNearestCommentBefore(result->comment() + comment);
796✔
890
            setNodePosAndFilename(result.value(), cursor);
796✔
891

892
            comment.clear();
796✔
893
            if (newlineOrComment(&comment))
796✔
894
                result.value().attachCommentAfter(comment);
9✔
895

896
            if (result->isListLike())
796✔
897
                setNodePosAndFilename(result->list().back());
796✔
898
            if (!suffix(')'))
796✔
899
                errorMissingSuffix(')', name);
1✔
900

901
            comment.clear();
795✔
902
            if (spaceComment(&comment))
795✔
903
                result.value().attachCommentAfter(comment);
14✔
904

905
            return result;
795✔
906
        }
907

908
        return std::nullopt;
9,959✔
909
    }
22,951✔
910
}
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