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

ArkScript-lang / Ark / 12467799916

23 Dec 2024 01:44PM UTC coverage: 79.433% (+0.03%) from 79.406%
12467799916

push

github

SuperFola
feat(tests): adding more macros tests

5770 of 7264 relevant lines covered (79.43%)

15068.23 hits per line

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

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

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

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

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

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

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

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

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

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

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

91
            if (!inner.constList().empty() && inner.constList()[0].nodeType() == NodeType::Keyword && inner.constList()[0].keyword() == Keyword::Fun)
393✔
92
            {
93
                const Node symbol = node.constList()[1];
128✔
94
                if (symbol.nodeType() == NodeType::Symbol)
128✔
95
                    m_defined_functions.emplace(symbol.string(), inner.constList()[1]);
127✔
96
                else
97
                    throwMacroProcessingError(fmt::format("Can not use a {} to define a variable", typeToString(symbol)), symbol);
1✔
98
            }
128✔
99
        }
923✔
100
    }
11,148✔
101

102
    void MacroProcessor::processNode(Node& node, unsigned depth, bool is_processing_namespace)
100,285✔
103
    {
100,285✔
104
        if (depth >= MaxMacroProcessingDepth)
100,285✔
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)
100,284✔
112
        {
113
            bool has_created = false;
11,275✔
114
            // recursive call
115
            std::size_t i = 0;
11,275✔
116
            while (i < node.list().size())
111,795✔
117
            {
118
                const std::size_t pos = i;
100,520✔
119
                Node& child = node.list()[pos];
100,520✔
120
                const bool had_begin = isBeginNode(child);
100,520✔
121
                bool added_begin = false;
100,520✔
122

123
                if (child.nodeType() == NodeType::Macro)
100,520✔
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) ||
150✔
127
                        (!has_created && !is_processing_namespace) ||
50✔
128
                        (m_macros.empty() && is_processing_namespace))
20✔
129
                    {
130
                        has_created = true;
80✔
131
                        m_macros.emplace_back(depth);
80✔
132
                    }
80✔
133

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

142
                    if (child.nodeType() == NodeType::Unused)
100,421✔
143
                        node.list().erase(node.constList().begin() + static_cast<std::vector<Node>::difference_type>(pos));
6✔
144
                    else if (!added_begin)
100,415✔
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;
100,045✔
151

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

161
                if (pos < node.constList().size())
100,520✔
162
                {
163
                    // if we now have a surrounding (begin ...) and didn't have one before, remove it
164
                    if (added_begin)
100,255✔
165
                        removeBegin(node, pos);
370✔
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)
99,885✔
168
                        node.list().erase(node.constList().begin() + static_cast<std::vector<Node>::difference_type>(pos));
65✔
169
                }
100,255✔
170
            }
100,520✔
171

172
            // delete a scope only if needed
173
            if (!m_macros.empty() && m_macros.back().depth() == depth && !is_processing_namespace)
11,275✔
174
                m_macros.pop_back();
59✔
175
        }
11,275✔
176
        else if (node.nodeType() == NodeType::Namespace)
89,009✔
177
        {
178
            Node& namespace_ast = *node.arkNamespace().ast;
25✔
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);
25✔
184
        }
25✔
185
    }
100,285✔
186

187
    bool MacroProcessor::applyMacro(Node& node, const unsigned depth)
101,419✔
188
    {
101,419✔
189
        if (depth > MaxMacroProcessingDepth)
101,419✔
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)
405,088✔
197
        {
198
            if (executor->canHandle(node))
303,862✔
199
            {
200
                if (executor->applyMacro(node, depth))
20,583✔
201
                    return true;
600✔
202
            }
19,791✔
203
        }
303,670✔
204
        return false;
100,722✔
205
    }
101,227✔
206

207
    void MacroProcessor::checkMacroArgCountEq(const Node& node, std::size_t expected, const std::string& name, const std::string& kind)
71✔
208
    {
71✔
209
        const std::size_t argcount = node.constList().size();
71✔
210
        if (argcount != expected + 1)
71✔
211
            throwMacroProcessingError(
×
212
                fmt::format(
×
213
                    "Interpreting a `{}'{} with {} arguments, expected {}.",
×
214
                    name,
×
215
                    kind.empty() ? kind : " " + kind,
×
216
                    argcount,
217
                    expected),
218
                node);
×
219
    }
71✔
220

221
    void MacroProcessor::checkMacroArgCountGe(const Node& node, std::size_t expected, const std::string& name, const std::string& kind)
42✔
222
    {
42✔
223
        const std::size_t argcount = node.constList().size();
42✔
224
        if (argcount < expected + 1)
42✔
225
            throwMacroProcessingError(
×
226
                fmt::format(
×
227
                    "Interpreting a `{}'{} with {} arguments, expected at least {}.",
×
228
                    name,
×
229
                    kind.empty() ? kind : " " + kind,
×
230
                    argcount,
231
                    expected),
232
                node);
×
233
    }
42✔
234

235
    Node MacroProcessor::evaluate(Node& node, const unsigned depth, const bool is_not_body)
10,081✔
236
    {
10,081✔
237
        if (node.nodeType() == NodeType::Symbol)
10,081✔
238
        {
239
            const Node* macro = findNearestMacro(node.string());
2,700✔
240
            if (macro != nullptr && macro->constList().size() == 2)
2,700✔
241
                return macro->constList()[1];
147✔
242
            return node;
2,553✔
243
        }
2,700✔
244
        if (node.nodeType() == NodeType::List && !node.constList().empty() && node.list()[0].nodeType() == NodeType::Symbol)
7,381✔
245
        {
246
            const std::string& name = node.list()[0].string();
3,939✔
247
            const std::size_t argcount = node.list().size() - 1;
3,939✔
248

249
            if (const Node* macro = findNearestMacro(name); macro != nullptr)
7,878✔
250
            {
251
                applyMacro(node.list()[0], depth + 1);
275✔
252
                if (node.list()[0].nodeType() == NodeType::Unused)
275✔
253
                    node.list().erase(node.constList().begin());
×
254
            }
275✔
255
            else if (name == "=" && is_not_body)
3,664✔
256
            {
257
                checkMacroArgCountEq(node, 2, "=", "condition");
8✔
258
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
8✔
259
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
8✔
260
                return (one == two) ? getTrueNode() : getFalseNode();
8✔
261
            }
8✔
262
            else if (name == "!=" && is_not_body)
3,656✔
263
            {
264
                checkMacroArgCountEq(node, 2, "!=", "condition");
2✔
265
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
2✔
266
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
2✔
267
                return (one != two) ? getTrueNode() : getFalseNode();
2✔
268
            }
2✔
269
            else if (name == "<" && is_not_body)
3,654✔
270
            {
271
                checkMacroArgCountEq(node, 2, "<", "condition");
2✔
272
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
2✔
273
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
2✔
274
                return (one < two) ? getTrueNode() : getFalseNode();
2✔
275
            }
2✔
276
            else if (name == ">" && is_not_body)
3,652✔
277
            {
278
                checkMacroArgCountEq(node, 2, ">", "condition");
15✔
279
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
15✔
280
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
15✔
281
                return !(one < two) && (one != two) ? getTrueNode() : getFalseNode();
15✔
282
            }
15✔
283
            else if (name == "<=" && is_not_body)
3,637✔
284
            {
285
                checkMacroArgCountEq(node, 2, "<=", "condition");
2✔
286
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
2✔
287
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
2✔
288
                return one < two || one == two ? getTrueNode() : getFalseNode();
2✔
289
            }
2✔
290
            else if (name == ">=" && is_not_body)
3,635✔
291
            {
292
                checkMacroArgCountEq(node, 2, ">=", "condition");
2✔
293
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
2✔
294
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
2✔
295
                return !(one < two) ? getTrueNode() : getFalseNode();
2✔
296
            }
2✔
297
            else if (name == "+" && is_not_body)
3,633✔
298
            {
299
                checkMacroArgCountGe(node, 2, "+", "operator");
4✔
300
                double v = 0.0;
4✔
301
                for (auto& child : node.list() | std::ranges::views::drop(1))
14✔
302
                {
303
                    Node ev = evaluate(child, depth + 1, is_not_body);
10✔
304
                    if (ev.nodeType() != NodeType::Number)
10✔
305
                        return node;
×
306
                    v += ev.number();
10✔
307
                }
10✔
308
                return Node(v);
4✔
309
            }
4✔
310
            else if (name == "-" && is_not_body)
3,629✔
311
            {
312
                checkMacroArgCountGe(node, 2, "-", "operator");
34✔
313
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
34✔
314
                if (one.nodeType() != NodeType::Number)
34✔
315
                    return node;
×
316

317
                double v = one.number();
34✔
318
                for (auto& child : node.list() | std::ranges::views::drop(2))
69✔
319
                {
320
                    Node ev = evaluate(child, depth + 1, is_not_body);
35✔
321
                    if (ev.nodeType() != NodeType::Number)
35✔
322
                        return node;
×
323
                    v -= ev.number();
35✔
324
                }
35✔
325
                return Node(v);
34✔
326
            }
34✔
327
            else if (name == "*" && is_not_body)
3,595✔
328
            {
329
                checkMacroArgCountGe(node, 2, "*", "operator");
1✔
330
                double v = 1.0;
1✔
331
                for (auto& child : node.list() | std::ranges::views::drop(1))
4✔
332
                {
333
                    Node ev = evaluate(child, depth + 1, is_not_body);
3✔
334
                    if (ev.nodeType() != NodeType::Number)
3✔
335
                        return node;
×
336
                    v *= ev.number();
3✔
337
                }
3✔
338
                return Node(v);
1✔
339
            }
1✔
340
            else if (name == "/" && is_not_body)
3,594✔
341
            {
342
                checkMacroArgCountGe(node, 2, "/", "operator");
2✔
343
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
2✔
344
                if (one.nodeType() != NodeType::Number)
2✔
345
                    return node;
1✔
346

347
                double v = one.number();
1✔
348
                for (auto& child : node.list() | std::ranges::views::drop(2))
3✔
349
                {
350
                    Node ev = evaluate(child, depth + 1, is_not_body);
2✔
351
                    if (ev.nodeType() != NodeType::Number)
2✔
352
                        return node;
×
353
                    v /= ev.number();
2✔
354
                }
2✔
355
                return Node(v);
1✔
356
            }
2✔
357
            else if (name == "not" && is_not_body)
3,592✔
358
            {
359
                checkMacroArgCountEq(node, 1, "not", "condition");
2✔
360
                return (!isTruthy(evaluate(node.list()[1], depth + 1, is_not_body))) ? getTrueNode() : getFalseNode();
2✔
361
            }
362
            else if (name == Language::And && is_not_body)
3,590✔
363
            {
364
                if (node.list().size() < 3)
7✔
365
                    throwMacroProcessingError(fmt::format("Interpreting a `{}' chain with {} arguments, expected at least 2.", Language::And, argcount), node);
1✔
366

367
                for (std::size_t i = 1, end = node.list().size(); i < end; ++i)
16✔
368
                {
369
                    if (!isTruthy(evaluate(node.list()[i], depth + 1, is_not_body)))
10✔
370
                        return getFalseNode();
2✔
371
                }
8✔
372
                return getTrueNode();
4✔
373
            }
374
            else if (name == Language::Or && is_not_body)
3,583✔
375
            {
376
                if (node.list().size() < 3)
5✔
377
                    throwMacroProcessingError(fmt::format("Interpreting an `{}' chain with {} arguments, expected at least 2.", Language::Or, argcount), node);
1✔
378

379
                for (std::size_t i = 1, end = node.list().size(); i < end; ++i)
12✔
380
                {
381
                    if (isTruthy(evaluate(node.list()[i], depth + 1, is_not_body)))
8✔
382
                        return getTrueNode();
2✔
383
                }
6✔
384
                return getFalseNode();
2✔
385
            }
386
            else if (name == "len")
3,578✔
387
            {
388
                if (node.list().size() > 2)
41✔
389
                    throwMacroProcessingError(fmt::format("When expanding `len' inside a macro, got {} arguments, expected 1", argcount), node);
1✔
390
                if (Node& lst = node.list()[1]; lst.nodeType() == NodeType::List)  // only apply len at compile time if we can
77✔
391
                {
392
                    if (isConstEval(lst))
37✔
393
                    {
394
                        if (!lst.list().empty() && lst.list()[0] == getListNode())
36✔
395
                            node.updateValueAndType(Node(static_cast<long>(lst.list().size()) - 1));
34✔
396
                        else
397
                            node.updateValueAndType(Node(static_cast<long>(lst.list().size())));
2✔
398
                    }
36✔
399
                }
37✔
400
            }
40✔
401
            else if (name == "empty?")
3,537✔
402
            {
403
                if (node.list().size() > 2)
8✔
404
                    throwMacroProcessingError(fmt::format("When expanding `empty?' inside a macro, got {} arguments, expected 1", argcount), node);
1✔
405
                if (Node& lst = node.list()[1]; lst.nodeType() == NodeType::List && isConstEval(lst))
14✔
406
                {
407
                    // only apply len at compile time if we can
408
                    if (!lst.list().empty() && lst.list()[0] == getListNode())
3✔
409
                        node.updateValueAndType(lst.list().size() - 1 == 0 ? getTrueNode() : getFalseNode());
1✔
410
                    else
411
                        node.updateValueAndType(lst.list().empty() ? getTrueNode() : getFalseNode());
2✔
412
                }
3✔
413
                else if (lst == getNilNode())
4✔
414
                    node.updateValueAndType(getTrueNode());
3✔
415
            }
7✔
416
            else if (name == "@")
3,529✔
417
            {
418
                checkMacroArgCountEq(node, 2, "@");
38✔
419

420
                Node sublist = evaluate(node.list()[1], depth + 1, is_not_body);
38✔
421
                const Node idx = evaluate(node.list()[2], depth + 1, is_not_body);
38✔
422

423
                if (sublist.nodeType() == NodeType::List && idx.nodeType() == NodeType::Number)
38✔
424
                {
425
                    const std::size_t size = sublist.list().size();
22✔
426
                    std::size_t real_size = size;
22✔
427
                    long num_idx = static_cast<long>(idx.number());
22✔
428

429
                    // if the first node is the function call to "list", don't count it
430
                    if (size > 0 && sublist.list()[0] == getListNode())
22✔
431
                    {
432
                        real_size--;
20✔
433
                        if (num_idx >= 0)
20✔
434
                            ++num_idx;
9✔
435
                    }
20✔
436

437
                    Node output;
22✔
438
                    if (num_idx >= 0 && std::cmp_less(num_idx, size))
22✔
439
                        output = sublist.list()[static_cast<std::size_t>(num_idx)];
11✔
440
                    else if (const auto c = static_cast<long>(size) + num_idx; num_idx < 0 && std::cmp_less(c, size) && c >= 0)
22✔
441
                        output = sublist.list()[static_cast<std::size_t>(c)];
10✔
442
                    else
443
                        throwMacroProcessingError(fmt::format("Index ({}) out of range (list size: {})", num_idx, real_size), node);
1✔
444

445
                    output.setFilename(node.filename());
21✔
446
                    output.setPos(node.line(), node.col());
21✔
447
                    return output;
21✔
448
                }
22✔
449
            }
38✔
450
            else if (name == "head")
3,491✔
451
            {
452
                if (node.list().size() > 2)
15✔
453
                    throwMacroProcessingError(fmt::format("When expanding `head' inside a macro, got {} arguments, expected 1", argcount), node);
1✔
454
                if (node.list()[1].nodeType() == NodeType::List)
14✔
455
                {
456
                    Node& sublist = node.list()[1];
9✔
457
                    if (!sublist.constList().empty() && sublist.constList()[0] == getListNode())
9✔
458
                    {
459
                        if (sublist.constList().size() > 1)
9✔
460
                        {
461
                            const Node sublistCopy = sublist.constList()[1];
6✔
462
                            node.updateValueAndType(sublistCopy);
6✔
463
                        }
6✔
464
                        else
465
                            node.updateValueAndType(getNilNode());
3✔
466
                    }
9✔
467
                    else if (!sublist.list().empty())
×
468
                        node.updateValueAndType(sublist.constList()[0]);
×
469
                    else
470
                        node.updateValueAndType(getNilNode());
×
471
                }
9✔
472
            }
14✔
473
            else if (name == "tail")
3,476✔
474
            {
475
                if (node.list().size() > 2)
19✔
476
                    throwMacroProcessingError(fmt::format("When expanding `tail' inside a macro, got {} arguments, expected 1", argcount), node);
1✔
477
                if (node.list()[1].nodeType() == NodeType::List)
18✔
478
                {
479
                    Node sublist = node.list()[1];
13✔
480
                    if (!sublist.list().empty() && sublist.list()[0] == getListNode())
13✔
481
                    {
482
                        if (sublist.list().size() > 1)
11✔
483
                        {
484
                            sublist.list().erase(sublist.constList().begin() + 1);
8✔
485
                            node.updateValueAndType(sublist);
8✔
486
                        }
8✔
487
                        else
488
                        {
489
                            node.updateValueAndType(Node(NodeType::List));
3✔
490
                            node.push_back(getListNode());
3✔
491
                        }
492
                    }
11✔
493
                    else if (!sublist.list().empty())
2✔
494
                    {
495
                        sublist.list().erase(sublist.constList().begin());
2✔
496
                        sublist.list().insert(sublist.list().begin(), getListNode());
2✔
497
                        node.updateValueAndType(sublist);
2✔
498
                    }
2✔
499
                    else
500
                    {
501
                        node.updateValueAndType(Node(NodeType::List));
×
502
                        node.push_back(getListNode());
×
503
                    }
504
                }
13✔
505
            }
18✔
506
            else if (name == Language::Symcat)
3,457✔
507
            {
508
                if (node.list().size() <= 2)
39✔
509
                    throwMacroProcessingError(fmt::format("When expanding `{}', expected at least 2 arguments, got {} arguments", Language::Symcat, argcount), node);
1✔
510
                if (node.list()[1].nodeType() != NodeType::Symbol)
38✔
511
                    throwMacroProcessingError(
1✔
512
                        fmt::format(
2✔
513
                            "When expanding `{}', expected the first argument to be a Symbol, got a {}: {}",
1✔
514
                            Language::Symcat,
515
                            typeToString(node.list()[1]),
1✔
516
                            node.list()[1].repr()),
1✔
517
                        node);
1✔
518

519
                std::string sym = node.list()[1].string();
37✔
520

521
                for (std::size_t i = 2, end = node.list().size(); i < end; ++i)
74✔
522
                {
523
                    const Node ev = evaluate(node.list()[i], depth + 1, /* is_not_body */ true);
37✔
524

525
                    switch (ev.nodeType())
36✔
526
                    {
14✔
527
                        case NodeType::Number:
528
                            // we don't want '.' in identifiers
529
                            sym += std::to_string(static_cast<long int>(ev.number()));
14✔
530
                            break;
35✔
531

532
                        case NodeType::String:
533
                        case NodeType::Symbol:
534
                            sym += ev.string();
21✔
535
                            break;
22✔
536

537
                        default:
538
                            throwMacroProcessingError(
1✔
539
                                fmt::format(
2✔
540
                                    "When expanding `{}', expected either a Number, String or Symbol, got a {}: {}",
1✔
541
                                    Language::Symcat,
542
                                    typeToString(ev),
1✔
543
                                    ev.repr()),
1✔
544
                                ev);
545
                    }
35✔
546
                }
37✔
547

548
                node.setNodeType(NodeType::Symbol);
35✔
549
                node.setString(sym);
35✔
550
            }
37✔
551
            else if (name == Language::Argcount)
3,418✔
552
            {
553
                const Node sym = node.constList()[1];
25✔
554
                if (sym.nodeType() == NodeType::Symbol)
25✔
555
                {
556
                    if (const auto maybe_func = lookupDefinedFunction(sym.string()); maybe_func.has_value())
44✔
557
                        node.updateValueAndType(Node(static_cast<long>(maybe_func->constList().size())));
21✔
558
                    else
559
                        throwMacroProcessingError(fmt::format("When expanding `{}', expected a known function name, got unbound variable {}", Language::Argcount, sym.string()), sym);
1✔
560
                }
21✔
561
                else if (sym.nodeType() == NodeType::List && sym.constList().size() == 3 && sym.constList()[0].nodeType() == NodeType::Keyword && sym.constList()[0].keyword() == Keyword::Fun)
3✔
562
                    node.updateValueAndType(Node(static_cast<long>(sym.constList()[1].constList().size())));
3✔
563
                else
564
                    throwMacroProcessingError(fmt::format("When trying to apply `{}', got a {} instead of a Symbol or Function", Language::Argcount, typeToString(sym)), sym);
×
565
            }
25✔
566
            else if (name == Language::Repr)
3,393✔
567
            {
568
                const Node ast = node.constList()[1];
563✔
569
                node.updateValueAndType(Node(NodeType::String, ast.repr()));
563✔
570
            }
563✔
571
            else if (name == Language::Paste)
2,830✔
572
            {
573
                if (node.list().size() != 2)
1,106✔
574
                    throwMacroProcessingError(fmt::format("When expanding `{}', expected one argument, got {} arguments", Language::Paste, argcount), node);
1✔
575
                return node.constList()[1];
1,105✔
576
            }
577
            else if (name == Language::Undef)
1,724✔
578
            {
579
                if (node.list().size() != 2)
9✔
580
                    throwMacroProcessingError(fmt::format("When expanding `{}', expected one argument, got {} arguments", Language::Undef, argcount), node);
×
581

582
                const Node sym = node.constList()[1];
9✔
583
                if (sym.nodeType() == NodeType::Symbol)
9✔
584
                {
585
                    deleteNearestMacro(sym.string());
8✔
586
                    node.setNodeType(NodeType::Unused);
8✔
587
                    return node;
8✔
588
                }
589

590
                throwMacroProcessingError(
1✔
591
                    fmt::format(
2✔
592
                        "When expanding `{}', got a {}. Can not un-define a macro without a valid name",
1✔
593
                        Language::Undef, typeToString(sym)),
1✔
594
                    sym);
595
            }
9✔
596
        }
3,940✔
597

598
        if (node.nodeType() == NodeType::List && !node.constList().empty())
6,149✔
599
        {
600
            for (auto& child : node.list())
12,285✔
601
                child.updateValueAndType(evaluate(child, depth + 1, is_not_body));
9,214✔
602
        }
3,071✔
603

604
        if (node.nodeType() == NodeType::Spread)
6,149✔
605
            throwMacroProcessingError(fmt::format("Found an unevaluated spread: `{}'", node.string()), node);
1✔
606

607
        return node;
6,148✔
608
    }
10,081✔
609

610
    bool MacroProcessor::isTruthy(const Node& node)
63✔
611
    {
63✔
612
        if (node.nodeType() == NodeType::Symbol)
63✔
613
        {
614
            if (node.string() == "true")
61✔
615
                return true;
38✔
616
            if (node.string() == "false" || node.string() == "nil")
23✔
617
                return false;
23✔
618
        }
×
619
        else if ((node.nodeType() == NodeType::Number && node.number() != 0.0) || (node.nodeType() == NodeType::String && !node.string().empty()))
2✔
620
            return true;
2✔
621
        else if (node.nodeType() == NodeType::Spread)
×
622
            throwMacroProcessingError("Can not determine the truth value of a spreaded symbol", node);
×
623
        return false;
×
624
    }
63✔
625

626
    std::optional<Node> MacroProcessor::lookupDefinedFunction(const std::string& name) const
22✔
627
    {
22✔
628
        if (m_defined_functions.contains(name))
22✔
629
            return m_defined_functions.at(name);
21✔
630
        return std::nullopt;
1✔
631
    }
22✔
632

633
    const Node* MacroProcessor::findNearestMacro(const std::string& name) const
27,249✔
634
    {
27,249✔
635
        if (m_macros.empty())
27,249✔
636
            return nullptr;
1,015✔
637

638
        for (const auto& m_macro : std::ranges::reverse_view(m_macros))
59,160✔
639
        {
640
            if (const auto res = m_macro.has(name); res != nullptr)
32,926✔
641
                return res;
1,754✔
642
        }
32,926✔
643
        return nullptr;
24,480✔
644
    }
27,249✔
645

646
    void MacroProcessor::deleteNearestMacro(const std::string& name)
8✔
647
    {
8✔
648
        if (m_macros.empty())
8✔
649
            return;
×
650

651
        for (auto& m_macro : std::ranges::reverse_view(m_macros))
21✔
652
        {
653
            if (m_macro.remove(name))
13✔
654
            {
655
                // stop right here because we found one matching macro
656
                return;
1✔
657
            }
658
        }
13✔
659
    }
8✔
660

661
    bool MacroProcessor::isBeginNode(const Node& node)
201,059✔
662
    {
201,059✔
663
        return node.nodeType() == NodeType::List &&
223,779✔
664
            !node.constList().empty() &&
22,720✔
665
            node.constList()[0].nodeType() == NodeType::Keyword &&
27,940✔
666
            node.constList()[0].keyword() == Keyword::Begin;
5,286✔
667
    }
668

669
    void MacroProcessor::removeBegin(Node& node, std::size_t i)
370✔
670
    {
370✔
671
        if (node.isListLike() && node.list()[i].nodeType() == NodeType::List && !node.list()[i].list().empty())
370✔
672
        {
673
            Node lst = node.constList()[i];
370✔
674
            Node first = lst.constList()[0];
370✔
675

676
            if (first.nodeType() == NodeType::Keyword && first.keyword() == Keyword::Begin)
370✔
677
            {
678
                const std::size_t previous = i;
370✔
679

680
                for (std::size_t block_idx = 1, end = lst.constList().size(); block_idx < end; ++block_idx)
941✔
681
                    node.list().insert(
1,142✔
682
                        node.constList().begin() + static_cast<std::vector<Node>::difference_type>(i + block_idx),
571✔
683
                        lst.list()[block_idx]);
571✔
684

685
                node.list().erase(node.constList().begin() + static_cast<std::vector<Node>::difference_type>(previous));
370✔
686
            }
370✔
687
        }
370✔
688
    }
370✔
689

690
    bool MacroProcessor::isConstEval(const Node& node) const
128✔
691
    {
128✔
692
        switch (node.nodeType())
128✔
693
        {
39✔
694
            case NodeType::Symbol:
695
            {
696
                const auto it = std::ranges::find(Language::operators, node.string());
39✔
697
                const auto it2 = std::ranges::find_if(Builtins::builtins,
39✔
698
                                                      [&node](const std::pair<std::string, Value>& element) -> bool {
2,117✔
699
                                                          return node.string() == element.first;
2,078✔
700
                                                      });
701

702
                return it != Language::operators.end() ||
77✔
703
                    it2 != Builtins::builtins.end() ||
38✔
704
                    findNearestMacro(node.string()) != nullptr ||
36✔
705
                    node.string() == "list" ||
37✔
706
                    node.string() == "nil";
1✔
707
            }
83✔
708

709
            case NodeType::List:
710
                return std::ranges::all_of(node.constList(), [this](const Node& child) {
132✔
711
                    return isConstEval(child);
88✔
712
                });
713

714
            case NodeType::Capture:
715
            case NodeType::Field:
716
                return false;
45✔
717

718
            case NodeType::Keyword:
719
            case NodeType::String:
720
            case NodeType::Number:
721
            case NodeType::Macro:
722
            case NodeType::Spread:
723
            case NodeType::Namespace:
724
            case NodeType::Unused:
725
                return true;
45✔
726
        }
×
727

728
        return false;
×
729
    }
128✔
730

731
    void MacroProcessor::throwMacroProcessingError(const std::string& message, const Node& node)
21✔
732
    {
21✔
733
        throw CodeError(message, node.filename(), node.line(), node.col(), node.repr());
21✔
734
    }
21✔
735
}
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