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

ArkScript-lang / Ark / 12467799916

23 Dec 2024 01:44PM UTC coverage: 79.433% (+0.03%) from 79.406%
12467799916

push

github

SuperFola
feat(tests): adding more macros tests

5770 of 7264 relevant lines covered (79.43%)

15068.23 hits per line

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

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

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

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

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

45
        m_logger.traceEnd();
217✔
46
    }
255✔
47

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

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

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

×
63
        const auto [row, col] = cursor.value_or(getCursor());
102,112✔
64
        node.setPos(row, col);
102,112✔
65
        node.setFilename(m_filename);
102,112✔
66
        return node;
102,112✔
67
    }
108,650✔
68

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

74
        if (auto result = wrapped(&Parser::letMutSet, "variable assignment or declaration"))
5,132✔
75
            return result;
510✔
76
        backtrack(position);
4,617✔
77

78
        if (auto result = wrapped(&Parser::function, "function"))
4,617✔
79
            return result;
201✔
80
        backtrack(position);
4,412✔
81

82
        if (auto result = wrapped(&Parser::condition, "condition"))
4,412✔
83
            return result;
93✔
84
        backtrack(position);
4,316✔
85

86
        if (auto result = wrapped(&Parser::loop, "loop"))
4,316✔
87
            return result;
48✔
88
        backtrack(position);
4,258✔
89

90
        if (auto result = import_(); result.has_value())
4,258✔
91
            return result;
65✔
92
        backtrack(position);
4,192✔
93

94
        if (auto result = block(); result.has_value())
4,192✔
95
            return result;
203✔
96
        backtrack(position);
3,989✔
97

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

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

106
        if (auto result = wrapped(&Parser::del, "del"))
3,844✔
107
            return result;
10✔
108
        backtrack(position);
3,828✔
109

110
        if (auto result = functionCall(); result.has_value())
3,828✔
111
            return result;
1,602✔
112
        backtrack(position);
2,225✔
113

114
        if (auto result = list(); result.has_value())
2,225✔
115
            return result;
187✔
116
        backtrack(position);
2,038✔
117

118
        return std::nullopt;  // will never reach
2,038✔
119
    }
5,149✔
120

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

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

133
        if (token == "let")
515✔
134
            leaf->push_back(Node(Keyword::Let));
334✔
135
        else if (token == "mut")
181✔
136
            leaf->push_back(Node(Keyword::Mut));
99✔
137
        else  // "set"
138
            leaf->push_back(Node(Keyword::Set));
82✔
139

140
        if (m_allow_macro_behavior > 0)
515✔
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)
514✔
156
        {
157
            // we haven't parsed anything while in "macro state"
×
158
            std::string symbol;
500✔
159
            if (!name(&symbol))
501✔
160
                errorWithNextToken(token + " needs a symbol");
2✔
161

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

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

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

173
        return leaf;
510✔
174
    }
2,714✔
175

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

181
        if (!oneOf({ "del" }))
1,618✔
182
            return std::nullopt;
1,607✔
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
    }
1,619✔
198

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

277
        if (!accept(IsChar('(')))
4,266✔
278
            return std::nullopt;
2,423✔
279
        std::string comment;
1,843✔
280
        newlineOrComment(&comment);
1,843✔
281
        leaf->attachNearestCommentBefore(comment);
1,843✔
282

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

289
        Import import_data;
73✔
290

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

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

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

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

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

326
                    if (path.size() > 255)
47✔
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
            }
49✔
333
            else if (accept(IsChar(':')) && accept(IsChar('*')))  // parsing :*
68✔
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;
61✔
351
        }
352

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

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

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

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

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

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

404
        expect(IsChar(')'));
58✔
405
        return leaf;
58✔
406
    }
4,274✔
407

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

413
        bool alt_syntax = false;
4,193✔
414
        std::string comment;
4,193✔
415
        if (accept(IsChar('(')))
4,193✔
416
        {
417
            newlineOrComment(&comment);
1,770✔
418
            if (!oneOf({ "begin" }))
1,770✔
419
                return std::nullopt;
1,763✔
420
        }
7✔
421
        else if (accept(IsChar('{')))
2,423✔
422
            alt_syntax = true;
197✔
423
        else
424
            return std::nullopt;
2,226✔
425

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

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

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

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

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

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

460
        bool has_captures = false;
189✔
461

462
        while (!isEOF())
485✔
463
        {
464
            const auto pos = getCursor();
484✔
465
            if (accept(IsChar('&')))  // captures
484✔
466
            {
467
                has_captures = true;
72✔
468
                std::string capture;
72✔
469
                if (!name(&capture))
72✔
470
                    break;
×
471
                Node node = Node(NodeType::Capture, capture).attachNearestCommentBefore(comment);
72✔
472
                setNodePosAndFilename(node, pos);
72✔
473
                args->push_back(node);
72✔
474
            }
72✔
475
            else
476
            {
477
                const auto count = getCount();
412✔
478
                std::string symbol;
412✔
479
                if (!name(&symbol))
412✔
480
                    break;
187✔
481
                if (has_captures)
225✔
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);
224✔
488
                setNodePosAndFilename(node, pos);
224✔
489
                args->push_back(node);
224✔
490
            }
412✔
491

492
            comment.clear();
296✔
493
            newlineOrComment(&comment);
296✔
494
        }
484✔
495

496
        if (accept(IsChar(')')))
188✔
497
            return args;
186✔
498
        return std::nullopt;
2✔
499
    }
190✔
500

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

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

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

513
        while (m_allow_macro_behavior > 0)
205✔
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
            {
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();
189✔
547
        if (auto args = functionArgs(); args.has_value())
378✔
548
            leaf->push_back(args.value().attachNearestCommentBefore(comment_before_args));
186✔
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
556
                errorWithNextToken("Expected an argument list");
×
557
        }
558

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

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

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

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

576
        if (!oneOf({ "$if" }))
1,763✔
577
            return std::nullopt;
1,730✔
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,765✔
611

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

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

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

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

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

636
            if (std::ranges::find(names, arg_name) != names.end())
125✔
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);
124✔
642
        }
216✔
643

644
        const auto pos = getCount();
92✔
645
        if (sequence("..."))
92✔
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✔
660
            }
×
661
        }
30✔
662

663
        if (!accept(IsChar(')')))
90✔
664
            return std::nullopt;
25✔
665
        comment.clear();
65✔
666
        if (newlineOrComment(&comment))
65✔
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;
65✔
675
    }
114✔
676

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

682
        if (!accept(IsChar('(')))
3,956✔
683
            return std::nullopt;
2,226✔
684
        std::string comment;
1,730✔
685
        newlineOrComment(&comment);
1,730✔
686

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

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

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

700
        const auto position = getCount();
111✔
701
        if (const auto args = macroArgs(); args.has_value())
222✔
702
            leaf->push_back(args.value());
65✔
703
        else
704
        {
705
            backtrack(position);
43✔
706

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

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

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

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

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

734
            expect(IsChar(')'));
×
735
            return leaf;
×
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());
64✔
744
        comment.clear();
64✔
745
        if (newlineOrComment(&comment))
64✔
746
            leaf->list().back().attachCommentAfter(comment);
3✔
747

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

752
    std::optional<Node> Parser::functionCall()
3,833✔
753
    {
3,833✔
754
        if (!accept(IsChar('(')))
3,838✔
755
            return std::nullopt;
2,226✔
756
        std::string comment;
1,607✔
757
        newlineOrComment(&comment);
1,607✔
758
        auto cursor = getCursor();
1,607✔
759

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

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

775
        while (!isEOF())
69,990✔
776
        {
777
            if (auto arg = nodeOrValue(); arg.has_value())
139,974✔
778
            {
779
                leaf->push_back(arg.value().attachNearestCommentBefore(comment));
68,383✔
780
                comment.clear();
68,383✔
781
                newlineOrComment(&comment);
68,383✔
782
            }
68,383✔
783
            else
784
                break;
1,602✔
785
        }
786

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

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

793
        expect(IsChar(')'));
1,605✔
794
        return leaf;
1,602✔
795
    }
5,440✔
796

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

802
        if (!accept(IsChar('[')))
2,226✔
803
            return std::nullopt;
2,038✔
804
        leaf->push_back(Node(NodeType::Symbol, "list"));
188✔
805

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

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

824
        expect(IsChar(']'));
188✔
825
        return leaf;
187✔
826
    }
2,227✔
827

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

832
        if (auto res = Parser::number(); res.has_value())
74,577✔
833
            return res;
66,829✔
834
        backtrack(pos);
7,745✔
835

836
        if (auto res = Parser::string(); res.has_value())
7,745✔
837
            return res;
469✔
838
        backtrack(pos);
7,276✔
839

840
        if (auto res = Parser::spread(); m_allow_macro_behavior > 0 && res.has_value())
7,276✔
841
            return res;
13✔
842
        backtrack(pos);
7,263✔
843

844
        if (auto res = Parser::field(); res.has_value())
7,263✔
845
            return res;
109✔
846
        backtrack(pos);
7,154✔
847

848
        if (auto res = Parser::symbol(); res.has_value())
7,154✔
849
            return res;
2,710✔
850
        backtrack(pos);
4,444✔
851

852
        if (auto res = Parser::nil(); res.has_value())
4,444✔
853
            return res;
43✔
854
        backtrack(pos);
4,401✔
855

856
        return std::nullopt;
4,401✔
857
    }
74,577✔
858

859
    std::optional<Node> Parser::anyAtomOf(const std::initializer_list<NodeType> types)
1,607✔
860
    {
1,607✔
861
        auto cursor = getCursor();
1,607✔
862
        if (auto value = atom(); value.has_value())
3,201✔
863
        {
864
            setNodePosAndFilename(value.value(), cursor);
1,594✔
865
            for (const auto type : types)
3,216✔
866
            {
867
                if (value->nodeType() == type)
1,622✔
868
                    return value;
1,594✔
869
            }
1,622✔
870
        }
×
871
        return std::nullopt;
13✔
872
    }
1,607✔
873

874
    std::optional<Node> Parser::nodeOrValue()
72,967✔
875
    {
72,967✔
876
        auto cursor = getCursor();
72,967✔
877
        if (auto value = atom(); value.has_value())
141,546✔
878
        {
879
            setNodePosAndFilename(value.value(), cursor);
68,579✔
880
            return value;
68,579✔
881
        }
882
        if (auto sub_node = node(); sub_node.has_value())
6,734✔
883
        {
884
            setNodePosAndFilename(sub_node.value(), cursor);
2,349✔
885
            return sub_node;
2,349✔
886
        }
887

888
        return std::nullopt;
2,036✔
889
    }
72,967✔
890

891
    std::optional<Node> Parser::wrapped(std::optional<Node> (Parser::*parser)(), const std::string& name)
26,310✔
892
    {
26,310✔
893
        auto cursor = getCursor();
26,310✔
894
        if (!prefix('('))
26,310✔
895
            return std::nullopt;
14,144✔
896
        std::string comment;
12,166✔
897
        newlineOrComment(&comment);
12,166✔
898

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

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

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

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

917
            return result;
893✔
918
        }
919

920
        return std::nullopt;
11,256✔
921
    }
26,311✔
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