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

ArkScript-lang / Ark / 12164232004

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

push

github

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

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

256 existing lines in 11 files now uncovered.

5412 of 6955 relevant lines covered (77.81%)

9300.67 hits per line

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

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

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

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

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

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

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

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

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

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

85
            const Node inner = node.constList()[2];
529✔
86
            if (inner.nodeType() != NodeType::List)
529✔
87
                return;
196✔
88

89
            if (!inner.constList().empty() && inner.constList()[0].nodeType() == NodeType::Keyword && inner.constList()[0].keyword() == Keyword::Fun)
333✔
90
            {
91
                const Node symbol = node.constList()[1];
120✔
92
                if (symbol.nodeType() == NodeType::Symbol)
120✔
93
                    m_defined_functions.emplace(symbol.string(), inner.constList()[1]);
119✔
94
                else
95
                    throwMacroProcessingError(fmt::format("Can not use a {} to define a variable", typeToString(symbol)), symbol);
1✔
96
            }
120✔
97
        }
784✔
98
    }
7,605✔
99

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

109
        if (node.nodeType() == NodeType::List)
23,314✔
110
        {
111
            bool has_created = false;
7,691✔
112
            // recursive call
113
            std::size_t i = 0;
7,691✔
114
            while (i < node.list().size())
31,299✔
115
            {
116
                const std::size_t pos = i;
23,608✔
117
                Node& child = node.list()[pos];
23,608✔
118
                const bool had_begin = isBeginNode(child);
23,608✔
119
                bool added_begin = false;
23,608✔
120

121
                if (child.nodeType() == NodeType::Macro)
23,608✔
122
                {
123
                    // create a scope only if needed
124
                    if ((!m_macros.empty() && !m_macros.back().empty() && m_macros.back().depth() < depth && !is_processing_namespace) ||
160✔
125
                        (!has_created && !is_processing_namespace) ||
55✔
126
                        (m_macros.empty() && is_processing_namespace))
25✔
127
                    {
128
                        has_created = true;
80✔
129
                        m_macros.emplace_back(depth);
80✔
130
                    }
80✔
131

132
                    handleMacroNode(child);
104✔
133
                    added_begin = isBeginNode(child) && !had_begin;
104✔
134
                }
104✔
135
                else  // running on non-macros
136
                {
137
                    applyMacro(child, 0);
23,504✔
138
                    added_begin = isBeginNode(child) && !had_begin;
23,504✔
139

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

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

159
                if (pos < node.constList().size())
23,608✔
160
                {
161
                    // if we now have a surrounding (begin ...) and didn't have one before, remove it
162
                    if (added_begin)
23,331✔
163
                        removeBegin(node, pos);
319✔
164
                    // if there is an unused node or a leftover macro need, we need to get rid of it in the final ast
165
                    else if (child.nodeType() == NodeType::Macro || child.nodeType() == NodeType::Unused)
23,012✔
166
                        node.list().erase(node.constList().begin() + static_cast<std::vector<Node>::difference_type>(pos));
70✔
167
                }
23,331✔
168
            }
23,608✔
169

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

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

194
        for (const auto& executor : m_executors)
96,910✔
195
        {
196
            if (executor->canHandle(node))
72,739✔
197
            {
198
                if (executor->applyMacro(node, depth))
14,095✔
199
                    return true;
542✔
200
            }
13,349✔
201
        }
72,535✔
202
        return false;
23,731✔
203
    }
24,172✔
204

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

219
    Node MacroProcessor::evaluate(Node& node, const unsigned depth, const bool is_not_body)
8,495✔
220
    {
8,495✔
221
        if (node.nodeType() == NodeType::Symbol)
8,495✔
222
        {
223
            const Node* macro = findNearestMacro(node.string());
2,301✔
224
            if (macro != nullptr && macro->constList().size() == 2)
2,301✔
225
                return macro->constList()[1];
147✔
226
            return node;
2,154✔
227
        }
2,301✔
228
        if (node.nodeType() == NodeType::List && !node.constList().empty() && node.list()[0].nodeType() == NodeType::Symbol)
6,194✔
229
        {
230
            const std::string& name = node.list()[0].string();
3,378✔
231
            const std::size_t argcount = node.list().size() - 1;
3,378✔
232

233
            if (const Node* macro = findNearestMacro(name); macro != nullptr)
6,756✔
234
            {
235
                applyMacro(node.list()[0], depth + 1);
229✔
236
                if (node.list()[0].nodeType() == NodeType::Unused)
229✔
UNCOV
237
                    node.list().erase(node.constList().begin());
×
238
            }
229✔
239
            else if (name == "=" && is_not_body)
3,149✔
240
            {
241
                checkMacroArgCount(node, 2, "=", "condition");
8✔
242
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
8✔
243
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
8✔
244
                return (one == two) ? getTrueNode() : getFalseNode();
8✔
245
            }
8✔
246
            else if (name == "!=" && is_not_body)
3,141✔
247
            {
248
                checkMacroArgCount(node, 2, "!=", "condition");
2✔
249
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
2✔
250
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
2✔
251
                return (one != two) ? getTrueNode() : getFalseNode();
2✔
252
            }
2✔
253
            else if (name == "<" && is_not_body)
3,139✔
254
            {
255
                checkMacroArgCount(node, 2, "<", "condition");
2✔
256
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
2✔
257
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
2✔
258
                return (one < two) ? getTrueNode() : getFalseNode();
2✔
259
            }
2✔
260
            else if (name == ">" && is_not_body)
3,137✔
261
            {
262
                checkMacroArgCount(node, 2, ">", "condition");
15✔
263
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
15✔
264
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
15✔
265
                return !(one < two) && (one != two) ? getTrueNode() : getFalseNode();
15✔
266
            }
15✔
267
            else if (name == "<=" && is_not_body)
3,122✔
268
            {
269
                checkMacroArgCount(node, 2, "<=", "condition");
2✔
270
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
2✔
271
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
2✔
272
                return one < two || one == two ? getTrueNode() : getFalseNode();
2✔
273
            }
2✔
274
            else if (name == ">=" && is_not_body)
3,120✔
275
            {
276
                checkMacroArgCount(node, 2, ">=", "condition");
2✔
277
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
2✔
278
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
2✔
279
                return !(one < two) ? getTrueNode() : getFalseNode();
2✔
280
            }
2✔
281
            else if (name == "+" && is_not_body)
3,118✔
282
            {
283
                checkMacroArgCount(node, 2, "+", "operator");
2✔
284
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
2✔
285
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
2✔
286
                return (one.nodeType() == two.nodeType() && two.nodeType() == NodeType::Number) ? Node(one.number() + two.number()) : node;
2✔
287
            }
2✔
288
            else if (name == "-" && is_not_body)
3,116✔
289
            {
290
                checkMacroArgCount(node, 2, "-", "operator");
35✔
291
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
35✔
292
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
35✔
293
                return (one.nodeType() == two.nodeType() && two.nodeType() == NodeType::Number) ? Node(one.number() - two.number()) : node;
35✔
294
            }
35✔
295
            else if (name == "*" && is_not_body)
3,081✔
296
            {
UNCOV
297
                checkMacroArgCount(node, 2, "*", "operator");
×
UNCOV
298
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
×
UNCOV
299
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
×
UNCOV
300
                return (one.nodeType() == two.nodeType() && two.nodeType() == NodeType::Number) ? Node(one.number() * two.number()) : node;
×
UNCOV
301
            }
×
302
            else if (name == "/" && is_not_body)
3,081✔
303
            {
UNCOV
304
                checkMacroArgCount(node, 2, "/", "operator");
×
UNCOV
305
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
×
UNCOV
306
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
×
UNCOV
307
                return (one.nodeType() == two.nodeType() && two.nodeType() == NodeType::Number) ? Node(one.number() / two.number()) : node;
×
UNCOV
308
            }
×
309
            else if (name == "not" && is_not_body)
3,081✔
310
            {
311
                checkMacroArgCount(node, 1, "not", "condition");
2✔
312
                return (!isTruthy(evaluate(node.list()[1], depth + 1, is_not_body))) ? getTrueNode() : getFalseNode();
2✔
313
            }
314
            else if (name == Language::And && is_not_body)
3,079✔
315
            {
316
                if (node.list().size() < 3)
7✔
317
                    throwMacroProcessingError(fmt::format("Interpreting a `{}' chain with {} arguments, expected at least 2.", Language::And, argcount), node);
1✔
318

319
                for (std::size_t i = 1, end = node.list().size(); i < end; ++i)
16✔
320
                {
321
                    if (!isTruthy(evaluate(node.list()[i], depth + 1, is_not_body)))
10✔
322
                        return getFalseNode();
2✔
323
                }
8✔
324
                return getTrueNode();
4✔
325
            }
326
            else if (name == Language::Or && is_not_body)
3,072✔
327
            {
328
                if (node.list().size() < 3)
5✔
329
                    throwMacroProcessingError(fmt::format("Interpreting an `{}' chain with {} arguments, expected at least 2.", Language::Or, argcount), node);
1✔
330

331
                for (std::size_t i = 1, end = node.list().size(); i < end; ++i)
12✔
332
                {
333
                    if (isTruthy(evaluate(node.list()[i], depth + 1, is_not_body)))
8✔
334
                        return getTrueNode();
2✔
335
                }
6✔
336
                return getFalseNode();
2✔
337
            }
338
            else if (name == "len")
3,067✔
339
            {
340
                if (node.list().size() > 2)
29✔
341
                    throwMacroProcessingError(fmt::format("When expanding `len' inside a macro, got {} arguments, expected 1", argcount), node);
1✔
342
                if (Node& lst = node.list()[1]; lst.nodeType() == NodeType::List)  // only apply len at compile time if we can
53✔
343
                {
344
                    if (isConstEval(lst))
25✔
345
                    {
346
                        if (!lst.list().empty() && lst.list()[0] == getListNode())
24✔
347
                            node.updateValueAndType(Node(static_cast<long>(lst.list().size()) - 1));
22✔
348
                        else
349
                            node.updateValueAndType(Node(static_cast<long>(lst.list().size())));
2✔
350
                    }
24✔
351
                }
25✔
352
            }
28✔
353
            else if (name == "empty?")
3,038✔
354
            {
355
                if (node.list().size() > 2)
7✔
356
                    throwMacroProcessingError(fmt::format("When expanding `empty?' inside a macro, got {} arguments, expected 1", argcount), node);
1✔
357
                if (Node& lst = node.list()[1]; lst.nodeType() == NodeType::List && isConstEval(lst))
12✔
358
                {
359
                    // only apply len at compile time if we can
360
                    if (!lst.list().empty() && lst.list()[0] == getListNode())
3✔
361
                        node.updateValueAndType(lst.list().size() - 1 == 0 ? getTrueNode() : getFalseNode());
1✔
362
                    else
363
                        node.updateValueAndType(lst.list().empty() ? getTrueNode() : getFalseNode());
2✔
364
                }
3✔
365
                else if (lst == getNilNode())
3✔
366
                    node.updateValueAndType(getTrueNode());
2✔
367
            }
6✔
368
            else if (name == "@")
3,031✔
369
            {
370
                checkMacroArgCount(node, 2, "@");
33✔
371

372
                Node sublist = evaluate(node.list()[1], depth + 1, is_not_body);
33✔
373
                const Node idx = evaluate(node.list()[2], depth + 1, is_not_body);
33✔
374

375
                if (sublist.nodeType() == NodeType::List && idx.nodeType() == NodeType::Number)
33✔
376
                {
377
                    const std::size_t size = sublist.list().size();
22✔
378
                    std::size_t real_size = size;
22✔
379
                    long num_idx = static_cast<long>(idx.number());
22✔
380

381
                    // if the first node is the function call to "list", don't count it
382
                    if (size > 0 && sublist.list()[0] == getListNode())
22✔
383
                    {
384
                        real_size--;
20✔
385
                        if (num_idx >= 0)
20✔
386
                            ++num_idx;
9✔
387
                    }
20✔
388

389
                    Node output;
22✔
390
                    if (num_idx >= 0 && std::cmp_less(num_idx, size))
22✔
391
                        output = sublist.list()[static_cast<std::size_t>(num_idx)];
11✔
392
                    else if (const auto c = static_cast<long>(size) + num_idx; num_idx < 0 && std::cmp_less(c, size) && c >= 0)
22✔
393
                        output = sublist.list()[static_cast<std::size_t>(c)];
10✔
394
                    else
395
                        throwMacroProcessingError(fmt::format("Index ({}) out of range (list size: {})", num_idx, real_size), node);
1✔
396

397
                    output.setFilename(node.filename());
21✔
398
                    output.setPos(node.line(), node.col());
21✔
399
                    return output;
21✔
400
                }
22✔
401
            }
33✔
402
            else if (name == "head")
2,998✔
403
            {
404
                if (node.list().size() > 2)
13✔
405
                    throwMacroProcessingError(fmt::format("When expanding `head' inside a macro, got {} arguments, expected 1", argcount), node);
1✔
406
                if (node.list()[1].nodeType() == NodeType::List)
12✔
407
                {
408
                    Node& sublist = node.list()[1];
9✔
409
                    if (!sublist.constList().empty() && sublist.constList()[0] == getListNode())
9✔
410
                    {
411
                        if (sublist.constList().size() > 1)
9✔
412
                        {
413
                            const Node sublistCopy = sublist.constList()[1];
6✔
414
                            node.updateValueAndType(sublistCopy);
6✔
415
                        }
6✔
416
                        else
417
                            node.updateValueAndType(getNilNode());
3✔
418
                    }
9✔
UNCOV
419
                    else if (!sublist.list().empty())
×
UNCOV
420
                        node.updateValueAndType(sublist.constList()[0]);
×
421
                    else
UNCOV
422
                        node.updateValueAndType(getNilNode());
×
423
                }
9✔
424
            }
12✔
425
            else if (name == "tail")
2,985✔
426
            {
427
                if (node.list().size() > 2)
17✔
428
                    throwMacroProcessingError(fmt::format("When expanding `tail' inside a macro, got {} arguments, expected 1", argcount), node);
1✔
429
                if (node.list()[1].nodeType() == NodeType::List)
16✔
430
                {
431
                    Node sublist = node.list()[1];
13✔
432
                    if (!sublist.list().empty() && sublist.list()[0] == getListNode())
13✔
433
                    {
434
                        if (sublist.list().size() > 1)
11✔
435
                        {
436
                            sublist.list().erase(sublist.constList().begin() + 1);
8✔
437
                            node.updateValueAndType(sublist);
8✔
438
                        }
8✔
439
                        else
440
                        {
441
                            node.updateValueAndType(Node(NodeType::List));
3✔
442
                            node.push_back(getListNode());
3✔
443
                        }
444
                    }
11✔
445
                    else if (!sublist.list().empty())
2✔
446
                    {
447
                        sublist.list().erase(sublist.constList().begin());
2✔
448
                        sublist.list().insert(sublist.list().begin(), getListNode());
2✔
449
                        node.updateValueAndType(sublist);
2✔
450
                    }
2✔
451
                    else
452
                    {
UNCOV
453
                        node.updateValueAndType(Node(NodeType::List));
×
UNCOV
454
                        node.push_back(getListNode());
×
455
                    }
456
                }
13✔
457
            }
16✔
458
            else if (name == Language::Symcat)
2,968✔
459
            {
460
                if (node.list().size() <= 2)
41✔
461
                    throwMacroProcessingError(fmt::format("When expanding `{}', expected at least 2 arguments, got {} arguments", Language::Symcat, argcount), node);
1✔
462
                if (node.list()[1].nodeType() != NodeType::Symbol)
40✔
463
                    throwMacroProcessingError(fmt::format("When expanding `{}', expected the first argument to be a Symbol, got a {}", Language::Symcat, typeToString(node.list()[1])), node);
1✔
464

465
                std::string sym = node.list()[1].string();
39✔
466

467
                for (std::size_t i = 2, end = node.list().size(); i < end; ++i)
78✔
468
                {
469
                    const Node ev = evaluate(node.list()[i], depth + 1, /* is_not_body */ true);
39✔
470

471
                    switch (ev.nodeType())
39✔
472
                    {
17✔
473
                        case NodeType::Number:
474
                            // we don't want '.' in identifiers
475
                            sym += std::to_string(static_cast<long int>(ev.number()));
17✔
476
                            break;
38✔
477

478
                        case NodeType::String:
479
                        case NodeType::Symbol:
480
                            sym += ev.string();
21✔
481
                            break;
22✔
482

483
                        default:
484
                            throwMacroProcessingError(fmt::format("When expanding `{}', expected either a Number, String or Symbol, got a {}", Language::Symcat, typeToString(ev)), ev);
1✔
485
                    }
38✔
486
                }
39✔
487

488
                node.setNodeType(NodeType::Symbol);
38✔
489
                node.setString(sym);
38✔
490
            }
39✔
491
            else if (name == Language::Argcount)
2,927✔
492
            {
493
                const Node sym = node.constList()[1];
13✔
494
                if (sym.nodeType() == NodeType::Symbol)
13✔
495
                {
496
                    if (const auto maybe_func = lookupDefinedFunction(sym.string()); maybe_func.has_value())
20✔
497
                        node.updateValueAndType(Node(static_cast<long>(maybe_func->constList().size())));
9✔
498
                    else
499
                        throwMacroProcessingError(fmt::format("When expanding `{}', expected a known function name, got unbound variable {}", Language::Argcount, sym.string()), sym);
1✔
500
                }
9✔
501
                else if (sym.nodeType() == NodeType::List && sym.constList().size() == 3 && sym.constList()[0].nodeType() == NodeType::Keyword && sym.constList()[0].keyword() == Keyword::Fun)
3✔
502
                    node.updateValueAndType(Node(static_cast<long>(sym.constList()[1].constList().size())));
3✔
503
                else
UNCOV
504
                    throwMacroProcessingError(fmt::format("When trying to apply `{}', got a {} instead of a Symbol or Function", Language::Argcount, typeToString(sym)), sym);
×
505
            }
13✔
506
            else if (name == Language::Repr)
2,914✔
507
            {
508
                const Node ast = node.constList()[1];
483✔
509
                node.updateValueAndType(Node(NodeType::String, ast.repr()));
483✔
510
            }
483✔
511
            else if (name == Language::Paste)
2,431✔
512
            {
513
                if (node.list().size() != 2)
946✔
514
                    throwMacroProcessingError(fmt::format("When expanding `{}', expected one argument, got {} arguments", Language::Paste, argcount), node);
1✔
515
                return node.constList()[1];
945✔
516
            }
517
            else if (name == Language::Undef)
1,485✔
518
            {
519
                if (node.list().size() != 2)
9✔
UNCOV
520
                    throwMacroProcessingError(fmt::format("When expanding `{}', expected one argument, got {} arguments", Language::Undef, argcount), node);
×
521

522
                const Node sym = node.constList()[1];
9✔
523
                if (sym.nodeType() == NodeType::Symbol)
9✔
524
                {
525
                    deleteNearestMacro(sym.string());
8✔
526
                    node.setNodeType(NodeType::Unused);
8✔
527
                    return node;
8✔
528
                }
529

530
                throwMacroProcessingError(
1✔
531
                    fmt::format(
2✔
532
                        "When expanding `{}', got a {}. Can not un-define a macro without a valid name",
1✔
533
                        Language::Undef, typeToString(sym)),
1✔
534
                    sym);
535
            }
9✔
536
        }
3,379✔
537

538
        if (node.nodeType() == NodeType::List && !node.constList().empty())
5,127✔
539
        {
540
            for (auto& child : node.list())
10,327✔
541
                child.updateValueAndType(evaluate(child, depth + 1, is_not_body));
7,728✔
542
        }
2,599✔
543

544
        if (node.nodeType() == NodeType::Spread)
5,127✔
545
            throwMacroProcessingError(fmt::format("Found an unevaluated spread: `{}'", node.string()), node);
1✔
546

547
        return node;
5,126✔
548
    }
8,495✔
549

550
    bool MacroProcessor::isTruthy(const Node& node)
63✔
551
    {
63✔
552
        if (node.nodeType() == NodeType::Symbol)
63✔
553
        {
554
            if (node.string() == "true")
61✔
555
                return true;
38✔
556
            if (node.string() == "false" || node.string() == "nil")
23✔
557
                return false;
23✔
UNCOV
558
        }
×
559
        else if ((node.nodeType() == NodeType::Number && node.number() != 0.0) || (node.nodeType() == NodeType::String && !node.string().empty()))
2✔
560
            return true;
2✔
UNCOV
561
        else if (node.nodeType() == NodeType::Spread)
×
UNCOV
562
            throwMacroProcessingError("Can not determine the truth value of a spreaded symbol", node);
×
UNCOV
563
        return false;
×
564
    }
63✔
565

566
    std::optional<Node> MacroProcessor::lookupDefinedFunction(const std::string& name) const
10✔
567
    {
10✔
568
        if (m_defined_functions.contains(name))
10✔
569
            return m_defined_functions.at(name);
9✔
570
        return std::nullopt;
1✔
571
    }
10✔
572

573
    const Node* MacroProcessor::findNearestMacro(const std::string& name) const
19,782✔
574
    {
19,782✔
575
        if (m_macros.empty())
19,782✔
576
            return nullptr;
785✔
577

578
        for (const auto& m_macro : std::ranges::reverse_view(m_macros))
42,937✔
579
        {
580
            if (const auto res = m_macro.has(name); res != nullptr)
23,940✔
581
                return res;
1,569✔
582
        }
23,940✔
583
        return nullptr;
17,428✔
584
    }
19,782✔
585

586
    void MacroProcessor::deleteNearestMacro(const std::string& name)
8✔
587
    {
8✔
588
        if (m_macros.empty())
8✔
UNCOV
589
            return;
×
590

591
        for (auto& m_macro : std::ranges::reverse_view(m_macros))
21✔
592
        {
593
            if (m_macro.remove(name))
13✔
594
            {
595
                // stop right here because we found one matching macro
596
                return;
1✔
597
            }
598
        }
13✔
599
    }
8✔
600

601
    bool MacroProcessor::isBeginNode(const Node& node)
47,235✔
602
    {
47,235✔
603
        return node.nodeType() == NodeType::List &&
62,888✔
604
            !node.constList().empty() &&
15,653✔
605
            node.constList()[0].nodeType() == NodeType::Keyword &&
19,552✔
606
            node.constList()[0].keyword() == Keyword::Begin;
3,963✔
607
    }
608

609
    void MacroProcessor::removeBegin(Node& node, std::size_t i)
319✔
610
    {
319✔
611
        if (node.isListLike() && node.list()[i].nodeType() == NodeType::List && !node.list()[i].list().empty())
319✔
612
        {
613
            Node lst = node.constList()[i];
319✔
614
            Node first = lst.constList()[0];
319✔
615

616
            if (first.nodeType() == NodeType::Keyword && first.keyword() == Keyword::Begin)
319✔
617
            {
618
                const std::size_t previous = i;
319✔
619

620
                for (std::size_t block_idx = 1, end = lst.constList().size(); block_idx < end; ++block_idx)
799✔
621
                    node.list().insert(
960✔
622
                        node.constList().begin() + static_cast<std::vector<Node>::difference_type>(i + block_idx),
480✔
623
                        lst.list()[block_idx]);
480✔
624

625
                node.list().erase(node.constList().begin() + static_cast<std::vector<Node>::difference_type>(previous));
319✔
626
            }
319✔
627
        }
319✔
628
    }
319✔
629

630
    bool MacroProcessor::isConstEval(const Node& node) const
92✔
631
    {
92✔
632
        switch (node.nodeType())
92✔
633
        {
27✔
634
            case NodeType::Symbol:
635
            {
636
                const auto it = std::ranges::find(Language::operators, node.string());
27✔
637
                const auto it2 = std::ranges::find_if(Builtins::builtins,
27✔
638
                                                      [&node](const std::pair<std::string, Value>& element) -> bool {
1,408✔
639
                                                          return node.string() == element.first;
1,381✔
640
                                                      });
641

642
                return it != Language::operators.end() ||
53✔
643
                    it2 != Builtins::builtins.end() ||
26✔
644
                    findNearestMacro(node.string()) != nullptr ||
24✔
645
                    node.string() == "list" ||
25✔
646
                    node.string() == "nil";
1✔
647
            }
59✔
648

649
            case NodeType::List:
650
                return std::ranges::all_of(node.constList(), [this](const Node& child) {
96✔
651
                    return isConstEval(child);
64✔
652
                });
653

654
            case NodeType::Capture:
655
            case NodeType::Field:
656
                return false;
33✔
657

658
            case NodeType::Keyword:
659
            case NodeType::String:
660
            case NodeType::Number:
661
            case NodeType::Macro:
662
            case NodeType::Spread:
663
            case NodeType::Namespace:
664
            case NodeType::Unused:
665
                return true;
33✔
UNCOV
666
        }
×
667

UNCOV
668
        return false;
×
669
    }
92✔
670

671
    void MacroProcessor::throwMacroProcessingError(const std::string& message, const Node& node)
21✔
672
    {
21✔
673
        throw CodeError(message, node.filename(), node.line(), node.col(), node.repr());
21✔
674
    }
21✔
675
}
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