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

ArkScript-lang / Ark / 25795566552

13 May 2026 11:14AM UTC coverage: 94.076% (-0.002%) from 94.078%
25795566552

push

github

SuperFola
fix(macro): $repr should be able to show the representation of a macro, when given one

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

8 existing lines in 2 files now uncovered.

9894 of 10517 relevant lines covered (94.08%)

719240.82 hits per line

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

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

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

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

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

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

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

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

58
        // (macro name value)
59
        if (node.constList().size() == 2)
358✔
60
        {
61
            assert(first_node.nodeType() == NodeType::Symbol && "Can not define a macro without a symbol");
79✔
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);
53✔
67
            applyMacro(node.list()[1], depth + 1);
53✔
68
            node.list()[1] = evaluate(node.list()[1], depth + 1, true);
53✔
69
            m_macros.back().add(first_node.string(), node);
53✔
70
        }
53✔
71
        // (macro name (args) body)
72
        else if (node.constList().size() == 3 && first_node.nodeType() == NodeType::Symbol)
279✔
73
        {
74
            assert(node.constList()[1].nodeType() == NodeType::List && "Invalid macro argument's list");
113✔
75
            m_macros.back().add(first_node.string(), node);
113✔
76
        }
113✔
77
        // in case we had a conditional, we need to evaluate and expand it
78
        else if (m_conditional_executor->canHandle(node))
166✔
79
            m_conditional_executor->applyMacro(node, depth + 1);
163✔
80
    }
326✔
81

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

92
            const Node inner = node.constList()[2];
27,902✔
93
            if (inner.nodeType() != NodeType::List)
27,902✔
94
                return;
9,077✔
95

96
            if (!inner.constList().empty() && inner.constList()[0].nodeType() == NodeType::Keyword && inner.constList()[0].keyword() == Keyword::Fun)
18,825✔
97
            {
98
                const Node symbol = node.constList()[1];
5,438✔
99
                if (symbol.nodeType() == NodeType::Symbol)
5,438✔
100
                    m_defined_functions.emplace(symbol.string(), inner.constList()[1]);
5,437✔
101
                else
102
                    throwMacroProcessingError(fmt::format("Can not use a {} to define a variable", typeToString(symbol)), symbol);
1✔
103
            }
5,438✔
104
        }
40,928✔
105
    }
132,614✔
106

107
    void MacroProcessor::processNode(Node& node, unsigned depth, const bool is_processing_namespace)
474,073✔
108
    {
474,073✔
109
        if (depth >= MaxMacroProcessingDepth)
474,073✔
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)
474,072✔
117
        {
118
            bool has_created = false;
133,284✔
119
            std::size_t i = 0;
133,284✔
120
            while (i < node.list().size())
607,250✔
121
            {
122
                const std::size_t pos = i;
473,966✔
123
                Node& child = node.list()[pos];
473,966✔
124
                const bool had_begin = isBeginNode(child);
473,966✔
125
                bool added_begin = false;
473,966✔
126

127
                if (child.nodeType() == NodeType::Macro)
473,966✔
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) ||
512✔
131
                        (!has_created && !is_processing_namespace) ||
206✔
132
                        (m_macros.empty() && is_processing_namespace))
103✔
133
                    {
134
                        has_created = true;
203✔
135
                        m_macros.emplace_back(depth);
203✔
136
                    }
203✔
137

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

148
                    if (child.nodeType() == NodeType::Unused)
473,666✔
149
                        node.list().erase(node.constList().begin() + static_cast<std::vector<Node>::difference_type>(pos));
9✔
150
                    else if (!added_begin)
473,657✔
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;
471,950✔
157

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

167
                if (pos < node.constList().size())
473,966✔
168
                {
169
                    // if we now have a surrounding (begin ...) and didn't have one before, remove it
170
                    if (added_begin)
473,689✔
171
                        removeBegin(node, pos);
1,715✔
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)
471,974✔
174
                        node.list().erase(node.constList().begin() + static_cast<std::vector<Node>::difference_type>(pos));
191✔
175
                }
473,689✔
176
            }
473,966✔
177

178
            // delete a scope only if needed
179
            if (!m_macros.empty() && m_macros.back().depth() == depth && !is_processing_namespace)
133,284✔
180
                m_macros.pop_back();
171✔
181
        }
133,284✔
182
        else if (node.nodeType() == NodeType::Namespace)
340,788✔
183
        {
184
            Node& namespace_ast = *node.arkNamespace().ast;
200✔
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);
200✔
190
        }
200✔
191
    }
474,073✔
192

193
    bool MacroProcessor::applyMacro(Node& node, const unsigned depth)
477,255✔
194
    {
477,255✔
195
        if (depth > MaxMacroProcessingDepth)
477,255✔
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,908,367✔
203
        {
204
            if (executor->canHandle(node))
1,431,331✔
205
            {
206
                m_macros_being_applied.push_back(executor->macroNode(node));
237,427✔
207
                const bool applied = executor->applyMacro(node, depth);
237,427✔
208
                m_macros_being_applied.pop_back();
237,209✔
209

210
                if (applied)
237,209✔
211
                    return true;
1,970✔
212
            }
237,209✔
213
        }
1,431,113✔
214
        return false;
475,175✔
215
    }
477,037✔
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,842✔
218
    {
2,842✔
219
        const std::size_t argcount = node.constList().size();
2,842✔
220
        if (argcount != expected + 1)
2,842✔
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,843✔
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)
59,177✔
260
    {
59,177✔
261
        if (node.nodeType() == NodeType::Symbol)
59,177✔
262
        {
263
            const Node* macro = findNearestMacro(node.string());
20,137✔
264
            if (macro != nullptr && macro->constList().size() == 2)
20,137✔
265
                return macro->constList()[1];
156✔
266
            return node;
19,981✔
267
        }
20,137✔
268
        if (node.nodeType() == NodeType::List && !node.constList().empty() && node.list()[0].nodeType() == NodeType::Symbol)
39,040✔
269
        {
270
            const std::string& name = node.list()[0].string();
15,549✔
271
            const std::size_t argcount = node.list().size() - 1;
15,549✔
272

273
            if (const Node* macro = findNearestMacro(name); macro != nullptr)
31,098✔
274
            {
275
                applyMacro(node.list()[0], depth + 1);
1,528✔
276
                if (node.list()[0].nodeType() == NodeType::Unused)
1,528✔
277
                    node.list().erase(node.constList().begin());
×
278
            }
1,528✔
279
            else if (name == "=" && is_not_body)
14,021✔
280
            {
281
                checkMacroArgCountEq(node, 2, "=", /* is_expansion= */ false, "condition");
78✔
282
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
77✔
283
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
77✔
284
                return (one == two) ? getTrueNode() : getFalseNode();
77✔
285
            }
77✔
286
            else if (name == "!=" && is_not_body)
13,943✔
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)
13,942✔
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)
13,941✔
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)
13,900✔
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)
13,899✔
315
            {
316
                checkMacroArgCountEq(node, 2, ">=", /* is_expansion= */ false, "condition");
17✔
317
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
17✔
318
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
17✔
319
                return !(one < two) ? getTrueNode() : getFalseNode();
17✔
320
            }
17✔
321
            else if (name == "+" && is_not_body)
13,882✔
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)
13,873✔
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)
13,809✔
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)
13,808✔
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)
13,806✔
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)
13,805✔
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)
13,800✔
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")
13,797✔
411
            {
412
                checkMacroArgCountEq(node, 1, "$len", true);
63✔
413
                Node& lst = node.list()[1];
62✔
414
                checkMacroTypeError("$len", "node", NodeType::List, lst);
62✔
415

416
                if (!lst.list().empty() && lst.list()[0] == getListNode())
61✔
417
                    node.updateValueAndType(Node(static_cast<long>(lst.list().size()) - 1));
60✔
418
                else
419
                    node.updateValueAndType(Node(static_cast<long>(lst.list().size())));
1✔
420
            }
62✔
421
            else if (name == "$empty?")
13,734✔
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")
13,712✔
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")
13,695✔
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")
13,683✔
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")
13,674✔
522
            {
523
                checkMacroArgCountEq(node, 0, "$gensym", true);
9✔
524
                node.updateValueAndType(Node(NodeType::Symbol, fmt::format("#gensym-{}", m_genned_sym)));
8✔
525
                ++m_genned_sym;
8✔
526
            }
8✔
527
            else if (name == Language::Symcat)
13,665✔
528
            {
529
                if (node.list().size() <= 2)
106✔
530
                    throwMacroProcessingError(fmt::format("When expanding `{}', expected at least 2 arguments, got {} arguments", Language::Symcat, argcount), node);
1✔
531
                if (node.list()[1].nodeType() != NodeType::Symbol && node.list()[1].nodeType() != NodeType::String)
105✔
532
                    checkMacroTypeError(Language::Symcat.data(), "symbol", NodeType::Symbol, node.list()[1]);
1✔
533

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

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

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

547
                        case NodeType::String:
548
                        case NodeType::Symbol:
549
                            sym += ev.string();
78✔
550
                            break;
79✔
551

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

563
                node.setNodeType(NodeType::Symbol);
103✔
564
                node.setString(sym);
103✔
565
            }
104✔
566
            else if (name == Language::Argcount)
13,559✔
567
            {
568
                checkMacroArgCountEq(node, 1, Language::Argcount.data(), true);
21✔
569

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

587
                const Node arg = node.constList()[1];
2,548✔
588
                if (arg.nodeType() == NodeType::Symbol)
2,548✔
589
                {
590
                    if (const Node* arg_as_macro = findNearestMacro(arg.string()); arg_as_macro != nullptr)
690✔
591
                        node.updateValueAndType(Node(NodeType::String, arg_as_macro->repr()));
13✔
592
                    else
593
                        node.updateValueAndType(Node(NodeType::String, arg.repr()));
332✔
594
                }
345✔
595
                else
596
                    node.updateValueAndType(Node(NodeType::String, arg.repr()));
2,203✔
597
            }
2,548✔
598
            else if (name == Language::AsIs)
10,989✔
599
            {
600
                if (node.list().size() != 2)
2,482✔
601
                    throwMacroProcessingError(fmt::format("When expanding `{}', expected one argument, got {} arguments", Language::AsIs, argcount), node);
1✔
602
                return node.constList()[1];
2,481✔
603
            }
604
            else if (name == Language::Undef)
8,507✔
605
            {
606
                if (node.list().size() != 2)
13✔
607
                    throwMacroProcessingError(fmt::format("When expanding `{}', expected one argument, got {} arguments", Language::Undef, argcount), node);
1✔
608

609
                const Node sym = node.constList()[1];
12✔
610
                if (sym.nodeType() == NodeType::Symbol)
12✔
611
                {
612
                    deleteNearestMacro(sym.string());
11✔
613
                    node.setNodeType(NodeType::Unused);
11✔
614
                    return node;
11✔
615
                }
616

617
                throwMacroProcessingError(
2✔
618
                    fmt::format(
2✔
619
                        "When expanding `{}', got a {}. Can not un-define a macro without a valid name",
1✔
620
                        Language::Undef, typeToString(sym)),
1✔
621
                    sym);
622
            }
12✔
623
            else if (name == Language::Type)
8,494✔
624
            {
625
                if (node.list().size() != 2)
36✔
626
                    throwMacroProcessingError(fmt::format("When expanding `{}', expected one argument, got {} arguments", Language::Type, argcount), node);
1✔
627

628
                const Node arg = node.constList()[1];
35✔
629
                node.updateValueAndType(Node(NodeType::String, typeToString(arg)));
35✔
630
            }
35✔
631
        }
15,550✔
632

633
        if (node.nodeType() == NodeType::List && !node.constList().empty())
36,202✔
634
        {
635
            for (auto& child : node.list())
75,104✔
636
                child.updateValueAndType(evaluate(child, depth + 1, is_not_body));
56,255✔
637
        }
18,849✔
638

639
        if (node.nodeType() == NodeType::Spread)
36,202✔
640
            throwMacroProcessingError(fmt::format("Found an unevaluated spread: `{}'", node.string()), node);
1✔
641

642
        return node;
36,201✔
643
    }
59,177✔
644

645
    bool MacroProcessor::isTruthy(const Node& node) const
178✔
646
    {
178✔
647
        if (node.nodeType() == NodeType::Symbol)
178✔
648
        {
649
            if (node.string() == "true")
177✔
650
                return true;
76✔
651
            if (node.string() == "false" || node.string() == "nil")
101✔
652
                return false;
101✔
UNCOV
653
        }
×
654
        else if ((node.nodeType() == NodeType::Number && node.number() != 0.0) || (node.nodeType() == NodeType::String && !node.string().empty()))
1✔
655
            return true;
1✔
656
        else if (node.nodeType() == NodeType::Spread)
×
657
            throwMacroProcessingError("Can not determine the truth value of a spread symbol", node);
×
UNCOV
658
        return false;
×
659
    }
178✔
660

661
    std::optional<Node> MacroProcessor::lookupDefinedFunction(const std::string& name) const
16✔
662
    {
16✔
663
        if (m_defined_functions.contains(name))
16✔
664
            return m_defined_functions.at(name);
15✔
665
        return std::nullopt;
1✔
666
    }
16✔
667

668
    const Node* MacroProcessor::findNearestMacro(const std::string& name) const
510,655✔
669
    {
510,655✔
670
        if (m_macros.empty())
510,655✔
671
            return nullptr;
242,725✔
672

673
        for (const auto& m_macro : std::ranges::reverse_view(m_macros))
765,184✔
674
        {
675
            if (const auto res = m_macro.has(name); res != nullptr)
497,254✔
676
                return res;
10,167✔
677
        }
497,254✔
678
        return nullptr;
257,763✔
679
    }
510,655✔
680

681
    void MacroProcessor::deleteNearestMacro(const std::string& name)
11✔
682
    {
11✔
683
        if (m_macros.empty())
11✔
UNCOV
684
            return;
×
685

686
        for (auto& m_macro : std::ranges::reverse_view(m_macros))
30✔
687
        {
688
            if (m_macro.remove(name))
19✔
689
            {
690
                // stop right here because we found one matching macro
691
                return;
1✔
692
            }
693
        }
19✔
694
    }
11✔
695

696
    bool MacroProcessor::isBeginNode(const Node& node)
947,968✔
697
    {
947,968✔
698
        return node.nodeType() == NodeType::List &&
1,213,691✔
699
            !node.constList().empty() &&
265,723✔
700
            node.constList()[0].nodeType() == NodeType::Keyword &&
382,280✔
701
            node.constList()[0].keyword() == Keyword::Begin;
116,997✔
702
    }
703

704
    void MacroProcessor::removeBegin(Node& node, const std::size_t i)
1,715✔
705
    {
1,715✔
706
        if (node.isListLike() && node.list()[i].nodeType() == NodeType::List && !node.list()[i].list().empty())
1,715✔
707
        {
708
            Node lst = node.constList()[i];
1,715✔
709
            const Node first = lst.constList()[0];
1,715✔
710

711
            if (first.nodeType() == NodeType::Keyword && first.keyword() == Keyword::Begin)
1,715✔
712
            {
713
                const std::size_t previous = i;
1,715✔
714

715
                for (std::size_t block_idx = 1, end = lst.constList().size(); block_idx < end; ++block_idx)
4,569✔
716
                    node.list().insert(
5,708✔
717
                        node.constList().begin() + static_cast<std::vector<Node>::difference_type>(i + block_idx),
2,854✔
718
                        lst.list()[block_idx]);
2,854✔
719

720
                node.list().erase(node.constList().begin() + static_cast<std::vector<Node>::difference_type>(previous));
1,715✔
721
            }
1,715✔
722
        }
1,715✔
723
    }
1,715✔
724

725
    void MacroProcessor::throwMacroProcessingError(const std::string& message, const Node& node) const
36✔
726
    {
36✔
727
        const std::optional<CodeErrorContext> maybe_context = [this]() -> std::optional<CodeErrorContext> {
72✔
728
            if (!m_macros_being_applied.empty())
36✔
729
            {
730
                const Node& origin = m_macros_being_applied.front();
24✔
731
                return CodeErrorContext(
48✔
732
                    origin.filename(),
24✔
733
                    origin.position(),
24✔
734
                    /* from_macro_expansion= */ true);
735
            }
24✔
736
            return std::nullopt;
12✔
737
        }();
36✔
738

739
        throw CodeError(message, CodeErrorContext(node.filename(), node.position()), maybe_context);
36✔
740
    }
72✔
741

742
    void MacroProcessor::checkMacroTypeError(const std::string& macro, const std::string& arg, const NodeType expected, const Node& actual) const
115✔
743
    {
115✔
744
        if (actual.nodeType() != expected)
115✔
745
            throwMacroProcessingError(
12✔
746
                fmt::format(
18✔
747
                    "When expanding `{}', expected '{}' to be a {}, got a {}: {}",
6✔
748
                    macro,
6✔
749
                    arg,
6✔
750
                    std::string(nodeTypes[static_cast<std::size_t>(expected)]),
6✔
751
                    typeToString(actual),
6✔
752
                    actual.repr()),
6✔
753
                actual);
6✔
754
    }
115✔
755
}
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