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

ArkScript-lang / Ark / 23402495745

22 Mar 2026 11:53AM UTC coverage: 93.682% (+0.02%) from 93.659%
23402495745

Pull #662

github

web-flow
Merge 5c604e442 into a172dc4e7
Pull Request #662: Various fixes for 4.5.0

44 of 47 new or added lines in 3 files covered. (93.62%)

1 existing line in 1 file now uncovered.

9638 of 10288 relevant lines covered (93.68%)

272380.46 hits per line

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

96.93
/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 :
918✔
21
        Pass("MacroProcessor", debug), m_genned_sym(0)
459✔
22
    {
918✔
23
        // create executors pipeline
24
        m_conditional_executor = std::make_shared<ConditionalExecutor>(this);
459✔
25
        m_executors.emplace_back(std::make_shared<SymbolExecutor>(this));
459✔
26
        m_executors.emplace_back(m_conditional_executor);
459✔
27
        m_executors.emplace_back(std::make_shared<FunctionExecutor>(this));
459✔
28
    }
459✔
29

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

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

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

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

50
    void MacroProcessor::handleMacroNode(Node& node, const unsigned depth)
264✔
51
    {
264✔
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");
264✔
55

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

58
        // (macro name value)
59
        if (node.constList().size() == 2)
264✔
60
        {
61
            assert(first_node.nodeType() == NodeType::Symbol && "Can not define a macro without a symbol");
71✔
62
            // processNode is needed here to correctly expand macros when we have something like
63
            //   (macro arg_bloc (__suffix-dup arg length))                                    /
64
            //   (macro all_args (__replace_placeholders [arg_bloc] ...args))  <--------------*
65
            // otherwise, 'arg_bloc' isn't expanded here, and we get in trouble
66
            processNode(node.list()[1], depth + 1);
45✔
67
            applyMacro(node.list()[1], depth + 1);
45✔
68
            node.list()[1] = evaluate(node.list()[1], depth + 1, true);
45✔
69
            m_macros.back().add(first_node.string(), node);
45✔
70
        }
45✔
71
        // (macro name (args) body)
72
        else if (node.constList().size() == 3 && first_node.nodeType() == NodeType::Symbol)
193✔
73
        {
74
            assert(node.constList()[1].nodeType() == NodeType::List && "Invalid macro argument's list");
69✔
75
            m_macros.back().add(first_node.string(), node);
69✔
76
        }
69✔
77
        // in case we had a conditional, we need to evaluate and expand it
78
        else if (m_conditional_executor->canHandle(node))
124✔
79
            m_conditional_executor->applyMacro(node, depth + 1);
121✔
80
    }
232✔
81

82
    // todo find a better way to do this
83
    void MacroProcessor::registerFuncDef(const Node& node)
98,039✔
84
    {
98,039✔
85
        if (node.nodeType() == NodeType::List && node.constList().size() == 3 && node.constList()[0].nodeType() == NodeType::Keyword)
98,039✔
86
        {
87
            const Keyword kw = node.constList()[0].keyword();
28,802✔
88
            // checking for function definition, which can occur only inside an assignment node
89
            if (kw != Keyword::Let && kw != Keyword::Mut && kw != Keyword::Set)
28,802✔
90
                return;
8,424✔
91

92
            const Node inner = node.constList()[2];
20,378✔
93
            if (inner.nodeType() != NodeType::List)
20,378✔
94
                return;
7,055✔
95

96
            if (!inner.constList().empty() && inner.constList()[0].nodeType() == NodeType::Keyword && inner.constList()[0].keyword() == Keyword::Fun)
13,323✔
97
            {
98
                const Node symbol = node.constList()[1];
3,447✔
99
                if (symbol.nodeType() == NodeType::Symbol)
3,447✔
100
                    m_defined_functions.emplace(symbol.string(), inner.constList()[1]);
3,446✔
101
                else
102
                    throwMacroProcessingError(fmt::format("Can not use a {} to define a variable", typeToString(symbol)), symbol);
1✔
103
            }
3,447✔
104
        }
28,802✔
105
    }
98,039✔
106

107
    void MacroProcessor::processNode(Node& node, unsigned depth, const bool is_processing_namespace)
369,497✔
108
    {
369,497✔
109
        if (depth >= MaxMacroProcessingDepth)
369,497✔
110
            throwMacroProcessingError(
1✔
111
                fmt::format(
2✔
112
                    "Max recursion depth reached ({}). You most likely have a badly defined recursive macro calling itself without a proper exit condition",
1✔
113
                    MaxMacroProcessingDepth),
114
                node);
1✔
115

116
        if (node.nodeType() == NodeType::List)
369,496✔
117
        {
118
            bool has_created = false;
98,613✔
119
            std::size_t i = 0;
98,613✔
120
            while (i < node.list().size())
468,005✔
121
            {
122
                const std::size_t pos = i;
369,392✔
123
                Node& child = node.list()[pos];
369,392✔
124
                const bool had_begin = isBeginNode(child);
369,392✔
125
                bool added_begin = false;
369,392✔
126

127
                if (child.nodeType() == NodeType::Macro)
369,392✔
128
                {
129
                    // create a scope only if needed
130
                    if ((!m_macros.empty() && !m_macros.back().empty() && m_macros.back().depth() < depth && !is_processing_namespace) ||
347✔
131
                        (!has_created && !is_processing_namespace) ||
138✔
132
                        (m_macros.empty() && is_processing_namespace))
59✔
133
                    {
134
                        has_created = true;
150✔
135
                        m_macros.emplace_back(depth);
150✔
136
                    }
150✔
137

138
                    handleMacroNode(child, depth);
206✔
139
                    if (child.nodeType() != NodeType::Macro)
206✔
140
                        evaluate(child, depth);
92✔
141
                    added_begin = isBeginNode(child) && !had_begin;
206✔
142
                }
206✔
143
                else  // running on non-macros
144
                {
145
                    applyMacro(child, 0);
369,186✔
146
                    added_begin = isBeginNode(child) && !had_begin;
369,186✔
147

148
                    if (child.nodeType() == NodeType::Unused)
369,186✔
149
                        node.list().erase(node.constList().begin() + static_cast<std::vector<Node>::difference_type>(pos));
9✔
150
                    else if (!added_begin)
369,177✔
151
                        // Go forward only if it isn't a macro, because we delete macros
152
                        // while running on the AST. Also, applying a macro can result in
153
                        // nodes being marked unused, and delete them immediately. When
154
                        // that happens, we can't increment i, otherwise we delete a node,
155
                        // advance, resulting in a node being skipped!
156
                        ++i;
367,653✔
157

158
                    // process subnodes if any
159
                    if (node.nodeType() == NodeType::List && pos < node.constList().size())
369,186✔
160
                    {
161
                        processNode(child, depth + 1);
368,910✔
162
                        // needed if we created a function node from a macro
163
                        registerFuncDef(child);
368,910✔
164
                    }
368,910✔
165
                }
166

167
                if (pos < node.constList().size())
369,392✔
168
                {
169
                    // if we now have a surrounding (begin ...) and didn't have one before, remove it
170
                    if (added_begin)
369,115✔
171
                        removeBegin(node, pos);
1,532✔
172
                    // if there is an unused node or a leftover macro need, we need to get rid of it in the final ast
173
                    else if (child.nodeType() == NodeType::Macro || child.nodeType() == NodeType::Unused)
367,583✔
174
                        node.list().erase(node.constList().begin() + static_cast<std::vector<Node>::difference_type>(pos));
137✔
175
                }
369,115✔
176
            }
369,392✔
177

178
            // delete a scope only if needed
179
            if (!m_macros.empty() && m_macros.back().depth() == depth && !is_processing_namespace)
98,613✔
180
                m_macros.pop_back();
118✔
181
        }
98,613✔
182
        else if (node.nodeType() == NodeType::Namespace)
270,883✔
183
        {
184
            Node& namespace_ast = *node.arkNamespace().ast;
144✔
185
            // We have to use depth - 1 because it was incremented previously, as a namespace node
186
            // must be in a list node. Then depth - 1 is safe as depth is at least 1.
187
            // Using a decreased value of depth ensures that macros are stored in the correct scope,
188
            // and not deleted when the namespace traversal ends.
189
            processNode(namespace_ast, depth - 1, /* is_processing_namespace= */ true);
144✔
190
        }
144✔
191
    }
369,497✔
192

193
    bool MacroProcessor::applyMacro(Node& node, const unsigned depth)
372,378✔
194
    {
372,378✔
195
        if (depth > MaxMacroProcessingDepth)
372,378✔
196
            throwMacroProcessingError(
1✔
197
                fmt::format(
2✔
198
                    "Max macro processing depth reached ({}). You may have a macro trying to evaluate itself, try splitting your code in multiple nodes.",
1✔
199
                    MaxMacroProcessingDepth),
200
                node);
1✔
201

202
        for (const auto& executor : m_executors)
1,488,875✔
203
        {
204
            if (executor->canHandle(node))
1,116,716✔
205
            {
206
                m_macros_being_applied.push_back(executor->macroNode(node));
174,984✔
207
                const bool applied = executor->applyMacro(node, depth);
174,984✔
208
                m_macros_being_applied.pop_back();
174,766✔
209

210
                if (applied)
174,766✔
211
                    return true;
1,775✔
212
            }
174,766✔
213
        }
1,116,498✔
214
        return false;
370,493✔
215
    }
372,160✔
216

217
    void MacroProcessor::checkMacroArgCountEq(const Node& node, std::size_t expected, const std::string& name, const bool is_expansion, const std::string& kind) const
2,514✔
218
    {
2,514✔
219
        const std::size_t argcount = node.constList().size();
2,514✔
220
        if (argcount != expected + 1)
2,514✔
221
        {
222
            if (is_expansion)
8✔
223
                throwMacroProcessingError(
14✔
224
                    fmt::format(
14✔
225
                        "When expanding `{}' inside a macro, got {} argument{}, expected {}",
7✔
226
                        name,
7✔
227
                        argcount - 1,
7✔
228
                        argcount > 2 ? "s" : "",
7✔
229
                        expected),
230
                    node);
7✔
231
            else
232
                throwMacroProcessingError(
2✔
233
                    fmt::format(
3✔
234
                        "Interpreting a `{}'{} with {} argument{}, expected {}.",
1✔
235
                        name,
1✔
236
                        kind.empty() ? kind : " " + kind,
1✔
237
                        argcount - 1,
1✔
238
                        argcount > 2 ? "s" : "",
1✔
239
                        expected),
240
                    node);
1✔
241
        }
242
    }
2,515✔
243

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

259
    Node MacroProcessor::evaluate(Node& node, const unsigned depth, const bool is_not_body)
52,490✔
260
    {
52,490✔
261
        if (node.nodeType() == NodeType::Symbol)
52,490✔
262
        {
263
            const Node* macro = findNearestMacro(node.string());
17,871✔
264
            if (macro != nullptr && macro->constList().size() == 2)
17,871✔
265
                return macro->constList()[1];
146✔
266
            return node;
17,725✔
267
        }
17,871✔
268
        if (node.nodeType() == NodeType::List && !node.constList().empty() && node.list()[0].nodeType() == NodeType::Symbol)
34,619✔
269
        {
270
            const std::string& name = node.list()[0].string();
13,799✔
271
            const std::size_t argcount = node.list().size() - 1;
13,799✔
272

273
            if (const Node* macro = findNearestMacro(name); macro != nullptr)
27,598✔
274
            {
275
                applyMacro(node.list()[0], depth + 1);
1,334✔
276
                if (node.list()[0].nodeType() == NodeType::Unused)
1,334✔
277
                    node.list().erase(node.constList().begin());
×
278
            }
1,334✔
279
            else if (name == "=" && is_not_body)
12,465✔
280
            {
281
                checkMacroArgCountEq(node, 2, "=", /* is_expansion= */ false, "condition");
43✔
282
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
42✔
283
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
42✔
284
                return (one == two) ? getTrueNode() : getFalseNode();
42✔
285
            }
42✔
286
            else if (name == "!=" && is_not_body)
12,422✔
287
            {
288
                checkMacroArgCountEq(node, 2, "!=", /* is_expansion= */ false, "condition");
1✔
289
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
1✔
290
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
1✔
291
                return (one != two) ? getTrueNode() : getFalseNode();
1✔
292
            }
1✔
293
            else if (name == "<" && is_not_body)
12,421✔
294
            {
295
                checkMacroArgCountEq(node, 2, "<", /* is_expansion= */ false, "condition");
1✔
296
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
1✔
297
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
1✔
298
                return (one < two) ? getTrueNode() : getFalseNode();
1✔
299
            }
1✔
300
            else if (name == ">" && is_not_body)
12,420✔
301
            {
302
                checkMacroArgCountEq(node, 2, ">", /* is_expansion= */ false, "condition");
41✔
303
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
41✔
304
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
41✔
305
                return !(one < two) && (one != two) ? getTrueNode() : getFalseNode();
41✔
306
            }
41✔
307
            else if (name == "<=" && is_not_body)
12,379✔
308
            {
309
                checkMacroArgCountEq(node, 2, "<=", /* is_expansion= */ false, "condition");
1✔
310
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
1✔
311
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
1✔
312
                return one < two || one == two ? getTrueNode() : getFalseNode();
1✔
313
            }
1✔
314
            else if (name == ">=" && is_not_body)
12,378✔
315
            {
316
                checkMacroArgCountEq(node, 2, ">=", /* is_expansion= */ false, "condition");
10✔
317
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
10✔
318
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
10✔
319
                return !(one < two) ? getTrueNode() : getFalseNode();
10✔
320
            }
10✔
321
            else if (name == "+" && is_not_body)
12,368✔
322
            {
323
                checkMacroArgCountGe(node, 2, "+", "operator");
9✔
324
                double v = 0.0;
8✔
325
                for (auto& child : node.list() | std::ranges::views::drop(1))
27✔
326
                {
327
                    Node ev = evaluate(child, depth + 1, is_not_body);
19✔
328
                    if (ev.nodeType() != NodeType::Number)
19✔
329
                        return node;
×
330
                    v += ev.number();
19✔
331
                }
19✔
332
                return Node(v);
8✔
333
            }
8✔
334
            else if (name == "-" && is_not_body)
12,359✔
335
            {
336
                checkMacroArgCountGe(node, 2, "-", "operator");
64✔
337
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
64✔
338
                if (one.nodeType() != NodeType::Number)
64✔
339
                    return node;
×
340

341
                double v = one.number();
64✔
342
                for (auto& child : node.list() | std::ranges::views::drop(2))
130✔
343
                {
344
                    Node ev = evaluate(child, depth + 1, is_not_body);
66✔
345
                    if (ev.nodeType() != NodeType::Number)
66✔
346
                        return node;
1✔
347
                    v -= ev.number();
65✔
348
                }
66✔
349
                return Node(v);
63✔
350
            }
64✔
351
            else if (name == "*" && is_not_body)
12,295✔
352
            {
353
                checkMacroArgCountGe(node, 2, "*", "operator");
1✔
354
                double v = 1.0;
1✔
355
                for (auto& child : node.list() | std::ranges::views::drop(1))
4✔
356
                {
357
                    Node ev = evaluate(child, depth + 1, is_not_body);
3✔
358
                    if (ev.nodeType() != NodeType::Number)
3✔
359
                        return node;
×
360
                    v *= ev.number();
3✔
361
                }
3✔
362
                return Node(v);
1✔
363
            }
1✔
364
            else if (name == "/" && is_not_body)
12,294✔
365
            {
366
                checkMacroArgCountGe(node, 2, "/", "operator");
2✔
367
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
2✔
368
                if (one.nodeType() != NodeType::Number)
2✔
369
                    return node;
1✔
370

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

391
                for (std::size_t i = 1, end = node.list().size(); i < end; ++i)
11✔
392
                {
393
                    if (!isTruthy(evaluate(node.list()[i], depth + 1, is_not_body)))
7✔
394
                        return getFalseNode();
1✔
395
                }
6✔
396
                return getTrueNode();
3✔
397
            }
398
            else if (name == Language::Or && is_not_body)
12,286✔
399
            {
400
                if (node.list().size() < 3)
3✔
401
                    throwMacroProcessingError(fmt::format("Interpreting an `{}' chain with {} arguments, expected at least 2.", Language::Or, argcount), node);
1✔
402

403
                for (std::size_t i = 1, end = node.list().size(); i < end; ++i)
6✔
404
                {
405
                    if (isTruthy(evaluate(node.list()[i], depth + 1, is_not_body)))
4✔
406
                        return getTrueNode();
1✔
407
                }
3✔
408
                return getFalseNode();
1✔
409
            }
410
            else if (name == "$len")
12,283✔
411
            {
412
                checkMacroArgCountEq(node, 1, "$len", true);
56✔
413
                Node& lst = node.list()[1];
55✔
414
                checkMacroTypeError("$len", "node", NodeType::List, lst);
55✔
415

416
                if (!lst.list().empty() && lst.list()[0] == getListNode())
54✔
417
                    node.updateValueAndType(Node(static_cast<long>(lst.list().size()) - 1));
53✔
418
                else
419
                    node.updateValueAndType(Node(static_cast<long>(lst.list().size())));
1✔
420
            }
55✔
421
            else if (name == "$empty?")
12,227✔
422
            {
423
                checkMacroArgCountEq(node, 1, "$empty?", true);
22✔
424

425
                if (Node& lst = node.list()[1]; lst.nodeType() == NodeType::List)
42✔
426
                {
427
                    // only apply len at compile time if we can
428
                    if (!lst.list().empty() && lst.list()[0] == getListNode())
20✔
429
                        node.updateValueAndType(lst.list().size() - 1 == 0 ? getTrueNode() : getFalseNode());
19✔
430
                    else
431
                        node.updateValueAndType(lst.list().empty() ? getTrueNode() : getFalseNode());
1✔
432
                }
20✔
433
                else if (lst == getNilNode())
1✔
434
                    node.updateValueAndType(getTrueNode());
1✔
435
            }
21✔
436
            else if (name == "$at")
12,205✔
437
            {
438
                checkMacroArgCountEq(node, 2, "$at");
17✔
439

440
                Node sublist = evaluate(node.list()[1], depth + 1, is_not_body);
17✔
441
                const Node idx = evaluate(node.list()[2], depth + 1, is_not_body);
17✔
442

443
                checkMacroTypeError("$at", "list", NodeType::List, sublist);
17✔
444
                checkMacroTypeError("$at", "index", NodeType::Number, idx);
16✔
445

446
                const std::size_t size = sublist.list().size();
15✔
447
                std::size_t real_size = size;
15✔
448
                long num_idx = static_cast<long>(idx.number());
15✔
449

450
                // if the first node is the function call to "list", don't count it
451
                if (size > 0 && sublist.list()[0] == getListNode())
15✔
452
                {
453
                    real_size--;
9✔
454
                    if (num_idx >= 0)
9✔
455
                        ++num_idx;
4✔
456
                }
9✔
457

458
                Node output;
15✔
459
                if (num_idx >= 0 && std::cmp_less(num_idx, size))
15✔
460
                    output = sublist.list()[static_cast<std::size_t>(num_idx)];
10✔
461
                else if (const auto c = static_cast<long>(size) + num_idx; num_idx < 0 && std::cmp_less(c, size) && c >= 0)
10✔
462
                    output = sublist.list()[static_cast<std::size_t>(c)];
4✔
463
                else
464
                    throwMacroProcessingError(fmt::format("Index ({}) out of range (list size: {})", num_idx, real_size), node);
1✔
465

466
                output.setPositionFrom(node);
14✔
467
                return output;
14✔
468
            }
17✔
469
            else if (name == "$head")
12,188✔
470
            {
471
                checkMacroArgCountEq(node, 1, "$head", true);
12✔
472
                Node sublist = node.list()[1];
11✔
473
                checkMacroTypeError("$head", "node", NodeType::List, sublist);
11✔
474

475
                if (!sublist.constList().empty() && sublist.constList()[0] == getListNode())
10✔
476
                {
477
                    if (sublist.constList().size() > 1)
6✔
478
                    {
479
                        const Node sublistCopy = sublist.constList()[1];
5✔
480
                        node.updateValueAndType(sublistCopy);
5✔
481
                    }
5✔
482
                    else
483
                        node.updateValueAndType(getNilNode());
1✔
484
                }
6✔
485
                else if (!sublist.list().empty())
4✔
486
                    node.updateValueAndType(sublist.constList()[0]);
4✔
487
                else
488
                    node.updateValueAndType(getNilNode());
×
489
            }
11✔
490
            else if (name == "$tail")
12,176✔
491
            {
492
                checkMacroArgCountEq(node, 1, "$tail", true);
9✔
493
                Node sublist = node.list()[1];
8✔
494
                checkMacroTypeError("$tail", "node", NodeType::List, sublist);
8✔
495

496
                if (!sublist.list().empty() && sublist.list()[0] == getListNode())
7✔
497
                {
498
                    if (sublist.list().size() > 1)
6✔
499
                    {
500
                        sublist.list().erase(sublist.constList().begin() + 1);
5✔
501
                        node.updateValueAndType(sublist);
5✔
502
                    }
5✔
503
                    else
504
                    {
505
                        node.updateValueAndType(Node(NodeType::List));
1✔
506
                        node.push_back(getListNode());
1✔
507
                    }
508
                }
6✔
509
                else if (!sublist.list().empty())
1✔
510
                {
511
                    sublist.list().erase(sublist.constList().begin());
1✔
512
                    sublist.list().insert(sublist.list().begin(), getListNode());
1✔
513
                    node.updateValueAndType(sublist);
1✔
514
                }
1✔
515
                else
516
                {
517
                    node.updateValueAndType(Node(NodeType::List));
×
518
                    node.push_back(getListNode());
×
519
                }
520
            }
8✔
521
            else if (name == "$gensym")
12,167✔
522
            {
523
                checkMacroArgCountEq(node, 0, "$gensym", true);
1✔
NEW
524
                node.updateValueAndType(Node(NodeType::Symbol, fmt::format("#gensym-{}", m_genned_sym)));
×
NEW
525
                ++m_genned_sym;
×
NEW
526
            }
×
527
            else if (name == Language::Symcat)
12,166✔
528
            {
529
                if (node.list().size() <= 2)
103✔
530
                    throwMacroProcessingError(fmt::format("When expanding `{}', expected at least 2 arguments, got {} arguments", Language::Symcat, argcount), node);
1✔
531
                checkMacroTypeError(Language::Symcat.data(), "symbol", NodeType::Symbol, node.list()[1]);
102✔
532

533
                std::string sym = node.list()[1].string();
101✔
534

535
                for (std::size_t i = 2, end = node.list().size(); i < end; ++i)
202✔
536
                {
537
                    const Node ev = evaluate(node.list()[i], depth + 1, /* is_not_body */ true);
101✔
538

539
                    switch (ev.nodeType())
101✔
540
                    {
25✔
541
                        case NodeType::Number:
542
                            // we don't want '.' in identifiers
543
                            sym += std::to_string(static_cast<long int>(ev.number()));
25✔
544
                            break;
100✔
545

546
                        case NodeType::String:
547
                        case NodeType::Symbol:
548
                            sym += ev.string();
75✔
549
                            break;
76✔
550

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

562
                node.setNodeType(NodeType::Symbol);
100✔
563
                node.setString(sym);
100✔
564
            }
101✔
565
            else if (name == Language::Argcount)
12,063✔
566
            {
567
                checkMacroArgCountEq(node, 1, Language::Argcount.data(), true);
21✔
568

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

586
                const Node arg = node.constList()[1];
2,277✔
587
                node.updateValueAndType(Node(NodeType::String, arg.repr()));
2,277✔
588
            }
2,277✔
589
            else if (name == Language::AsIs)
9,764✔
590
            {
591
                if (node.list().size() != 2)
2,223✔
592
                    throwMacroProcessingError(fmt::format("When expanding `{}', expected one argument, got {} arguments", Language::AsIs, argcount), node);
1✔
593
                return node.constList()[1];
2,222✔
594
            }
595
            else if (name == Language::Undef)
7,541✔
596
            {
597
                if (node.list().size() != 2)
13✔
598
                    throwMacroProcessingError(fmt::format("When expanding `{}', expected one argument, got {} arguments", Language::Undef, argcount), node);
1✔
599

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

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

619
                const Node arg = node.constList()[1];
9✔
620
                node.updateValueAndType(Node(NodeType::String, typeToString(arg)));
9✔
621
            }
9✔
622
        }
13,800✔
623

624
        if (node.nodeType() == NodeType::List && !node.constList().empty())
32,082✔
625
        {
626
            for (auto& child : node.list())
66,676✔
627
                child.updateValueAndType(evaluate(child, depth + 1, is_not_body));
49,942✔
628
        }
16,734✔
629

630
        if (node.nodeType() == NodeType::Spread)
32,082✔
631
            throwMacroProcessingError(fmt::format("Found an unevaluated spread: `{}'", node.string()), node);
1✔
632

633
        return node;
32,081✔
634
    }
52,490✔
635

636
    bool MacroProcessor::isTruthy(const Node& node) const
136✔
637
    {
136✔
638
        if (node.nodeType() == NodeType::Symbol)
136✔
639
        {
640
            if (node.string() == "true")
135✔
641
                return true;
68✔
642
            if (node.string() == "false" || node.string() == "nil")
67✔
643
                return false;
67✔
644
        }
×
645
        else if ((node.nodeType() == NodeType::Number && node.number() != 0.0) || (node.nodeType() == NodeType::String && !node.string().empty()))
1✔
646
            return true;
1✔
647
        else if (node.nodeType() == NodeType::Spread)
×
648
            throwMacroProcessingError("Can not determine the truth value of a spread symbol", node);
×
649
        return false;
×
650
    }
136✔
651

652
    std::optional<Node> MacroProcessor::lookupDefinedFunction(const std::string& name) const
16✔
653
    {
16✔
654
        if (m_defined_functions.contains(name))
16✔
655
            return m_defined_functions.at(name);
15✔
656
        return std::nullopt;
1✔
657
    }
16✔
658

659
    const Node* MacroProcessor::findNearestMacro(const std::string& name) const
381,408✔
660
    {
381,408✔
661
        if (m_macros.empty())
381,408✔
662
            return nullptr;
151,865✔
663

664
        for (const auto& m_macro : std::ranges::reverse_view(m_macros))
487,723✔
665
        {
666
            if (const auto res = m_macro.has(name); res != nullptr)
258,180✔
667
                return res;
8,978✔
668
        }
258,180✔
669
        return nullptr;
220,565✔
670
    }
381,408✔
671

672
    void MacroProcessor::deleteNearestMacro(const std::string& name)
11✔
673
    {
11✔
674
        if (m_macros.empty())
11✔
675
            return;
×
676

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

687
    bool MacroProcessor::isBeginNode(const Node& node)
738,820✔
688
    {
738,820✔
689
        return node.nodeType() == NodeType::List &&
935,376✔
690
            !node.constList().empty() &&
196,556✔
691
            node.constList()[0].nodeType() == NodeType::Keyword &&
281,702✔
692
            node.constList()[0].keyword() == Keyword::Begin;
85,540✔
693
    }
694

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

702
            if (first.nodeType() == NodeType::Keyword && first.keyword() == Keyword::Begin)
1,532✔
703
            {
704
                const std::size_t previous = i;
1,532✔
705

706
                for (std::size_t block_idx = 1, end = lst.constList().size(); block_idx < end; ++block_idx)
4,084✔
707
                    node.list().insert(
5,104✔
708
                        node.constList().begin() + static_cast<std::vector<Node>::difference_type>(i + block_idx),
2,552✔
709
                        lst.list()[block_idx]);
2,552✔
710

711
                node.list().erase(node.constList().begin() + static_cast<std::vector<Node>::difference_type>(previous));
1,532✔
712
            }
1,532✔
713
        }
1,532✔
714
    }
1,532✔
715

716
    void MacroProcessor::throwMacroProcessingError(const std::string& message, const Node& node) const
36✔
717
    {
36✔
718
        const std::optional<CodeErrorContext> maybe_context = [this]() -> std::optional<CodeErrorContext> {
72✔
719
            if (!m_macros_being_applied.empty())
36✔
720
            {
721
                const Node& origin = m_macros_being_applied.front();
24✔
722
                return CodeErrorContext(
48✔
723
                    origin.filename(),
24✔
724
                    origin.position(),
24✔
725
                    /* from_macro_expansion= */ true);
726
            }
24✔
727
            return std::nullopt;
12✔
728
        }();
36✔
729

730
        throw CodeError(message, CodeErrorContext(node.filename(), node.position()), maybe_context);
36✔
731
    }
72✔
732

733
    void MacroProcessor::checkMacroTypeError(const std::string& macro, const std::string& arg, const NodeType expected, const Node& actual) const
209✔
734
    {
209✔
735
        if (actual.nodeType() != expected)
209✔
736
            throwMacroProcessingError(
12✔
737
                fmt::format(
18✔
738
                    "When expanding `{}', expected '{}' to be a {}, got a {}: {}",
6✔
739
                    macro,
6✔
740
                    arg,
6✔
741
                    std::string(nodeTypes[static_cast<std::size_t>(expected)]),
6✔
742
                    typeToString(actual),
6✔
743
                    actual.repr()),
6✔
744
                actual);
6✔
745
    }
209✔
746
}
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