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

ArkScript-lang / Ark / 11629611787

01 Nov 2024 12:48PM UTC coverage: 77.319% (+0.3%) from 77.042%
11629611787

push

github

SuperFola
feat(tests): adding first test for IR generation and optimization

5209 of 6737 relevant lines covered (77.32%)

9473.78 hits per line

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

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

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

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

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

45
        m_logger.traceEnd();
149✔
46
    }
188✔
47

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

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

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

×
63
        const auto [row, col] = cursor.value_or(getCursor());
30,937✔
64
        node.setPos(row, col);
30,937✔
65
        node.setFilename(m_filename);
30,937✔
66
        return node;
30,937✔
67
    }
36,902✔
68

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

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

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

82
        if (auto result = wrapped(&Parser::condition, "condition"))
3,922✔
83
            return result;
91✔
84
        backtrack(position);
3,828✔
85

86
        if (auto result = wrapped(&Parser::loop, "loop"))
3,828✔
87
            return result;
50✔
88
        backtrack(position);
3,768✔
89

90
        if (auto result = import_(); result.has_value())
3,768✔
91
            return result;
49✔
92
        backtrack(position);
3,718✔
93

94
        if (auto result = block(); result.has_value())
3,718✔
95
            return result;
194✔
96
        backtrack(position);
3,524✔
97

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

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

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

110
        if (auto result = functionCall(); result.has_value())
3,365✔
111
            return result;
1,429✔
112
        backtrack(position);
1,935✔
113

114
        if (auto result = list(); result.has_value())
1,935✔
115
            return result;
132✔
116
        backtrack(position);
1,803✔
117

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

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

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

133
        if (token == "let")
450✔
134
            leaf->push_back(Node(Keyword::Let));
264✔
135
        else if (token == "mut")
186✔
136
            leaf->push_back(Node(Keyword::Mut));
96✔
137
        else  // "set"
138
            leaf->push_back(Node(Keyword::Set));
90✔
139

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

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

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

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

173
        return leaf;
445✔
174
    }
2,433✔
175

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

289
        Import import_data;
57✔
290

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

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

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

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

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

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

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

346
                return leaf;
6✔
347
            }
348
            else
349
                break;
46✔
350
        }
351

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

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

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

376
                    symbols.push_back(Node(NodeType::Symbol, symbol).attachNearestCommentBefore(comment));
21✔
377
                    comment.clear();
21✔
378
                    setNodePosAndFilename(symbols.list().back());
21✔
379
                    import_data.symbols.push_back(symbol);
21✔
380
                }
24✔
381

382
                if (!space())
24✔
383
                    break;
15✔
384
                comment.clear();
9✔
385
                newlineOrComment(&comment);
9✔
386
            }
387

388
            if (!comment.empty() && !symbols.list().empty())
15✔
389
                symbols.list().back().attachCommentAfter(comment);
2✔
390
        }
15✔
391

392
        leaf->push_back(packageNode);
43✔
393
        leaf->push_back(symbols);
43✔
394
        // save the import data
395
        m_imports.push_back(import_data);
43✔
396

397
        comment.clear();
43✔
398
        if (newlineOrComment(&comment))
43✔
399
            leaf->list().back().attachCommentAfter(comment);
1✔
400

401
        expect(IsChar(')'));
43✔
402
        return leaf;
43✔
403
    }
3,784✔
404

405
    std::optional<Node> Parser::block()
3,719✔
406
    {
3,719✔
407
        std::optional<Node> leaf { NodeType::List };
3,719✔
408
        setNodePosAndFilename(leaf.value());
3,719✔
409

410
        bool alt_syntax = false;
3,719✔
411
        std::string comment;
3,719✔
412
        if (accept(IsChar('(')))
3,719✔
413
        {
414
            newlineOrComment(&comment);
1,595✔
415
            if (!oneOf({ "begin" }))
1,595✔
416
                return std::nullopt;
1,588✔
417
        }
7✔
418
        else if (accept(IsChar('{')))
2,124✔
419
            alt_syntax = true;
188✔
420
        else
421
            return std::nullopt;
1,936✔
422

423
        leaf->push_back(Node(Keyword::Begin).attachNearestCommentBefore(comment));
195✔
424

425
        comment.clear();
195✔
426
        newlineOrComment(&comment);
195✔
427

428
        while (!isEOF())
954✔
429
        {
430
            if (auto value = nodeOrValue(); value.has_value())
1,906✔
431
            {
432
                leaf->push_back(value.value().attachNearestCommentBefore(comment));
759✔
433
                comment.clear();
759✔
434
                newlineOrComment(&comment);
759✔
435
            }
759✔
436
            else
437
                break;
194✔
438
        }
439

440
        newlineOrComment(&comment);
195✔
441
        expect(IsChar(!alt_syntax ? ')' : '}'));
195✔
442
        setNodePosAndFilename(leaf->list().back());
194✔
443
        leaf->list().back().attachCommentAfter(comment);
194✔
444
        return leaf;
194✔
445
    }
3,720✔
446

447
    std::optional<Node> Parser::functionArgs()
164✔
448
    {
164✔
449
        expect(IsChar('('));
165✔
450
        std::optional<Node> args { NodeType::List };
164✔
451
        setNodePosAndFilename(args.value());
164✔
452

453
        std::string comment;
164✔
454
        newlineOrComment(&comment);
164✔
455
        args->attachNearestCommentBefore(comment);
164✔
456

457
        bool has_captures = false;
164✔
458

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

483
                args->push_back(Node(NodeType::Symbol, symbol).attachNearestCommentBefore(comment));
211✔
484
                comment.clear();
211✔
485
                newlineOrComment(&comment);
211✔
486
            }
374✔
487
        }
488

489
        if (accept(IsChar(')')))
163✔
490
            return args;
161✔
491
        return std::nullopt;
2✔
492
    }
165✔
493

494
    std::optional<Node> Parser::function()
1,978✔
495
    {
1,978✔
496
        std::optional<Node> leaf { NodeType::List };
1,978✔
497
        setNodePosAndFilename(leaf.value());
1,978✔
498

499
        if (!oneOf({ "fun" }))
1,978✔
500
            return std::nullopt;
1,798✔
501
        leaf->push_back(Node(Keyword::Fun));
180✔
502

503
        std::string comment_before_args;
180✔
504
        newlineOrComment(&comment_before_args);
180✔
505

506
        while (m_allow_macro_behavior > 0)
180✔
507
        {
508
            const auto position = getCount();
16✔
509

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

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

539
        const auto position = getCount();
164✔
540
        if (auto args = functionArgs(); args.has_value())
328✔
541
            leaf->push_back(args.value().attachNearestCommentBefore(comment_before_args));
161✔
542
        else
543
        {
544
            backtrack(position);
2✔
545

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

552
        std::string comment;
162✔
553
        newlineOrComment(&comment);
162✔
554

555
        if (auto value = nodeOrValue(); value.has_value())
324✔
556
            leaf->push_back(value.value().attachNearestCommentBefore(comment));
160✔
557
        else
558
            errorWithNextToken("Expected a body for the function");
2✔
559

560
        setNodePosAndFilename(leaf->list().back());
160✔
561
        return leaf;
160✔
562
    }
1,982✔
563

564
    std::optional<Node> Parser::macroCondition()
1,588✔
565
    {
1,588✔
566
        std::optional<Node> leaf { NodeType::Macro };
1,588✔
567
        setNodePosAndFilename(leaf.value());
1,588✔
568

569
        if (!oneOf({ "$if" }))
1,588✔
570
            return std::nullopt;
1,555✔
571
        leaf->push_back(Node(Keyword::If));
33✔
572

573
        std::string comment;
33✔
574
        newlineOrComment(&comment);
33✔
575
        leaf->attachNearestCommentBefore(comment);
33✔
576

577
        if (const auto condition = nodeOrValue(); condition.has_value())
66✔
578
            leaf->push_back(condition.value());
32✔
579
        else
580
            errorWithNextToken("$if need a valid condition");
1✔
581

582
        comment.clear();
32✔
583
        newlineOrComment(&comment);
32✔
584

585
        if (auto value_if_true = nodeOrValue(); value_if_true.has_value())
64✔
586
            leaf->push_back(value_if_true.value().attachNearestCommentBefore(comment));
31✔
587
        else
588
            errorWithNextToken("Expected a node or value after condition");
1✔
589

590
        comment.clear();
31✔
591
        newlineOrComment(&comment);
31✔
592

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

601
        setNodePosAndFilename(leaf->list().back());
31✔
602
        return leaf;
31✔
603
    }
1,590✔
604

605
    std::optional<Node> Parser::macroArgs()
107✔
606
    {
107✔
607
        if (!accept(IsChar('(')))
110✔
608
            return std::nullopt;
18✔
609

610
        std::optional<Node> args { NodeType::List };
89✔
611
        setNodePosAndFilename(args.value());
89✔
612

613
        std::string comment;
89✔
614
        newlineOrComment(&comment);
89✔
615
        args->attachNearestCommentBefore(comment);
89✔
616

617
        std::vector<std::string> names;
89✔
618
        while (!isEOF())
210✔
619
        {
620
            const auto pos = getCount();
209✔
621

622
            std::string arg_name;
209✔
623
            if (!name(&arg_name))
209✔
624
                break;
87✔
625
            comment.clear();
122✔
626
            newlineOrComment(&comment);
122✔
627
            args->push_back(Node(NodeType::Symbol, arg_name).attachNearestCommentBefore(comment));
122✔
628

629
            if (std::ranges::find(names, arg_name) != names.end())
122✔
630
            {
631
                backtrack(pos);
1✔
632
                errorWithNextToken(fmt::format("Argument names must be unique, can not reuse `{}'", arg_name));
1✔
633
            }
×
634
            names.push_back(arg_name);
121✔
635
        }
209✔
636

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

645
            comment.clear();
29✔
646
            if (newlineOrComment(&comment))
29✔
647
                args->list().back().attachCommentAfter(comment);
2✔
648

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

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

667
        return args;
66✔
668
    }
110✔
669

670
    std::optional<Node> Parser::macro()
3,491✔
671
    {
3,491✔
672
        std::optional<Node> leaf { NodeType::Macro };
3,491✔
673
        setNodePosAndFilename(leaf.value());
3,491✔
674

675
        if (!accept(IsChar('(')))
3,491✔
676
            return std::nullopt;
1,936✔
677
        std::string comment;
1,555✔
678
        newlineOrComment(&comment);
1,555✔
679

680
        if (!oneOf({ "$" }))
1,555✔
681
            return std::nullopt;
1,447✔
682
        newlineOrComment(&comment);
108✔
683
        leaf->attachNearestCommentBefore(comment);
108✔
684

685
        std::string symbol;
108✔
686
        if (!name(&symbol))
108✔
687
            errorWithNextToken("$ needs a symbol to declare a macro");
1✔
688
        comment.clear();
107✔
689
        newlineOrComment(&comment);
107✔
690

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

693
        const auto position = getCount();
107✔
694
        if (const auto args = macroArgs(); args.has_value())
214✔
695
            leaf->push_back(args.value());
66✔
696
        else
697
        {
698
            backtrack(position);
38✔
699

700
            ++m_allow_macro_behavior;
38✔
701
            const auto value = nodeOrValue();
38✔
702
            --m_allow_macro_behavior;
37✔
703

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

709
            setNodePosAndFilename(leaf->list().back());
36✔
710
            if (accept(IsChar(')')))
36✔
711
                return leaf;
36✔
712
        }
38✔
713

714
        ++m_allow_macro_behavior;
66✔
715
        const auto value = nodeOrValue();
66✔
716
        --m_allow_macro_behavior;
65✔
717

718
        if (value.has_value())
65✔
719
            leaf->push_back(value.value());
64✔
720
        else
721
        {
722
            backtrack(position);
1✔
723

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

735
        setNodePosAndFilename(leaf->list().back());
64✔
736
        comment.clear();
64✔
737
        if (newlineOrComment(&comment))
64✔
738
            leaf->list().back().attachCommentAfter(comment);
3✔
739

740
        expect(IsChar(')'));
64✔
741
        return leaf;
64✔
742
    }
3,498✔
743

744
    std::optional<Node> Parser::functionCall()
3,370✔
745
    {
3,370✔
746
        if (!accept(IsChar('(')))
3,375✔
747
            return std::nullopt;
1,936✔
748
        auto cursor = getCursor();
1,434✔
749
        std::string comment;
1,434✔
750
        newlineOrComment(&comment);
1,434✔
751

752
        std::optional<Node> func;
1,434✔
753
        if (auto atom = anyAtomOf({ NodeType::Symbol, NodeType::Field }); atom.has_value())
2,868✔
754
            func = atom->attachNearestCommentBefore(comment);
1,422✔
755
        else if (auto nested = node(); nested.has_value())
24✔
756
            func = nested->attachNearestCommentBefore(comment);
12✔
757
        else
758
            return std::nullopt;
×
759
        comment.clear();
1,434✔
760
        newlineOrComment(&comment);
1,434✔
761

762
        std::optional<Node> leaf { NodeType::List };
1,434✔
763
        setNodePosAndFilename(leaf.value(), cursor);
1,434✔
764
        leaf->push_back(func.value());
1,434✔
765

766
        while (!isEOF())
3,939✔
767
        {
768
            if (auto arg = nodeOrValue(); arg.has_value())
7,872✔
769
            {
770
                leaf->push_back(arg.value().attachNearestCommentBefore(comment));
2,505✔
771
                comment.clear();
2,505✔
772
                newlineOrComment(&comment);
2,505✔
773
            }
2,505✔
774
            else
775
                break;
1,429✔
776
        }
777

778
        leaf->list().back().attachCommentAfter(comment);
1,432✔
779
        setNodePosAndFilename(leaf->list().back());
1,432✔
780

781
        comment.clear();
1,432✔
782
        if (newlineOrComment(&comment))
1,432✔
783
            leaf->list().back().attachCommentAfter(comment);
×
784

785
        expect(IsChar(')'));
1,432✔
786
        return leaf;
1,429✔
787
    }
4,804✔
788

789
    std::optional<Node> Parser::list()
1,936✔
790
    {
1,936✔
791
        std::optional<Node> leaf { NodeType::List };
1,936✔
792
        setNodePosAndFilename(leaf.value());
1,936✔
793

794
        if (!accept(IsChar('[')))
1,936✔
795
            return std::nullopt;
1,803✔
796
        leaf->push_back(Node(NodeType::Symbol, "list"));
133✔
797

798
        std::string comment;
133✔
799
        newlineOrComment(&comment);
133✔
800
        leaf->attachNearestCommentBefore(comment);
133✔
801

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

816
        setNodePosAndFilename(leaf->list().back());
133✔
817

818
        expect(IsChar(']'));
133✔
819
        return leaf;
132✔
820
    }
1,937✔
821

822
    std::optional<Node> Parser::atom()
7,977✔
823
    {
7,977✔
824
        const auto pos = getCount();
7,977✔
825

826
        if (auto res = Parser::number(); res.has_value())
7,977✔
827
            return res;
986✔
828
        backtrack(pos);
6,988✔
829

830
        if (auto res = Parser::string(); res.has_value())
6,988✔
831
            return res;
362✔
832
        backtrack(pos);
6,626✔
833

834
        if (auto res = Parser::spread(); m_allow_macro_behavior > 0 && res.has_value())
6,626✔
835
            return res;
13✔
836
        backtrack(pos);
6,613✔
837

838
        if (auto res = Parser::field(); res.has_value())
6,613✔
839
            return res;
96✔
840
        backtrack(pos);
6,517✔
841

842
        if (auto res = Parser::symbol(); res.has_value())
6,517✔
843
            return res;
2,500✔
844
        backtrack(pos);
4,017✔
845

846
        if (auto res = Parser::nil(); res.has_value())
4,017✔
847
            return res;
33✔
848
        backtrack(pos);
3,984✔
849

850
        return std::nullopt;
3,984✔
851
    }
7,977✔
852

853
    std::optional<Node> Parser::anyAtomOf(const std::initializer_list<NodeType> types)
1,434✔
854
    {
1,434✔
855
        if (auto value = atom(); value.has_value())
2,856✔
856
        {
857
            for (const auto type : types)
2,867✔
858
            {
859
                if (value->nodeType() == type)
1,445✔
860
                    return value;
1,422✔
861
            }
1,445✔
862
        }
×
863
        return std::nullopt;
12✔
864
    }
1,434✔
865

866
    std::optional<Node> Parser::nodeOrValue()
6,540✔
867
    {
6,540✔
868
        if (auto value = atom(); value.has_value())
9,108✔
869
        {
870
            setNodePosAndFilename(value.value());
2,568✔
871
            return value;
2,568✔
872
        }
873
        if (auto sub_node = node(); sub_node.has_value())
6,137✔
874
        {
875
            setNodePosAndFilename(sub_node.value());
2,168✔
876
            return sub_node;
2,168✔
877
        }
878

879
        return std::nullopt;
1,801✔
880
    }
6,540✔
881

882
    std::optional<Node> Parser::wrapped(std::optional<Node> (Parser::*parser)(), const std::string& name)
23,311✔
883
    {
23,311✔
884
        auto cursor = getCursor();
23,311✔
885
        if (!prefix('('))
23,311✔
886
            return std::nullopt;
12,368✔
887
        std::string comment;
10,943✔
888
        newlineOrComment(&comment);
10,943✔
889

890
        if (auto result = (this->*parser)(); result.has_value())
11,749✔
891
        {
892
            result->attachNearestCommentBefore(result->comment() + comment);
806✔
893
            setNodePosAndFilename(result.value(), cursor);
806✔
894

895
            comment.clear();
806✔
896
            if (newlineOrComment(&comment))
806✔
897
                result.value().attachCommentAfter(comment);
9✔
898

899
            if (result->isListLike())
806✔
900
                setNodePosAndFilename(result->list().back());
806✔
901
            if (!suffix(')'))
806✔
902
                errorMissingSuffix(')', name);
1✔
903

904
            comment.clear();
805✔
905
            if (spaceComment(&comment))
805✔
906
                result.value().attachCommentAfter(comment);
14✔
907

908
            return result;
805✔
909
        }
910

911
        return std::nullopt;
10,121✔
912
    }
23,312✔
913
}
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