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

ArkScript-lang / Ark / 12164232004

04 Dec 2024 04:37PM UTC coverage: 77.815% (+0.6%) from 77.19%
12164232004

push

github

SuperFola
feat(name resolution): allow fqn if the scope is only exporting symbols

1 of 3 new or added lines in 2 files covered. (33.33%)

256 existing lines in 11 files now uncovered.

5412 of 6955 relevant lines covered (77.81%)

9300.67 hits per line

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

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

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

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

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

45
        m_logger.traceEnd();
173✔
46
    }
212✔
47

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

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

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

×
63
        const auto [row, col] = cursor.value_or(getCursor());
31,536✔
64
        node.setPos(row, col);
31,536✔
65
        node.setFilename(m_filename);
31,536✔
66
        return node;
31,536✔
67
    }
37,593✔
68

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

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

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

82
        if (auto result = wrapped(&Parser::condition, "condition"))
3,950✔
83
            return result;
90✔
84
        backtrack(position);
3,857✔
85

86
        if (auto result = wrapped(&Parser::loop, "loop"))
3,857✔
87
            return result;
47✔
88
        backtrack(position);
3,800✔
89

90
        if (auto result = import_(); result.has_value())
3,800✔
91
            return result;
58✔
92
        backtrack(position);
3,741✔
93

94
        if (auto result = block(); result.has_value())
3,741✔
95
            return result;
193✔
96
        backtrack(position);
3,548✔
97

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

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

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

110
        if (auto result = functionCall(); result.has_value())
3,389✔
111
            return result;
1,440✔
112
        backtrack(position);
1,948✔
113

114
        if (auto result = list(); result.has_value())
1,948✔
115
            return result;
135✔
116
        backtrack(position);
1,813✔
117

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

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

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

133
        if (token == "let")
480✔
134
            leaf->push_back(Node(Keyword::Let));
311✔
135
        else if (token == "mut")
169✔
136
            leaf->push_back(Node(Keyword::Mut));
86✔
137
        else  // "set"
138
            leaf->push_back(Node(Keyword::Set));
83✔
139

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

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

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

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

173
        return leaf;
475✔
174
    }
2,496✔
175

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

289
        Import import_data;
66✔
290

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

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

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

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

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

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

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

361
            while (!isEOF())
28✔
362
            {
363
                if (accept(IsChar(':')))  // parsing potential :a :b :c
28✔
364
                {
365
                    std::string symbol;
25✔
366
                    if (!name(&symbol))
25✔
367
                        errorWithNextToken("Expected a valid symbol to import");
1✔
368
                    if (symbol == "*")
24✔
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() == '*')
23✔
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✔
UNCOV
375
                    }
×
376

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

383
                if (!space())
25✔
384
                    break;
16✔
385
                comment.clear();
9✔
386
                newlineOrComment(&comment);
9✔
387
            }
388

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

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

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

402
        expect(IsChar(')'));
51✔
403
        return leaf;
51✔
404
    }
3,816✔
405

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

411
        bool alt_syntax = false;
3,742✔
412
        std::string comment;
3,742✔
413
        if (accept(IsChar('(')))
3,742✔
414
        {
415
            newlineOrComment(&comment);
1,606✔
416
            if (!oneOf({ "begin" }))
1,606✔
417
                return std::nullopt;
1,599✔
418
        }
7✔
419
        else if (accept(IsChar('{')))
2,136✔
420
            alt_syntax = true;
187✔
421
        else
422
            return std::nullopt;
1,949✔
423

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

426
        comment.clear();
194✔
427
        newlineOrComment(&comment);
194✔
428

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

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

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

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

458
        bool has_captures = false;
181✔
459

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

485
                Node node = Node(NodeType::Symbol, symbol).attachNearestCommentBefore(comment);
218✔
486
                setNodePosAndFilename(node, pos);
218✔
487
                args->push_back(node);
218✔
488
            }
398✔
489

490
            comment.clear();
289✔
491
            newlineOrComment(&comment);
289✔
492
        }
469✔
493

494
        if (accept(IsChar(')')))
180✔
495
            return args;
178✔
496
        return std::nullopt;
2✔
497
    }
182✔
498

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

504
        if (!oneOf({ "fun" }))
2,011✔
505
            return std::nullopt;
1,814✔
506
        leaf->push_back(Node(Keyword::Fun));
197✔
507

508
        std::string comment_before_args;
197✔
509
        newlineOrComment(&comment_before_args);
197✔
510

511
        while (m_allow_macro_behavior > 0)
197✔
512
        {
513
            const auto position = getCount();
16✔
514

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

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

544
        const auto position = getCount();
181✔
545
        if (auto args = functionArgs(); args.has_value())
362✔
546
            leaf->push_back(args.value().attachNearestCommentBefore(comment_before_args));
178✔
547
        else
548
        {
549
            backtrack(position);
2✔
550

551
            if (auto value = nodeOrValue(); value.has_value())
4✔
552
                leaf->push_back(value.value().attachNearestCommentBefore(comment_before_args));
1✔
553
            else
UNCOV
554
                errorWithNextToken("Expected an argument list");
×
555
        }
556

557
        std::string comment;
179✔
558
        newlineOrComment(&comment);
179✔
559

560
        if (auto value = nodeOrValue(); value.has_value())
358✔
561
            leaf->push_back(value.value().attachNearestCommentBefore(comment));
177✔
562
        else
563
            errorWithNextToken("Expected a body for the function");
2✔
564

565
        setNodePosAndFilename(leaf->list().back());
177✔
566
        return leaf;
177✔
567
    }
2,015✔
568

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

574
        if (!oneOf({ "$if" }))
1,599✔
575
            return std::nullopt;
1,566✔
576
        leaf->push_back(Node(Keyword::If));
33✔
577

578
        std::string comment;
33✔
579
        newlineOrComment(&comment);
33✔
580
        leaf->attachNearestCommentBefore(comment);
33✔
581

582
        if (const auto condition = nodeOrValue(); condition.has_value())
66✔
583
            leaf->push_back(condition.value());
32✔
584
        else
585
            errorWithNextToken("$if need a valid condition");
1✔
586

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

590
        if (auto value_if_true = nodeOrValue(); value_if_true.has_value())
64✔
591
            leaf->push_back(value_if_true.value().attachNearestCommentBefore(comment));
31✔
592
        else
593
            errorWithNextToken("Expected a node or value after condition");
1✔
594

595
        comment.clear();
31✔
596
        newlineOrComment(&comment);
31✔
597

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

606
        setNodePosAndFilename(leaf->list().back());
31✔
607
        return leaf;
31✔
608
    }
1,601✔
609

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

615
        std::optional<Node> args { NodeType::List };
89✔
616
        setNodePosAndFilename(args.value());
89✔
617

618
        std::string comment;
89✔
619
        newlineOrComment(&comment);
89✔
620
        args->attachNearestCommentBefore(comment);
89✔
621

622
        std::vector<std::string> names;
89✔
623
        while (!isEOF())
210✔
624
        {
625
            const auto pos = getCount();
209✔
626

627
            std::string arg_name;
209✔
628
            if (!name(&arg_name))
209✔
629
                break;
87✔
630
            comment.clear();
122✔
631
            newlineOrComment(&comment);
122✔
632
            args->push_back(Node(NodeType::Symbol, arg_name).attachNearestCommentBefore(comment));
122✔
633

634
            if (std::ranges::find(names, arg_name) != names.end())
122✔
635
            {
636
                backtrack(pos);
1✔
637
                errorWithNextToken(fmt::format("Argument names must be unique, can not reuse `{}'", arg_name));
1✔
UNCOV
638
            }
×
639
            names.push_back(arg_name);
121✔
640
        }
209✔
641

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

650
            comment.clear();
29✔
651
            if (newlineOrComment(&comment))
29✔
652
                args->list().back().attachCommentAfter(comment);
2✔
653

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

661
        if (!accept(IsChar(')')))
86✔
662
            return std::nullopt;
20✔
663
        comment.clear();
66✔
664
        if (newlineOrComment(&comment))
66✔
665
        {
666
            if (args->list().empty())
2✔
667
                args->attachCommentAfter(comment);
2✔
668
            else
UNCOV
669
                args->list().back().attachCommentAfter(comment);
×
670
        }
2✔
671

672
        return args;
66✔
673
    }
110✔
674

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

680
        if (!accept(IsChar('(')))
3,515✔
681
            return std::nullopt;
1,949✔
682
        std::string comment;
1,566✔
683
        newlineOrComment(&comment);
1,566✔
684

685
        if (!oneOf({ "$" }))
1,566✔
686
            return std::nullopt;
1,458✔
687
        newlineOrComment(&comment);
108✔
688
        leaf->attachNearestCommentBefore(comment);
108✔
689

690
        std::string symbol;
108✔
691
        if (!name(&symbol))
108✔
692
            errorWithNextToken("$ needs a symbol to declare a macro");
1✔
693
        comment.clear();
107✔
694
        newlineOrComment(&comment);
107✔
695

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

698
        const auto position = getCount();
107✔
699
        if (const auto args = macroArgs(); args.has_value())
214✔
700
            leaf->push_back(args.value());
66✔
701
        else
702
        {
703
            backtrack(position);
38✔
704

705
            ++m_allow_macro_behavior;
38✔
706
            const auto value = nodeOrValue();
38✔
707
            --m_allow_macro_behavior;
37✔
708

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

714
            setNodePosAndFilename(leaf->list().back());
36✔
715
            if (accept(IsChar(')')))
36✔
716
                return leaf;
36✔
717
        }
38✔
718

719
        ++m_allow_macro_behavior;
66✔
720
        const auto value = nodeOrValue();
66✔
721
        --m_allow_macro_behavior;
65✔
722

723
        if (value.has_value())
65✔
724
            leaf->push_back(value.value());
64✔
725
        else
726
        {
727
            backtrack(position);
1✔
728

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

740
        setNodePosAndFilename(leaf->list().back());
64✔
741
        comment.clear();
64✔
742
        if (newlineOrComment(&comment))
64✔
743
            leaf->list().back().attachCommentAfter(comment);
3✔
744

745
        expect(IsChar(')'));
64✔
746
        return leaf;
64✔
747
    }
3,522✔
748

749
    std::optional<Node> Parser::functionCall()
3,394✔
750
    {
3,394✔
751
        if (!accept(IsChar('(')))
3,399✔
752
            return std::nullopt;
1,949✔
753
        auto cursor = getCursor();
1,445✔
754
        std::string comment;
1,445✔
755
        newlineOrComment(&comment);
1,445✔
756

757
        std::optional<Node> func;
1,445✔
758
        if (auto atom = anyAtomOf({ NodeType::Symbol, NodeType::Field }); atom.has_value())
2,890✔
759
            func = atom->attachNearestCommentBefore(comment);
1,432✔
760
        else if (auto nested = node(); nested.has_value())
26✔
761
            func = nested->attachNearestCommentBefore(comment);
13✔
762
        else
UNCOV
763
            return std::nullopt;
×
764
        comment.clear();
1,445✔
765
        newlineOrComment(&comment);
1,445✔
766

767
        std::optional<Node> leaf { NodeType::List };
1,445✔
768
        setNodePosAndFilename(leaf.value(), cursor);
1,445✔
769
        leaf->push_back(func.value());
1,445✔
770

771
        while (!isEOF())
3,952✔
772
        {
773
            if (auto arg = nodeOrValue(); arg.has_value())
7,898✔
774
            {
775
                leaf->push_back(arg.value().attachNearestCommentBefore(comment));
2,507✔
776
                comment.clear();
2,507✔
777
                newlineOrComment(&comment);
2,507✔
778
            }
2,507✔
779
            else
780
                break;
1,440✔
781
        }
782

783
        leaf->list().back().attachCommentAfter(comment);
1,443✔
784
        setNodePosAndFilename(leaf->list().back());
1,443✔
785

786
        comment.clear();
1,443✔
787
        if (newlineOrComment(&comment))
1,443✔
UNCOV
788
            leaf->list().back().attachCommentAfter(comment);
×
789

790
        expect(IsChar(')'));
1,443✔
791
        return leaf;
1,440✔
792
    }
4,839✔
793

794
    std::optional<Node> Parser::list()
1,949✔
795
    {
1,949✔
796
        std::optional<Node> leaf { NodeType::List };
1,949✔
797
        setNodePosAndFilename(leaf.value());
1,949✔
798

799
        if (!accept(IsChar('[')))
1,949✔
800
            return std::nullopt;
1,813✔
801
        leaf->push_back(Node(NodeType::Symbol, "list"));
136✔
802

803
        std::string comment;
136✔
804
        newlineOrComment(&comment);
136✔
805
        leaf->attachNearestCommentBefore(comment);
136✔
806

807
        comment.clear();
136✔
808
        while (!isEOF())
427✔
809
        {
810
            if (auto value = nodeOrValue(); value.has_value())
852✔
811
            {
812
                leaf->push_back(value.value().attachNearestCommentBefore(comment));
291✔
813
                comment.clear();
291✔
814
                newlineOrComment(&comment);
291✔
815
            }
291✔
816
            else
817
                break;
135✔
818
        }
819
        leaf->list().back().attachCommentAfter(comment);
136✔
820

821
        setNodePosAndFilename(leaf->list().back());
136✔
822

823
        expect(IsChar(']'));
136✔
824
        return leaf;
135✔
825
    }
1,950✔
826

827
    std::optional<Node> Parser::atom()
8,019✔
828
    {
8,019✔
829
        const auto pos = getCount();
8,019✔
830

831
        if (auto res = Parser::number(); res.has_value())
8,019✔
832
            return res;
1,019✔
833
        backtrack(pos);
6,997✔
834

835
        if (auto res = Parser::string(); res.has_value())
6,997✔
836
            return res;
399✔
837
        backtrack(pos);
6,598✔
838

839
        if (auto res = Parser::spread(); m_allow_macro_behavior > 0 && res.has_value())
6,598✔
840
            return res;
13✔
841
        backtrack(pos);
6,585✔
842

843
        if (auto res = Parser::field(); res.has_value())
6,585✔
844
            return res;
109✔
845
        backtrack(pos);
6,476✔
846

847
        if (auto res = Parser::symbol(); res.has_value())
6,476✔
848
            return res;
2,465✔
849
        backtrack(pos);
4,011✔
850

851
        if (auto res = Parser::nil(); res.has_value())
4,011✔
852
            return res;
39✔
853
        backtrack(pos);
3,972✔
854

855
        return std::nullopt;
3,972✔
856
    }
8,019✔
857

858
    std::optional<Node> Parser::anyAtomOf(const std::initializer_list<NodeType> types)
1,445✔
859
    {
1,445✔
860
        if (auto value = atom(); value.has_value())
2,877✔
861
        {
862
            for (const auto type : types)
2,892✔
863
            {
864
                if (value->nodeType() == type)
1,460✔
865
                    return value;
1,432✔
866
            }
1,460✔
UNCOV
867
        }
×
868
        return std::nullopt;
13✔
869
    }
1,445✔
870

871
    std::optional<Node> Parser::nodeOrValue()
6,571✔
872
    {
6,571✔
873
        if (auto value = atom(); value.has_value())
9,183✔
874
        {
875
            setNodePosAndFilename(value.value());
2,612✔
876
            return value;
2,612✔
877
        }
878
        if (auto sub_node = node(); sub_node.has_value())
6,101✔
879
        {
880
            setNodePosAndFilename(sub_node.value());
2,145✔
881
            return sub_node;
2,145✔
882
        }
883

884
        return std::nullopt;
1,811✔
885
    }
6,571✔
886

887
    std::optional<Node> Parser::wrapped(std::optional<Node> (Parser::*parser)(), const std::string& name)
23,536✔
888
    {
23,536✔
889
        auto cursor = getCursor();
23,536✔
890
        if (!prefix('('))
23,536✔
891
            return std::nullopt;
12,442✔
892
        std::string comment;
11,094✔
893
        newlineOrComment(&comment);
11,094✔
894

895
        if (auto result = (this->*parser)(); result.has_value())
11,943✔
896
        {
897
            result->attachNearestCommentBefore(result->comment() + comment);
849✔
898
            setNodePosAndFilename(result.value(), cursor);
849✔
899

900
            comment.clear();
849✔
901
            if (newlineOrComment(&comment))
849✔
902
                result.value().attachCommentAfter(comment);
9✔
903

904
            if (result->isListLike())
849✔
905
                setNodePosAndFilename(result->list().back());
849✔
906
            if (!suffix(')'))
849✔
907
                errorMissingSuffix(')', name);
1✔
908

909
            comment.clear();
848✔
910
            if (spaceComment(&comment))
848✔
911
                result.value().attachCommentAfter(comment);
14✔
912

913
            return result;
848✔
914
        }
915

916
        return std::nullopt;
10,229✔
917
    }
23,537✔
918
}
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