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

ArkScript-lang / Ark / 20065764942

09 Dec 2025 01:49PM UTC coverage: 90.564% (+0.008%) from 90.556%
20065764942

push

github

SuperFola
chore(tests): adding IR generation tests for arg attributes

8043 of 8881 relevant lines covered (90.56%)

180911.91 hits per line

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

97.36
/src/arkreactor/Compiler/Macros/Processor.cpp
1
#include <Ark/Compiler/Macros/Processor.hpp>
2

3
#include <utility>
4
#include <algorithm>
5
#include <cassert>
6
#include <ranges>
7
#include <sstream>
8
#include <fmt/core.h>
9

10
#include <Ark/Constants.hpp>
11
#include <Ark/Error/Exceptions.hpp>
12
#include <Ark/Builtins/Builtins.hpp>
13
#include <Ark/Compiler/Macros/Executor.hpp>
14
#include <Ark/Compiler/Macros/Executors/Symbol.hpp>
15
#include <Ark/Compiler/Macros/Executors/Function.hpp>
16
#include <Ark/Compiler/Macros/Executors/Conditional.hpp>
17

18
namespace Ark::internal
19
{
20
    MacroProcessor::MacroProcessor(const unsigned debug) noexcept :
690✔
21
        Pass("MacroProcessor", debug)
345✔
22
    {
690✔
23
        // create executors pipeline
24
        m_conditional_executor = std::make_shared<ConditionalExecutor>(this);
345✔
25
        m_executors.emplace_back(std::make_shared<SymbolExecutor>(this));
345✔
26
        m_executors.emplace_back(m_conditional_executor);
345✔
27
        m_executors.emplace_back(std::make_shared<FunctionExecutor>(this));
345✔
28
    }
345✔
29

30
    void MacroProcessor::process(const Node& ast)
308✔
31
    {
308✔
32
        m_logger.debug("Processing macros...");
308✔
33
        m_logger.traceStart("process");
308✔
34

35
        // to be able to modify it
36
        m_ast = ast;
308✔
37
        processNode(m_ast, 0);
308✔
38

39
        m_logger.traceEnd();
308✔
40
        m_logger.trace("AST after processing macros");
308✔
41
        if (m_logger.shouldTrace())
308✔
42
            m_ast.debugPrint(std::cout) << '\n';
1✔
43
    }
308✔
44

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

50
    void MacroProcessor::handleMacroNode(Node& node)
279✔
51
    {
279✔
52
        // a macro needs at least 2 nodes, name + value is the minimal form
53
        // this is guaranteed by the parser
54
        assert(node.constList().size() >= 2 && "Invalid macro, missing value");
279✔
55

56
        const Node& first_node = node.list()[0];
279✔
57

58
        // (macro name value)
59
        if (node.constList().size() == 2)
279✔
60
        {
61
            assert(first_node.nodeType() == NodeType::Symbol && "Can not define a macro without a symbol");
73✔
62
            applyMacro(node.list()[1], 0);
49✔
63
            node.list()[1] = evaluate(node.list()[1], 0, true);
49✔
64
            m_macros.back().add(first_node.string(), node);
49✔
65
        }
49✔
66
        // ($ name (args) body)
67
        else if (node.constList().size() == 3 && first_node.nodeType() == NodeType::Symbol)
206✔
68
        {
69
            assert(node.constList()[1].nodeType() == NodeType::List && "Invalid macro argument's list");
60✔
70
            m_macros.back().add(first_node.string(), node);
60✔
71
        }
60✔
72
        // in case we had a conditional, we need to evaluate and expand it
73
        else if (m_conditional_executor->canHandle(node))
146✔
74
            m_conditional_executor->applyMacro(node, 0);
143✔
75
    }
249✔
76

77
    // todo find a better way to do this
78
    void MacroProcessor::registerFuncDef(const Node& node)
61,657✔
79
    {
61,657✔
80
        if (node.nodeType() == NodeType::List && node.constList().size() == 3 && node.constList()[0].nodeType() == NodeType::Keyword)
61,657✔
81
        {
82
            const Keyword kw = node.constList()[0].keyword();
14,619✔
83
            // checking for function definition, which can occur only inside an assignment node
84
            if (kw != Keyword::Let && kw != Keyword::Mut && kw != Keyword::Set)
14,619✔
85
                return;
5,886✔
86

87
            const Node inner = node.constList()[2];
8,733✔
88
            if (inner.nodeType() != NodeType::List)
8,733✔
89
                return;
2,368✔
90

91
            if (!inner.constList().empty() && inner.constList()[0].nodeType() == NodeType::Keyword && inner.constList()[0].keyword() == Keyword::Fun)
6,365✔
92
            {
93
                const Node symbol = node.constList()[1];
2,813✔
94
                if (symbol.nodeType() == NodeType::Symbol)
2,813✔
95
                    m_defined_functions.emplace(symbol.string(), inner.constList()[1]);
2,812✔
96
                else
97
                    throwMacroProcessingError(fmt::format("Can not use a {} to define a variable", typeToString(symbol)), symbol);
1✔
98
            }
2,813✔
99
        }
14,619✔
100
    }
61,657✔
101

102
    void MacroProcessor::processNode(Node& node, unsigned depth, const bool is_processing_namespace)
254,014✔
103
    {
254,014✔
104
        if (depth >= MaxMacroProcessingDepth)
254,014✔
105
            throwMacroProcessingError(
1✔
106
                fmt::format(
2✔
107
                    "Max recursion depth reached ({}). You most likely have a badly defined recursive macro calling itself without a proper exit condition",
1✔
108
                    MaxMacroProcessingDepth),
109
                node);
1✔
110

111
        if (node.nodeType() == NodeType::List)
254,013✔
112
        {
113
            bool has_created = false;
62,075✔
114
            // recursive call
115
            std::size_t i = 0;
62,075✔
116
            while (i < node.list().size())
316,156✔
117
            {
118
                const std::size_t pos = i;
254,081✔
119
                Node& child = node.list()[pos];
254,081✔
120
                const bool had_begin = isBeginNode(child);
254,081✔
121
                bool added_begin = false;
254,081✔
122

123
                if (child.nodeType() == NodeType::Macro)
254,081✔
124
                {
125
                    // create a scope only if needed
126
                    if ((!m_macros.empty() && !m_macros.back().empty() && m_macros.back().depth() < depth && !is_processing_namespace) ||
351✔
127
                        (!has_created && !is_processing_namespace) ||
133✔
128
                        (m_macros.empty() && is_processing_namespace))
61✔
129
                    {
130
                        has_created = true;
157✔
131
                        m_macros.emplace_back(depth);
157✔
132
                    }
157✔
133

134
                    handleMacroNode(child);
216✔
135
                    added_begin = isBeginNode(child) && !had_begin;
216✔
136
                }
216✔
137
                else  // running on non-macros
138
                {
139
                    applyMacro(child, 0);
253,865✔
140
                    added_begin = isBeginNode(child) && !had_begin;
253,865✔
141

142
                    if (child.nodeType() == NodeType::Unused)
253,865✔
143
                        node.list().erase(node.constList().begin() + static_cast<std::vector<Node>::difference_type>(pos));
9✔
144
                    else if (!added_begin)
253,856✔
145
                        // Go forward only if it isn't a macro, because we delete macros
146
                        // while running on the AST. Also, applying a macro can result in
147
                        // nodes being marked unused, and delete them immediately. When
148
                        // that happens, we can't increment i, otherwise we delete a node,
149
                        // advance, resulting in a node being skipped!
150
                        ++i;
252,829✔
151

152
                    // process subnodes if any
153
                    if (node.nodeType() == NodeType::List && pos < node.constList().size())
253,865✔
154
                    {
155
                        processNode(child, depth + 1);
253,595✔
156
                        // needed if we created a function node from a macro
157
                        registerFuncDef(child);
253,595✔
158
                    }
253,595✔
159
                }
160

161
                if (pos < node.constList().size())
254,081✔
162
                {
163
                    // if we now have a surrounding (begin ...) and didn't have one before, remove it
164
                    if (added_begin)
253,810✔
165
                        removeBegin(node, pos);
1,037✔
166
                    // if there is an unused node or a leftover macro need, we need to get rid of it in the final ast
167
                    else if (child.nodeType() == NodeType::Macro || child.nodeType() == NodeType::Unused)
252,773✔
168
                        node.list().erase(node.constList().begin() + static_cast<std::vector<Node>::difference_type>(pos));
128✔
169
                }
253,810✔
170
            }
254,081✔
171

172
            // delete a scope only if needed
173
            if (!m_macros.empty() && m_macros.back().depth() == depth && !is_processing_namespace)
62,075✔
174
                m_macros.pop_back();
132✔
175
        }
62,075✔
176
        else if (node.nodeType() == NodeType::Namespace)
191,938✔
177
        {
178
            Node& namespace_ast = *node.arkNamespace().ast;
139✔
179
            // We have to use depth - 1 because it was incremented previously, as a namespace node
180
            // must be in a list node. Then depth - 1 is safe as depth is at least 1.
181
            // Using a decreased value of depth ensures that macros are stored in the correct scope,
182
            // and not deleted when the namespace traversal ends.
183
            processNode(namespace_ast, depth - 1, /* is_processing_namespace= */ true);
139✔
184
        }
139✔
185
    }
254,014✔
186

187
    bool MacroProcessor::applyMacro(Node& node, const unsigned depth)
256,036✔
188
    {
256,036✔
189
        if (depth > MaxMacroProcessingDepth)
256,036✔
190
            throwMacroProcessingError(
1✔
191
                fmt::format(
2✔
192
                    "Max macro processing depth reached ({}). You may have a macro trying to evaluate itself, try splitting your code in multiple nodes.",
1✔
193
                    MaxMacroProcessingDepth),
194
                node);
1✔
195

196
        for (const auto& executor : m_executors)
1,023,495✔
197
        {
198
            if (executor->canHandle(node))
767,664✔
199
            {
200
                m_macros_being_applied.push_back(executor->macroNode(node));
110,492✔
201
                const bool applied = executor->applyMacro(node, depth);
110,492✔
202
                m_macros_being_applied.pop_back();
110,288✔
203

204
                if (applied)
110,288✔
205
                    return true;
1,302✔
206
            }
110,288✔
207
        }
767,460✔
208
        return false;
254,631✔
209
    }
255,832✔
210

211
    void MacroProcessor::checkMacroArgCountEq(const Node& node, std::size_t expected, const std::string& name, const bool is_expansion, const std::string& kind)
1,923✔
212
    {
1,923✔
213
        const std::size_t argcount = node.constList().size();
1,923✔
214
        if (argcount != expected + 1)
1,923✔
215
        {
216
            if (is_expansion)
7✔
217
                throwMacroProcessingError(
12✔
218
                    fmt::format(
12✔
219
                        "When expanding `{}' inside a macro, got {} argument{}, expected {}",
6✔
220
                        name,
6✔
221
                        argcount - 1,
6✔
222
                        argcount > 2 ? "s" : "",
6✔
223
                        expected),
224
                    node);
6✔
225
            else
226
                throwMacroProcessingError(
2✔
227
                    fmt::format(
3✔
228
                        "Interpreting a `{}'{} with {} argument{}, expected {}.",
1✔
229
                        name,
1✔
230
                        kind.empty() ? kind : " " + kind,
1✔
231
                        argcount - 1,
1✔
232
                        argcount > 2 ? "s" : "",
1✔
233
                        expected),
234
                    node);
1✔
235
        }
236
    }
1,924✔
237

238
    void MacroProcessor::checkMacroArgCountGe(const Node& node, std::size_t expected, const std::string& name, const std::string& kind)
71✔
239
    {
71✔
240
        const std::size_t argcount = node.constList().size();
71✔
241
        if (argcount < expected + 1)
71✔
242
            throwMacroProcessingError(
2✔
243
                fmt::format(
3✔
244
                    "Interpreting a `{}'{} with {} argument{}, expected at least {}.",
1✔
245
                    name,
1✔
246
                    kind.empty() ? kind : " " + kind,
1✔
247
                    argcount - 1,
1✔
248
                    argcount > 2 ? "s" : "",
1✔
249
                    expected),
250
                node);
1✔
251
    }
71✔
252

253
    Node MacroProcessor::evaluate(Node& node, const unsigned depth, const bool is_not_body)
26,698✔
254
    {
26,698✔
255
        if (node.nodeType() == NodeType::Symbol)
26,698✔
256
        {
257
            const Node* macro = findNearestMacro(node.string());
7,324✔
258
            if (macro != nullptr && macro->constList().size() == 2)
7,324✔
259
                return macro->constList()[1];
159✔
260
            return node;
7,165✔
261
        }
7,324✔
262
        if (node.nodeType() == NodeType::List && !node.constList().empty() && node.list()[0].nodeType() == NodeType::Symbol)
19,374✔
263
        {
264
            const std::string& name = node.list()[0].string();
10,580✔
265
            const std::size_t argcount = node.list().size() - 1;
10,580✔
266

267
            if (const Node* macro = findNearestMacro(name); macro != nullptr)
21,160✔
268
            {
269
                applyMacro(node.list()[0], depth + 1);
772✔
270
                if (node.list()[0].nodeType() == NodeType::Unused)
772✔
271
                    node.list().erase(node.constList().begin());
×
272
            }
772✔
273
            else if (name == "=" && is_not_body)
9,808✔
274
            {
275
                checkMacroArgCountEq(node, 2, "=", /* is_expansion= */ false, "condition");
51✔
276
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
50✔
277
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
50✔
278
                return (one == two) ? getTrueNode() : getFalseNode();
50✔
279
            }
50✔
280
            else if (name == "!=" && is_not_body)
9,757✔
281
            {
282
                checkMacroArgCountEq(node, 2, "!=", /* is_expansion= */ false, "condition");
2✔
283
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
2✔
284
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
2✔
285
                return (one != two) ? getTrueNode() : getFalseNode();
2✔
286
            }
2✔
287
            else if (name == "<" && is_not_body)
9,755✔
288
            {
289
                checkMacroArgCountEq(node, 2, "<", /* is_expansion= */ false, "condition");
2✔
290
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
2✔
291
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
2✔
292
                return (one < two) ? getTrueNode() : getFalseNode();
2✔
293
            }
2✔
294
            else if (name == ">" && is_not_body)
9,753✔
295
            {
296
                checkMacroArgCountEq(node, 2, ">", /* is_expansion= */ false, "condition");
45✔
297
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
45✔
298
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
45✔
299
                return !(one < two) && (one != two) ? getTrueNode() : getFalseNode();
45✔
300
            }
45✔
301
            else if (name == "<=" && is_not_body)
9,708✔
302
            {
303
                checkMacroArgCountEq(node, 2, "<=", /* is_expansion= */ false, "condition");
2✔
304
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
2✔
305
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
2✔
306
                return one < two || one == two ? getTrueNode() : getFalseNode();
2✔
307
            }
2✔
308
            else if (name == ">=" && is_not_body)
9,706✔
309
            {
310
                checkMacroArgCountEq(node, 2, ">=", /* is_expansion= */ false, "condition");
9✔
311
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
9✔
312
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
9✔
313
                return !(one < two) ? getTrueNode() : getFalseNode();
9✔
314
            }
9✔
315
            else if (name == "+" && is_not_body)
9,697✔
316
            {
317
                checkMacroArgCountGe(node, 2, "+", "operator");
7✔
318
                double v = 0.0;
6✔
319
                for (auto& child : node.list() | std::ranges::views::drop(1))
21✔
320
                {
321
                    Node ev = evaluate(child, depth + 1, is_not_body);
15✔
322
                    if (ev.nodeType() != NodeType::Number)
15✔
323
                        return node;
×
324
                    v += ev.number();
15✔
325
                }
15✔
326
                return Node(v);
6✔
327
            }
6✔
328
            else if (name == "-" && is_not_body)
9,690✔
329
            {
330
                checkMacroArgCountGe(node, 2, "-", "operator");
60✔
331
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
60✔
332
                if (one.nodeType() != NodeType::Number)
60✔
333
                    return node;
×
334

335
                double v = one.number();
60✔
336
                for (auto& child : node.list() | std::ranges::views::drop(2))
122✔
337
                {
338
                    Node ev = evaluate(child, depth + 1, is_not_body);
62✔
339
                    if (ev.nodeType() != NodeType::Number)
62✔
340
                        return node;
×
341
                    v -= ev.number();
62✔
342
                }
62✔
343
                return Node(v);
60✔
344
            }
60✔
345
            else if (name == "*" && is_not_body)
9,630✔
346
            {
347
                checkMacroArgCountGe(node, 2, "*", "operator");
1✔
348
                double v = 1.0;
1✔
349
                for (auto& child : node.list() | std::ranges::views::drop(1))
4✔
350
                {
351
                    Node ev = evaluate(child, depth + 1, is_not_body);
3✔
352
                    if (ev.nodeType() != NodeType::Number)
3✔
353
                        return node;
×
354
                    v *= ev.number();
3✔
355
                }
3✔
356
                return Node(v);
1✔
357
            }
1✔
358
            else if (name == "/" && is_not_body)
9,629✔
359
            {
360
                checkMacroArgCountGe(node, 2, "/", "operator");
2✔
361
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
2✔
362
                if (one.nodeType() != NodeType::Number)
2✔
363
                    return node;
1✔
364

365
                double v = one.number();
1✔
366
                for (auto& child : node.list() | std::ranges::views::drop(2))
3✔
367
                {
368
                    Node ev = evaluate(child, depth + 1, is_not_body);
2✔
369
                    if (ev.nodeType() != NodeType::Number)
2✔
370
                        return node;
×
371
                    v /= ev.number();
2✔
372
                }
2✔
373
                return Node(v);
1✔
374
            }
2✔
375
            else if (name == "not" && is_not_body)
9,627✔
376
            {
377
                checkMacroArgCountEq(node, 1, "not", /* is_expansion= */ false, "condition");
2✔
378
                return (!isTruthy(evaluate(node.list()[1], depth + 1, is_not_body))) ? getTrueNode() : getFalseNode();
2✔
379
            }
380
            else if (name == Language::And && is_not_body)
9,625✔
381
            {
382
                if (node.list().size() < 3)
8✔
383
                    throwMacroProcessingError(fmt::format("Interpreting a `{}' chain with {} arguments, expected at least 2.", Language::And, argcount), node);
1✔
384

385
                for (std::size_t i = 1, end = node.list().size(); i < end; ++i)
19✔
386
                {
387
                    if (!isTruthy(evaluate(node.list()[i], depth + 1, is_not_body)))
12✔
388
                        return getFalseNode();
2✔
389
                }
10✔
390
                return getTrueNode();
5✔
391
            }
392
            else if (name == Language::Or && is_not_body)
9,617✔
393
            {
394
                if (node.list().size() < 3)
5✔
395
                    throwMacroProcessingError(fmt::format("Interpreting an `{}' chain with {} arguments, expected at least 2.", Language::Or, argcount), node);
1✔
396

397
                for (std::size_t i = 1, end = node.list().size(); i < end; ++i)
12✔
398
                {
399
                    if (isTruthy(evaluate(node.list()[i], depth + 1, is_not_body)))
8✔
400
                        return getTrueNode();
2✔
401
                }
6✔
402
                return getFalseNode();
2✔
403
            }
404
            else if (name == "len")
9,612✔
405
            {
406
                checkMacroArgCountEq(node, 1, "len", true);
89✔
407

408
                if (Node& lst = node.list()[1]; lst.nodeType() == NodeType::List)  // only apply len at compile time if we can
169✔
409
                {
410
                    if (isConstEval(lst))
81✔
411
                    {
412
                        if (!lst.list().empty() && lst.list()[0] == getListNode())
61✔
413
                            node.updateValueAndType(Node(static_cast<long>(lst.list().size()) - 1));
58✔
414
                        else
415
                            node.updateValueAndType(Node(static_cast<long>(lst.list().size())));
3✔
416
                    }
61✔
417
                }
81✔
418
            }
88✔
419
            else if (name == "empty?")
9,523✔
420
            {
421
                checkMacroArgCountEq(node, 1, "empty?", true);
27✔
422

423
                if (Node& lst = node.list()[1]; lst.nodeType() == NodeType::List && isConstEval(lst))
52✔
424
                {
425
                    // only apply len at compile time if we can
426
                    if (!lst.list().empty() && lst.list()[0] == getListNode())
11✔
427
                        node.updateValueAndType(lst.list().size() - 1 == 0 ? getTrueNode() : getFalseNode());
9✔
428
                    else
429
                        node.updateValueAndType(lst.list().empty() ? getTrueNode() : getFalseNode());
2✔
430
                }
11✔
431
                else if (lst == getNilNode())
15✔
432
                    node.updateValueAndType(getTrueNode());
3✔
433
            }
26✔
434
            else if (name == "@")
9,496✔
435
            {
436
                checkMacroArgCountEq(node, 2, "@");
73✔
437

438
                Node sublist = evaluate(node.list()[1], depth + 1, is_not_body);
73✔
439
                const Node idx = evaluate(node.list()[2], depth + 1, is_not_body);
73✔
440

441
                if (sublist.nodeType() == NodeType::List && idx.nodeType() == NodeType::Number)
73✔
442
                {
443
                    const std::size_t size = sublist.list().size();
37✔
444
                    std::size_t real_size = size;
37✔
445
                    long num_idx = static_cast<long>(idx.number());
37✔
446

447
                    // if the first node is the function call to "list", don't count it
448
                    if (size > 0 && sublist.list()[0] == getListNode())
37✔
449
                    {
450
                        real_size--;
31✔
451
                        if (num_idx >= 0)
31✔
452
                            ++num_idx;
14✔
453
                    }
31✔
454

455
                    Node output;
37✔
456
                    if (num_idx >= 0 && std::cmp_less(num_idx, size))
37✔
457
                        output = sublist.list()[static_cast<std::size_t>(num_idx)];
20✔
458
                    else if (const auto c = static_cast<long>(size) + num_idx; num_idx < 0 && std::cmp_less(c, size) && c >= 0)
34✔
459
                        output = sublist.list()[static_cast<std::size_t>(c)];
16✔
460
                    else
461
                        throwMacroProcessingError(fmt::format("Index ({}) out of range (list size: {})", num_idx, real_size), node);
1✔
462

463
                    output.setPositionFrom(node);
36✔
464
                    return output;
36✔
465
                }
37✔
466
            }
73✔
467
            else if (name == "head")
9,423✔
468
            {
469
                checkMacroArgCountEq(node, 1, "head", true);
22✔
470

471
                if (node.list()[1].nodeType() == NodeType::List)
21✔
472
                {
473
                    Node& sublist = node.list()[1];
13✔
474
                    if (!sublist.constList().empty() && sublist.constList()[0] == getListNode())
13✔
475
                    {
476
                        if (sublist.constList().size() > 1)
9✔
477
                        {
478
                            const Node sublistCopy = sublist.constList()[1];
6✔
479
                            node.updateValueAndType(sublistCopy);
6✔
480
                        }
6✔
481
                        else
482
                            node.updateValueAndType(getNilNode());
3✔
483
                    }
9✔
484
                    else if (!sublist.list().empty())
4✔
485
                        node.updateValueAndType(sublist.constList()[0]);
4✔
486
                    else
487
                        node.updateValueAndType(getNilNode());
×
488
                }
13✔
489
            }
21✔
490
            else if (name == "tail")
9,401✔
491
            {
492
                checkMacroArgCountEq(node, 1, "tail", true);
21✔
493

494
                if (node.list()[1].nodeType() == NodeType::List)
20✔
495
                {
496
                    Node sublist = node.list()[1];
13✔
497
                    if (!sublist.list().empty() && sublist.list()[0] == getListNode())
13✔
498
                    {
499
                        if (sublist.list().size() > 1)
11✔
500
                        {
501
                            sublist.list().erase(sublist.constList().begin() + 1);
8✔
502
                            node.updateValueAndType(sublist);
8✔
503
                        }
8✔
504
                        else
505
                        {
506
                            node.updateValueAndType(Node(NodeType::List));
3✔
507
                            node.push_back(getListNode());
3✔
508
                        }
509
                    }
11✔
510
                    else if (!sublist.list().empty())
2✔
511
                    {
512
                        sublist.list().erase(sublist.constList().begin());
2✔
513
                        sublist.list().insert(sublist.list().begin(), getListNode());
2✔
514
                        node.updateValueAndType(sublist);
2✔
515
                    }
2✔
516
                    else
517
                    {
518
                        node.updateValueAndType(Node(NodeType::List));
×
519
                        node.push_back(getListNode());
×
520
                    }
521
                }
13✔
522
            }
20✔
523
            else if (name == Language::Symcat)
9,380✔
524
            {
525
                if (node.list().size() <= 2)
95✔
526
                    throwMacroProcessingError(fmt::format("When expanding `{}', expected at least 2 arguments, got {} arguments", Language::Symcat, argcount), node);
1✔
527
                if (node.list()[1].nodeType() != NodeType::Symbol)
94✔
528
                    throwMacroProcessingError(
2✔
529
                        fmt::format(
2✔
530
                            "When expanding `{}', expected the first argument to be a Symbol, got a {}: {}",
1✔
531
                            Language::Symcat,
532
                            typeToString(node.list()[1]),
1✔
533
                            node.list()[1].repr()),
1✔
534
                        node.list()[1]);
1✔
535

536
                std::string sym = node.list()[1].string();
93✔
537

538
                for (std::size_t i = 2, end = node.list().size(); i < end; ++i)
186✔
539
                {
540
                    const Node ev = evaluate(node.list()[i], depth + 1, /* is_not_body */ true);
93✔
541

542
                    switch (ev.nodeType())
92✔
543
                    {
22✔
544
                        case NodeType::Number:
545
                            // we don't want '.' in identifiers
546
                            sym += std::to_string(static_cast<long int>(ev.number()));
22✔
547
                            break;
91✔
548

549
                        case NodeType::String:
550
                        case NodeType::Symbol:
551
                            sym += ev.string();
69✔
552
                            break;
70✔
553

554
                        default:
555
                            throwMacroProcessingError(
2✔
556
                                fmt::format(
2✔
557
                                    "When expanding `{}', expected either a Number, String or Symbol, got a {}: {}",
1✔
558
                                    Language::Symcat,
559
                                    typeToString(ev),
1✔
560
                                    ev.repr()),
1✔
561
                                ev);
562
                    }
91✔
563
                }
93✔
564

565
                node.setNodeType(NodeType::Symbol);
91✔
566
                node.setString(sym);
91✔
567
            }
93✔
568
            else if (name == Language::Argcount)
9,285✔
569
            {
570
                checkMacroArgCountEq(node, 1, Language::Argcount.data(), true);
48✔
571

572
                const Node sym = node.constList()[1];
47✔
573
                if (sym.nodeType() == NodeType::Symbol)
47✔
574
                {
575
                    if (const auto maybe_func = lookupDefinedFunction(sym.string()); maybe_func.has_value())
86✔
576
                        node.updateValueAndType(Node(static_cast<long>(maybe_func->constList().size())));
42✔
577
                    else
578
                        throwMacroProcessingError(fmt::format("When expanding `{}', expected a known function name, got unbound variable {}", Language::Argcount, sym.string()), sym);
1✔
579
                }
42✔
580
                else if (sym.nodeType() == NodeType::List && sym.constList().size() == 3 && sym.constList()[0].nodeType() == NodeType::Keyword && sym.constList()[0].keyword() == Keyword::Fun)
4✔
581
                    node.updateValueAndType(Node(static_cast<long>(sym.constList()[1].constList().size())));
3✔
582
                else
583
                    throwMacroProcessingError(fmt::format("When trying to apply `{}', got a {} instead of a Symbol or Function", Language::Argcount, typeToString(sym)), sym);
1✔
584
            }
47✔
585
            else if (name == Language::Repr)
9,237✔
586
            {
587
                checkMacroArgCountEq(node, 1, Language::Repr.data(), true);
1,530✔
588

589
                const Node arg = node.constList()[1];
1,529✔
590
                node.updateValueAndType(Node(NodeType::String, arg.repr()));
1,529✔
591
            }
1,529✔
592
            else if (name == Language::AsIs)
7,707✔
593
            {
594
                if (node.list().size() != 2)
2,936✔
595
                    throwMacroProcessingError(fmt::format("When expanding `{}', expected one argument, got {} arguments", Language::AsIs, argcount), node);
1✔
596
                return node.constList()[1];
2,935✔
597
            }
598
            else if (name == Language::Undef)
4,771✔
599
            {
600
                if (node.list().size() != 2)
13✔
601
                    throwMacroProcessingError(fmt::format("When expanding `{}', expected one argument, got {} arguments", Language::Undef, argcount), node);
1✔
602

603
                const Node sym = node.constList()[1];
12✔
604
                if (sym.nodeType() == NodeType::Symbol)
12✔
605
                {
606
                    deleteNearestMacro(sym.string());
11✔
607
                    node.setNodeType(NodeType::Unused);
11✔
608
                    return node;
11✔
609
                }
610

611
                throwMacroProcessingError(
2✔
612
                    fmt::format(
2✔
613
                        "When expanding `{}', got a {}. Can not un-define a macro without a valid name",
1✔
614
                        Language::Undef, typeToString(sym)),
1✔
615
                    sym);
616
            }
12✔
617
            else if (name == Language::Type)
4,758✔
618
            {
619
                if (node.list().size() != 2)
17✔
620
                    throwMacroProcessingError(fmt::format("When expanding `{}', expected one argument, got {} arguments", Language::Type, argcount), node);
1✔
621

622
                const Node arg = node.constList()[1];
16✔
623
                node.updateValueAndType(Node(NodeType::String, typeToString(arg)));
16✔
624
            }
16✔
625
        }
10,581✔
626

627
        if (node.nodeType() == NodeType::List && !node.constList().empty())
16,093✔
628
        {
629
            for (auto& child : node.list())
33,029✔
630
                child.updateValueAndType(evaluate(child, depth + 1, is_not_body));
24,567✔
631
        }
8,462✔
632

633
        if (node.nodeType() == NodeType::Spread)
16,093✔
634
            throwMacroProcessingError(fmt::format("Found an unevaluated spread: `{}'", node.string()), node);
1✔
635

636
        return node;
16,092✔
637
    }
26,698✔
638

639
    bool MacroProcessor::isTruthy(const Node& node)
165✔
640
    {
165✔
641
        if (node.nodeType() == NodeType::Symbol)
165✔
642
        {
643
            if (node.string() == "true")
153✔
644
                return true;
87✔
645
            if (node.string() == "false" || node.string() == "nil")
66✔
646
                return false;
66✔
647
        }
×
648
        else if ((node.nodeType() == NodeType::Number && node.number() != 0.0) || (node.nodeType() == NodeType::String && !node.string().empty()))
12✔
649
            return true;
2✔
650
        else if (node.nodeType() == NodeType::Spread)
10✔
651
            throwMacroProcessingError("Can not determine the truth value of a spread symbol", node);
×
652
        return false;
10✔
653
    }
165✔
654

655
    std::optional<Node> MacroProcessor::lookupDefinedFunction(const std::string& name) const
43✔
656
    {
43✔
657
        if (m_defined_functions.contains(name))
43✔
658
            return m_defined_functions.at(name);
42✔
659
        return std::nullopt;
1✔
660
    }
43✔
661

662
    const Node* MacroProcessor::findNearestMacro(const std::string& name) const
238,830✔
663
    {
238,830✔
664
        if (m_macros.empty())
238,830✔
665
            return nullptr;
106,793✔
666

667
        for (const auto& m_macro : std::ranges::reverse_view(m_macros))
288,479✔
668
        {
669
            if (const auto res = m_macro.has(name); res != nullptr)
156,442✔
670
                return res;
5,800✔
671
        }
156,442✔
672
        return nullptr;
126,237✔
673
    }
238,830✔
674

675
    void MacroProcessor::deleteNearestMacro(const std::string& name)
11✔
676
    {
11✔
677
        if (m_macros.empty())
11✔
678
            return;
×
679

680
        for (auto& m_macro : std::ranges::reverse_view(m_macros))
27✔
681
        {
682
            if (m_macro.remove(name))
16✔
683
            {
684
                // stop right here because we found one matching macro
685
                return;
1✔
686
            }
687
        }
16✔
688
    }
11✔
689

690
    bool MacroProcessor::isBeginNode(const Node& node)
508,189✔
691
    {
508,189✔
692
        return node.nodeType() == NodeType::List &&
631,968✔
693
            !node.constList().empty() &&
123,779✔
694
            node.constList()[0].nodeType() == NodeType::Keyword &&
165,757✔
695
            node.constList()[0].keyword() == Keyword::Begin;
42,312✔
696
    }
697

698
    void MacroProcessor::removeBegin(Node& node, const std::size_t i)
1,037✔
699
    {
1,037✔
700
        if (node.isListLike() && node.list()[i].nodeType() == NodeType::List && !node.list()[i].list().empty())
1,037✔
701
        {
702
            Node lst = node.constList()[i];
1,037✔
703
            Node first = lst.constList()[0];
1,037✔
704

705
            if (first.nodeType() == NodeType::Keyword && first.keyword() == Keyword::Begin)
1,037✔
706
            {
707
                const std::size_t previous = i;
1,037✔
708

709
                for (std::size_t block_idx = 1, end = lst.constList().size(); block_idx < end; ++block_idx)
2,675✔
710
                    node.list().insert(
3,276✔
711
                        node.constList().begin() + static_cast<std::vector<Node>::difference_type>(i + block_idx),
1,638✔
712
                        lst.list()[block_idx]);
1,638✔
713

714
                node.list().erase(node.constList().begin() + static_cast<std::vector<Node>::difference_type>(previous));
1,037✔
715
            }
1,037✔
716
        }
1,037✔
717
    }
1,037✔
718

719
    bool MacroProcessor::isConstEval(const Node& node) const
399✔
720
    {
399✔
721
        switch (node.nodeType())
399✔
722
        {
185✔
723
            case NodeType::Symbol:
724
            {
725
                const auto it = std::ranges::find(Language::operators, node.string());
185✔
726
                const auto it2 = std::ranges::find_if(Builtins::builtins,
185✔
727
                                                      [&node](const std::pair<std::string, Value>& element) -> bool {
10,504✔
728
                                                          return node.string() == element.first;
10,319✔
729
                                                      });
730

731
                return it != Language::operators.end() ||
368✔
732
                    it2 != Builtins::builtins.end() ||
183✔
733
                    findNearestMacro(node.string()) != nullptr ||
151✔
734
                    node.string() == "list" ||
161✔
735
                    node.string() == "nil";
31✔
736
            }
321✔
737

738
            case NodeType::List:
739
                return std::ranges::all_of(node.constList(), [this](const Node& child) {
432✔
740
                    return isConstEval(child);
296✔
741
                });
742

743
            case NodeType::MutArg:
744
            case NodeType::RefArg:
745
            case NodeType::Capture:
746
            case NodeType::Field:
747
                return false;
78✔
748

749
            case NodeType::Keyword:
750
            case NodeType::String:
751
            case NodeType::Number:
752
            case NodeType::Macro:
753
            case NodeType::Spread:
754
            case NodeType::Namespace:
755
            case NodeType::Unused:
756
                return true;
78✔
757
        }
×
758

759
        return false;
×
760
    }
399✔
761

762
    void MacroProcessor::throwMacroProcessingError(const std::string& message, const Node& node) const
29✔
763
    {
29✔
764
        const std::optional<CodeErrorContext> maybe_context = [this]() -> std::optional<CodeErrorContext> {
58✔
765
            if (!m_macros_being_applied.empty())
29✔
766
            {
767
                const Node& origin = m_macros_being_applied.front();
17✔
768
                return CodeErrorContext(
34✔
769
                    origin.filename(),
17✔
770
                    origin.position(),
17✔
771
                    /* from_macro_expansion= */ true);
772
            }
17✔
773
            return std::nullopt;
12✔
774
        }();
29✔
775

776
        throw CodeError(message, CodeErrorContext(node.filename(), node.position()), maybe_context);
29✔
777
    }
58✔
778
}
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