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

ArkScript-lang / Ark / 11087505602

28 Sep 2024 10:31PM UTC coverage: 75.218% (+1.6%) from 73.576%
11087505602

push

github

SuperFola
fix(json compiler): disable import solver in json compiler

1 of 1 new or added line in 1 file covered. (100.0%)

364 existing lines in 24 files now uncovered.

4841 of 6436 relevant lines covered (75.22%)

9644.12 hits per line

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

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

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

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

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

45
    const Node& Parser::ast() const noexcept
157✔
46
    {
157✔
47
        return m_ast;
157✔
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
35,666✔
56
    {
35,666✔
57
        const auto [row, col] = cursor.value_or(getCursor());
35,666✔
58
        node.setPos(row, col);
35,666✔
59
        node.setFilename(m_filename);
35,666✔
60
        return node;
35,666✔
61
    }
35,666✔
UNCOV
62

×
63
    std::optional<Node> Parser::node()
4,394✔
64
    {
4,394✔
65
        // save current position in buffer to be able to go back if needed
66
        const auto position = getCount();
4,394✔
67

68
        if (auto result = wrapped(&Parser::letMutSet, "variable assignment or declaration"))
4,394✔
69
            return result;
436✔
70
        backtrack(position);
3,953✔
71

72
        if (auto result = wrapped(&Parser::function, "function"))
3,953✔
73
            return result;
171✔
74
        backtrack(position);
3,779✔
75

76
        if (auto result = wrapped(&Parser::condition, "condition"))
3,779✔
77
            return result;
85✔
78
        backtrack(position);
3,693✔
79

80
        if (auto result = wrapped(&Parser::loop, "loop"))
3,693✔
81
            return result;
50✔
82
        backtrack(position);
3,637✔
83

84
        if (auto result = import_(); result.has_value())
3,637✔
85
            return result;
47✔
86
        backtrack(position);
3,589✔
87

88
        if (auto result = block(); result.has_value())
3,589✔
89
            return result;
186✔
90
        backtrack(position);
3,403✔
91

92
        if (auto result = wrapped(&Parser::macroCondition, "$if"))
3,403✔
93
            return result;
31✔
94
        backtrack(position);
3,367✔
95

96
        if (auto result = macro(); result.has_value())
3,367✔
97
            return result;
95✔
98
        backtrack(position);
3,272✔
99

100
        if (auto result = wrapped(&Parser::del, "del"))
3,272✔
101
            return result;
12✔
102
        backtrack(position);
3,254✔
103

104
        if (auto result = functionCall(); result.has_value())
3,254✔
105
            return result;
1,384✔
106
        backtrack(position);
1,869✔
107

108
        if (auto result = list(); result.has_value())
1,869✔
109
            return result;
131✔
110
        backtrack(position);
1,738✔
111

112
        return std::nullopt;  // will never reach
1,738✔
113
    }
4,404✔
114

115
    std::optional<Node> Parser::letMutSet()
2,343✔
116
    {
2,343✔
117
        std::optional<Node> leaf { NodeType::List };
2,343✔
118
        setNodePosAndFilename(leaf.value());
2,343✔
119

120
        std::string token;
2,343✔
121
        if (!oneOf({ "let", "mut", "set" }, &token))
2,343✔
122
            return std::nullopt;
1,902✔
123
        std::string comment;
441✔
124
        newlineOrComment(&comment);
441✔
125
        leaf->attachNearestCommentBefore(comment);
441✔
126

127
        if (token == "let")
441✔
128
            leaf->push_back(Node(Keyword::Let));
257✔
129
        else if (token == "mut")
184✔
130
            leaf->push_back(Node(Keyword::Mut));
96✔
131
        else  // "set"
132
            leaf->push_back(Node(Keyword::Set));
88✔
133

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

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

156
            leaf->push_back(Node(NodeType::Symbol, symbol));
424✔
157
        }
426✔
UNCOV
158

×
159
        comment.clear();
438✔
160
        newlineOrComment(&comment);
438✔
161

162
        if (auto value = nodeOrValue(); value.has_value())
876✔
163
            leaf->push_back(value.value().attachNearestCommentBefore(comment));
436✔
164
        else
UNCOV
165
            errorWithNextToken("Expected a value");
×
166

167
        return leaf;
436✔
168
    }
2,348✔
169

170
    std::optional<Node> Parser::del()
1,402✔
171
    {
1,402✔
172
        std::optional<Node> leaf { NodeType::List };
1,402✔
173
        setNodePosAndFilename(leaf.value());
1,402✔
174

175
        if (!oneOf({ "del" }))
1,402✔
176
            return std::nullopt;
1,389✔
177
        leaf->push_back(Node(Keyword::Del));
13✔
178

179
        std::string comment;
13✔
180
        newlineOrComment(&comment);
13✔
181

182
        std::string symbol;
13✔
183
        if (!name(&symbol))
13✔
184
            errorWithNextToken("del needs a symbol");
1✔
185

186
        leaf->push_back(Node(NodeType::Symbol, symbol));
12✔
187
        leaf->list().back().attachNearestCommentBefore(comment);
12✔
188
        setNodePosAndFilename(leaf->list().back());
12✔
189

×
190
        return leaf;
12✔
191
    }
1,403✔
192

193
    std::optional<Node> Parser::condition()
1,728✔
194
    {
1,728✔
195
        std::optional<Node> leaf { NodeType::List };
1,728✔
196
        setNodePosAndFilename(leaf.value());
1,728✔
UNCOV
197

×
198
        if (!oneOf({ "if" }))
1,728✔
199
            return std::nullopt;
1,643✔
200

201
        std::string comment;
86✔
202
        newlineOrComment(&comment);
86✔
203

204
        leaf->push_back(Node(Keyword::If));
86✔
205

206
        if (auto condition = nodeOrValue(); condition.has_value())
172✔
207
            leaf->push_back(condition.value().attachNearestCommentBefore(comment));
86✔
208
        else
UNCOV
209
            errorWithNextToken("If need a valid condition");
×
210

211
        comment.clear();
86✔
212
        newlineOrComment(&comment);
86✔
213

214
        if (auto value_if_true = nodeOrValue(); value_if_true.has_value())
172✔
215
            leaf->push_back(value_if_true.value().attachNearestCommentBefore(comment));
86✔
216
        else
UNCOV
217
            errorWithNextToken("Expected a value");
×
218

219
        comment.clear();
86✔
220
        newlineOrComment(&comment);
86✔
221

222
        if (auto value_if_false = nodeOrValue(); value_if_false.has_value())
172✔
223
        {
224
            leaf->push_back(value_if_false.value().attachNearestCommentBefore(comment));
59✔
225
            comment.clear();
59✔
226
            if (newlineOrComment(&comment))
59✔
227
                leaf->list().back().attachCommentAfter(comment);
2✔
228
        }
59✔
229
        else if (!comment.empty())
27✔
230
            leaf->attachCommentAfter(comment);
2✔
231

232
        setNodePosAndFilename(leaf->list().back());
86✔
233
        return leaf;
86✔
234
    }
1,728✔
235

236
    std::optional<Node> Parser::loop()
1,642✔
237
    {
1,642✔
238
        std::optional<Node> leaf { NodeType::List };
1,642✔
239
        setNodePosAndFilename(leaf.value());
1,642✔
240

241
        if (!oneOf({ "while" }))
1,642✔
242
            return std::nullopt;
1,592✔
243

244
        std::string comment;
50✔
245
        newlineOrComment(&comment);
50✔
246

247
        leaf->push_back(Node(Keyword::While));
50✔
248

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

254
        comment.clear();
50✔
255
        newlineOrComment(&comment);
50✔
256

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

262
        setNodePosAndFilename(leaf->list().back());
50✔
263
        return leaf;
50✔
264
    }
1,642✔
265

266
    std::optional<Node> Parser::import_()
3,643✔
267
    {
3,643✔
268
        std::optional<Node> leaf { NodeType::List };
3,643✔
269
        setNodePosAndFilename(leaf.value());
3,643✔
270

271
        if (!accept(IsChar('(')))
3,643✔
272
            return std::nullopt;
2,051✔
273
        std::string comment;
1,592✔
274
        newlineOrComment(&comment);
1,592✔
275
        leaf->attachNearestCommentBefore(comment);
1,592✔
276

277
        if (!oneOf({ "import" }))
1,592✔
278
            return std::nullopt;
1,539✔
279
        comment.clear();
53✔
280
        newlineOrComment(&comment);
53✔
281
        leaf->push_back(Node(Keyword::Import));
53✔
282

283
        Import import_data;
53✔
284

285
        const auto pos = getCount();
53✔
286
        if (!packageName(&import_data.prefix))
53✔
287
            errorWithNextToken("Import expected a package name");
1✔
288

289
        if (import_data.prefix.size() > 255)
52✔
290
        {
291
            backtrack(pos);
1✔
292
            errorWithNextToken(fmt::format("Import name too long, expected at most 255 characters, got {}", import_data.prefix.size()));
1✔
UNCOV
293
        }
×
294
        import_data.package.push_back(import_data.prefix);
51✔
295

296
        const auto [row, col] = getCursor();
51✔
297
        import_data.col = col;
51✔
298
        import_data.line = row;
51✔
299

300
        Node packageNode(NodeType::List);
51✔
301
        setNodePosAndFilename(packageNode.attachNearestCommentBefore(comment));
51✔
302
        packageNode.push_back(Node(NodeType::Symbol, import_data.prefix));
51✔
303

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

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

333
                space();
6✔
334
                expect(IsChar(')'));
6✔
335

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

340
                return leaf;
6✔
341
            }
342
            else
343
                break;
42✔
344
        }
345

346
        Node symbols(NodeType::List);
42✔
347
        setNodePosAndFilename(symbols);
42✔
348
        // then parse the symbols to import, if any
349
        if (space())  // fixme: potential regression introduced here
42✔
350
        {
351
            comment.clear();
14✔
352
            newlineOrComment(&comment);
14✔
353

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

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

370
                    symbols.push_back(Node(NodeType::Symbol, symbol).attachNearestCommentBefore(comment));
19✔
371
                    setNodePosAndFilename(symbols.list().back());
19✔
372
                    import_data.symbols.push_back(symbol);
19✔
373
                }
20✔
374

375
                if (!space())
20✔
376
                    break;
13✔
377
                comment.clear();
7✔
378
                newlineOrComment(&comment);
7✔
379
            }
380
        }
13✔
381

382
        leaf->push_back(packageNode);
41✔
383
        leaf->push_back(symbols);
41✔
384
        // save the import data
385
        m_imports.push_back(import_data);
41✔
386

387
        comment.clear();
41✔
388
        if (newlineOrComment(&comment))
41✔
389
            leaf->list().back().attachCommentAfter(comment);
1✔
390

391
        expect(IsChar(')'));
41✔
392
        return leaf;
41✔
393
    }
3,649✔
394

395
    std::optional<Node> Parser::block()
3,590✔
396
    {
3,590✔
397
        std::optional<Node> leaf { NodeType::List };
3,590✔
398
        setNodePosAndFilename(leaf.value());
3,590✔
399

400
        bool alt_syntax = false;
3,590✔
401
        std::string comment;
3,590✔
402
        if (accept(IsChar('(')))
3,590✔
403
        {
404
            newlineOrComment(&comment);
1,539✔
405
            if (!oneOf({ "begin" }))
1,539✔
406
                return std::nullopt;
1,533✔
407
        }
6✔
408
        else if (accept(IsChar('{')))
2,051✔
409
            alt_syntax = true;
181✔
410
        else
411
            return std::nullopt;
1,870✔
412

413
        leaf->push_back(Node(Keyword::Begin).attachNearestCommentBefore(comment));
187✔
414

415
        comment.clear();
187✔
416
        newlineOrComment(&comment);
187✔
417

418
        while (!isEOF())
919✔
419
        {
420
            if (auto value = nodeOrValue(); value.has_value())
1,836✔
421
            {
422
                leaf->push_back(value.value().attachNearestCommentBefore(comment));
732✔
423
                comment.clear();
732✔
424
                newlineOrComment(&comment);
732✔
425
            }
732✔
426
            else
427
                break;
186✔
428
        }
429

430
        newlineOrComment(&comment);
187✔
431
        expect(IsChar(!alt_syntax ? ')' : '}'));
187✔
432
        setNodePosAndFilename(leaf->list().back());
186✔
433
        leaf->list().back().attachCommentAfter(comment);
186✔
434
        return leaf;
186✔
435
    }
3,591✔
436

437
    std::optional<Node> Parser::functionArgs()
160✔
438
    {
160✔
439
        expect(IsChar('('));
161✔
440
        std::optional<Node> args { NodeType::List };
160✔
441
        setNodePosAndFilename(args.value());
160✔
442

443
        std::string comment;
160✔
444
        newlineOrComment(&comment);
160✔
445
        args->attachNearestCommentBefore(comment);
160✔
446

447
        bool has_captures = false;
160✔
448

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

473
                args->push_back(Node(NodeType::Symbol, symbol).attachNearestCommentBefore(comment));
204✔
474
                comment.clear();
204✔
475
                newlineOrComment(&comment);
204✔
476
            }
363✔
477
        }
478

479
        if (accept(IsChar(')')))
159✔
480
            return args;
157✔
481
        return std::nullopt;
2✔
482
    }
161✔
483

484
    std::optional<Node> Parser::function()
1,902✔
485
    {
1,902✔
486
        std::optional<Node> leaf { NodeType::List };
1,902✔
487
        setNodePosAndFilename(leaf.value());
1,902✔
488

489
        if (!oneOf({ "fun" }))
1,902✔
490
            return std::nullopt;
1,728✔
491
        leaf->push_back(Node(Keyword::Fun));
174✔
492

493
        std::string comment_before_args;
174✔
494
        newlineOrComment(&comment_before_args);
174✔
495

496
        while (m_allow_macro_behavior > 0)
174✔
497
        {
498
            const auto position = getCount();
14✔
499

500
            // args
501
            if (const auto value = nodeOrValue(); value.has_value())
28✔
502
            {
503
                // if value is nil, just add an empty argument bloc to prevent bugs when
504
                // declaring functions inside macros
505
                Node args = value.value();
14✔
506
                setNodePosAndFilename(args);
14✔
507
                if (args.nodeType() == NodeType::Symbol && args.string() == "nil")
14✔
508
                    leaf->push_back(Node(NodeType::List));
2✔
509
                else
510
                    leaf->push_back(args);
12✔
511
            }
14✔
512
            else
513
            {
UNCOV
514
                backtrack(position);
×
UNCOV
515
                break;
×
516
            }
517

518
            std::string comment;
14✔
519
            newlineOrComment(&comment);
14✔
520
            // body
521
            if (auto value = nodeOrValue(); value.has_value())
28✔
522
                leaf->push_back(value.value().attachNearestCommentBefore(comment));
14✔
523
            else
UNCOV
524
                errorWithNextToken("Expected a body for the function");
×
525
            setNodePosAndFilename(leaf->list().back());
14✔
526
            return leaf;
14✔
527
        }
14✔
528

529
        const auto position = getCount();
160✔
530
        if (auto args = functionArgs(); args.has_value())
320✔
531
            leaf->push_back(args.value().attachNearestCommentBefore(comment_before_args));
157✔
532
        else
533
        {
534
            backtrack(position);
2✔
535

536
            if (auto value = nodeOrValue(); value.has_value())
4✔
537
                leaf->push_back(value.value().attachNearestCommentBefore(comment_before_args));
1✔
538
            else
UNCOV
539
                errorWithNextToken("Expected an argument list");
×
540
        }
541

542
        std::string comment;
158✔
543
        newlineOrComment(&comment);
158✔
544

545
        if (auto value = nodeOrValue(); value.has_value())
316✔
546
            leaf->push_back(value.value().attachNearestCommentBefore(comment));
157✔
547
        else
548
            errorWithNextToken("Expected a body for the function");
1✔
549

550
        setNodePosAndFilename(leaf->list().back());
157✔
551
        return leaf;
157✔
552
    }
1,905✔
553

554
    std::optional<Node> Parser::macroCondition()
1,533✔
555
    {
1,533✔
556
        std::optional<Node> leaf { NodeType::Macro };
1,533✔
557
        setNodePosAndFilename(leaf.value());
1,533✔
558

559
        if (!oneOf({ "$if" }))
1,533✔
560
            return std::nullopt;
1,502✔
561
        leaf->push_back(Node(Keyword::If));
31✔
562

563
        std::string comment;
31✔
564
        newlineOrComment(&comment);
31✔
565
        leaf->attachNearestCommentBefore(comment);
31✔
566

567
        if (const auto condition = nodeOrValue(); condition.has_value())
62✔
568
            leaf->push_back(condition.value());
31✔
569
        else
UNCOV
570
            errorWithNextToken("$if need a valid condition");
×
571

572
        comment.clear();
31✔
573
        newlineOrComment(&comment);
31✔
574

575
        if (auto value_if_true = nodeOrValue(); value_if_true.has_value())
62✔
576
            leaf->push_back(value_if_true.value().attachNearestCommentBefore(comment));
31✔
577
        else
UNCOV
578
            errorWithNextToken("Expected a value");
×
579

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

583
        if (auto value_if_false = nodeOrValue(); value_if_false.has_value())
55✔
584
        {
585
            leaf->push_back(value_if_false.value().attachNearestCommentBefore(comment));
24✔
586
            comment.clear();
24✔
587
            newlineOrComment(&comment);
24✔
588
            leaf->list().back().attachCommentAfter(comment);
24✔
589
        }
24✔
590

591
        setNodePosAndFilename(leaf->list().back());
31✔
592
        return leaf;
31✔
593
    }
1,533✔
594

595
    std::optional<Node> Parser::macroArgs()
99✔
596
    {
99✔
597
        if (!accept(IsChar('(')))
101✔
598
            return std::nullopt;
17✔
599

600
        std::optional<Node> args { NodeType::List };
82✔
601
        setNodePosAndFilename(args.value());
82✔
602

603
        std::string comment;
82✔
604
        newlineOrComment(&comment);
82✔
605
        args->attachNearestCommentBefore(comment);
82✔
606

607
        std::vector<std::string> names;
82✔
608
        while (!isEOF())
197✔
609
        {
610
            const auto pos = getCount();
196✔
611

612
            std::string arg_name;
196✔
613
            if (!name(&arg_name))
196✔
614
                break;
81✔
615
            comment.clear();
115✔
616
            newlineOrComment(&comment);
115✔
617
            args->push_back(Node(NodeType::Symbol, arg_name).attachNearestCommentBefore(comment));
115✔
618

619
            if (std::ranges::find(names, arg_name) != names.end())
115✔
620
            {
UNCOV
621
                backtrack(pos);
×
UNCOV
622
                errorWithNextToken(fmt::format("Argument names must be unique, can not reuse `{}'", arg_name));
×
UNCOV
623
            }
×
624
            names.push_back(arg_name);
115✔
625
        }
196✔
626

627
        const auto pos = getCount();
82✔
628
        if (sequence("..."))
82✔
629
        {
630
            std::string spread_name;
30✔
631
            if (!name(&spread_name))
30✔
632
                errorWithNextToken("Expected a name for the variadic arguments list");
1✔
633
            args->push_back(Node(NodeType::Spread, spread_name));
29✔
634

635
            comment.clear();
29✔
636
            if (newlineOrComment(&comment))
29✔
637
                args->list().back().attachCommentAfter(comment);
2✔
638

639
            if (std::ranges::find(names, spread_name) != names.end())
29✔
640
            {
641
                backtrack(pos);
1✔
642
                errorWithNextToken(fmt::format("Argument names must be unique, can not reuse `{}'", spread_name));
1✔
UNCOV
643
            }
×
644
        }
30✔
645

646
        if (!accept(IsChar(')')))
80✔
647
            return std::nullopt;
21✔
648
        comment.clear();
59✔
649
        if (newlineOrComment(&comment))
59✔
650
        {
651
            if (args->list().empty())
2✔
652
                args->attachCommentAfter(comment);
2✔
653
            else
UNCOV
654
                args->list().back().attachCommentAfter(comment);
×
655
        }
2✔
656

657
        return args;
59✔
658
    }
101✔
659

660
    std::optional<Node> Parser::macro()
3,372✔
661
    {
3,372✔
662
        std::optional<Node> leaf { NodeType::Macro };
3,372✔
663
        setNodePosAndFilename(leaf.value());
3,372✔
664

665
        if (!accept(IsChar('(')))
3,372✔
666
            return std::nullopt;
1,870✔
667
        std::string comment;
1,502✔
668
        newlineOrComment(&comment);
1,502✔
669

670
        if (!oneOf({ "$" }))
1,502✔
671
            return std::nullopt;
1,402✔
672
        newlineOrComment(&comment);
100✔
673
        leaf->attachNearestCommentBefore(comment);
100✔
674

675
        std::string symbol;
100✔
676
        if (!name(&symbol))
100✔
677
            errorWithNextToken("$ needs a symbol to declare a macro");
1✔
678
        comment.clear();
99✔
679
        newlineOrComment(&comment);
99✔
680

681
        leaf->push_back(Node(NodeType::Symbol, symbol).attachNearestCommentBefore(comment));
99✔
682

683
        const auto position = getCount();
99✔
684
        if (const auto args = macroArgs(); args.has_value())
198✔
685
            leaf->push_back(args.value());
59✔
686
        else
687
        {
688
            backtrack(position);
38✔
689

690
            ++m_allow_macro_behavior;
38✔
691
            const auto value = nodeOrValue();
38✔
692
            --m_allow_macro_behavior;
37✔
693

694
            if (value.has_value())
37✔
695
                leaf->push_back(value.value());
37✔
696
            else
UNCOV
697
                errorWithNextToken(fmt::format("Expected an argument list, atom or node while defining macro `{}'", symbol));
×
698

699
            setNodePosAndFilename(leaf->list().back());
37✔
700
            if (accept(IsChar(')')))
37✔
701
                return leaf;
37✔
702
        }
38✔
703

704
        ++m_allow_macro_behavior;
59✔
705
        const auto value = nodeOrValue();
59✔
706
        --m_allow_macro_behavior;
58✔
707

708
        if (value.has_value())
58✔
709
            leaf->push_back(value.value());
58✔
710
        else
UNCOV
711
            errorWithNextToken(fmt::format("Expected a value while defining macro `{}'", symbol));
×
712

713
        setNodePosAndFilename(leaf->list().back());
58✔
714
        comment.clear();
58✔
715
        if (newlineOrComment(&comment))
58✔
716
            leaf->list().back().attachCommentAfter(comment);
3✔
717

718
        expect(IsChar(')'));
58✔
719
        return leaf;
58✔
720
    }
3,376✔
721

722
    std::optional<Node> Parser::functionCall()
3,259✔
723
    {
3,259✔
724
        if (!accept(IsChar('(')))
3,264✔
725
            return std::nullopt;
1,870✔
726
        auto cursor = getCursor();
1,389✔
727
        std::string comment;
1,389✔
728
        newlineOrComment(&comment);
1,389✔
729

730
        std::optional<Node> func;
1,389✔
731
        if (auto atom = anyAtomOf({ NodeType::Symbol, NodeType::Field }); atom.has_value())
2,778✔
732
            func = atom->attachNearestCommentBefore(comment);
1,377✔
733
        else if (auto nested = node(); nested.has_value())
24✔
734
            func = nested->attachNearestCommentBefore(comment);
12✔
735
        else
UNCOV
736
            return std::nullopt;
×
737
        comment.clear();
1,389✔
738
        newlineOrComment(&comment);
1,389✔
739

740
        auto call_type = NodeType::List;
1,389✔
741
        if (const auto node = func.value(); node.nodeType() == NodeType::Symbol)
2,743✔
742
        {
743
            // TODO enhance this to work with more/all macros
744
            if (node.string() == "$undef")
1,354✔
745
                call_type = NodeType::Macro;
12✔
746
        }
1,354✔
747

748
        std::optional<Node> leaf { call_type };
1,389✔
749
        setNodePosAndFilename(leaf.value(), cursor);
1,389✔
750
        leaf->push_back(func.value());
1,389✔
751

752
        while (!isEOF())
3,823✔
753
        {
754
            if (auto arg = nodeOrValue(); arg.has_value())
7,640✔
755
            {
756
                leaf->push_back(arg.value().attachNearestCommentBefore(comment));
2,434✔
757
                comment.clear();
2,434✔
758
                newlineOrComment(&comment);
2,434✔
759
            }
2,434✔
760
            else
761
                break;
1,384✔
762
        }
763

764
        leaf->list().back().attachCommentAfter(comment);
1,387✔
765
        setNodePosAndFilename(leaf->list().back());
1,387✔
766

767
        comment.clear();
1,387✔
768
        if (newlineOrComment(&comment))
1,387✔
UNCOV
769
            leaf->list().back().attachCommentAfter(comment);
×
770

771
        expect(IsChar(')'));
1,387✔
772
        return leaf;
1,384✔
773
    }
3,264✔
774

775
    std::optional<Node> Parser::list()
1,870✔
776
    {
1,870✔
777
        std::optional<Node> leaf { NodeType::List };
1,870✔
778
        setNodePosAndFilename(leaf.value());
1,870✔
779

780
        if (!accept(IsChar('[')))
1,870✔
781
            return std::nullopt;
1,738✔
782
        leaf->push_back(Node(NodeType::Symbol, "list"));
132✔
783

784
        std::string comment;
132✔
785
        newlineOrComment(&comment);
132✔
786
        leaf->attachNearestCommentBefore(comment);
132✔
787

788
        comment.clear();
132✔
789
        while (!isEOF())
412✔
790
        {
791
            if (auto value = nodeOrValue(); value.has_value())
822✔
792
            {
793
                leaf->push_back(value.value().attachNearestCommentBefore(comment));
280✔
794
                comment.clear();
280✔
795
                newlineOrComment(&comment);
280✔
796
            }
280✔
797
            else
798
                break;
131✔
799
        }
800
        leaf->list().back().attachCommentAfter(comment);
132✔
801

802
        setNodePosAndFilename(leaf->list().back());
132✔
803

804
        expect(IsChar(']'));
132✔
805
        return leaf;
131✔
806
    }
1,871✔
807

808
    std::optional<Node> Parser::atom()
7,726✔
809
    {
7,726✔
810
        const auto pos = getCount();
7,726✔
811

812
        if (auto res = Parser::number(); res.has_value())
7,726✔
813
            return res;
957✔
814
        backtrack(pos);
6,766✔
815

816
        if (auto res = Parser::string(); res.has_value())
6,766✔
817
            return res;
354✔
818
        backtrack(pos);
6,412✔
819

820
        if (auto res = Parser::spread(); m_allow_macro_behavior > 0 && res.has_value())
6,412✔
821
            return res;
13✔
822
        backtrack(pos);
6,399✔
823

824
        if (auto res = Parser::field(); res.has_value())
6,399✔
825
            return res;
96✔
826
        backtrack(pos);
6,303✔
827

828
        if (auto res = Parser::symbol(); res.has_value())
6,303✔
829
            return res;
2,427✔
830
        backtrack(pos);
3,876✔
831

832
        if (auto res = Parser::nil(); res.has_value())
3,876✔
833
            return res;
29✔
834
        backtrack(pos);
3,847✔
835

836
        return std::nullopt;
3,847✔
837
    }
7,726✔
838

839
    std::optional<Node> Parser::anyAtomOf(const std::initializer_list<NodeType> types)
1,389✔
840
    {
1,389✔
841
        if (auto value = atom(); value.has_value())
2,766✔
842
        {
843
            for (const auto type : types)
2,777✔
844
            {
845
                if (value->nodeType() == type)
1,400✔
846
                    return value;
1,377✔
847
            }
1,400✔
UNCOV
848
        }
×
849
        return std::nullopt;
12✔
850
    }
1,389✔
851

852
    std::optional<Node> Parser::nodeOrValue()
6,334✔
853
    {
6,334✔
854
        if (auto value = atom(); value.has_value())
8,833✔
855
        {
856
            setNodePosAndFilename(value.value());
2,499✔
857
            return value;
2,499✔
858
        }
859
        if (auto sub_node = node(); sub_node.has_value())
5,928✔
860
        {
861
            setNodePosAndFilename(sub_node.value());
2,096✔
862
            return sub_node;
2,096✔
863
        }
864

865
        return std::nullopt;
1,736✔
866
    }
6,334✔
867

868
    std::optional<Node> Parser::wrapped(std::optional<Node> (Parser::*parser)(), const std::string& name)
22,494✔
869
    {
22,494✔
870
        auto cursor = getCursor();
22,494✔
871
        if (!prefix('('))
22,494✔
872
            return std::nullopt;
11,944✔
873
        std::string comment;
10,550✔
874
        newlineOrComment(&comment);
10,550✔
875

876
        if (auto result = (this->*parser)(); result.has_value())
11,336✔
877
        {
878
            result->attachNearestCommentBefore(result->comment() + comment);
786✔
879
            setNodePosAndFilename(result.value(), cursor);
786✔
880

881
            comment.clear();
786✔
882
            if (newlineOrComment(&comment))
786✔
883
                result.value().attachCommentAfter(comment);
9✔
884

885
            if (result->isListLike())
786✔
886
                setNodePosAndFilename(result->list().back());
786✔
887
            if (!suffix(')'))
786✔
888
                errorMissingSuffix(')', name);
1✔
889

890
            comment.clear();
785✔
891
            if (spaceComment(&comment))
785✔
892
                result.value().attachCommentAfter(comment);
14✔
893

894
            return result;
785✔
895
        }
896

897
        return std::nullopt;
9,755✔
898
    }
22,495✔
899
}
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