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

ArkScript-lang / Ark / 11629611787

01 Nov 2024 12:48PM UTC coverage: 77.319% (+0.3%) from 77.042%
11629611787

push

github

SuperFola
feat(tests): adding first test for IR generation and optimization

5209 of 6737 relevant lines covered (77.32%)

9473.78 hits per line

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

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

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

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

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

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

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

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

57
        // ($ name value)
58
        if (node.constList().size() == 2)
104✔
59
        {
60
            assert(first_node.nodeType() == NodeType::Symbol && "Can not define a macro without a symbol");
26✔
61
            m_macros.back().add(first_node.string(), node);
26✔
62
        }
26✔
63
        // ($ name (args) body)
64
        else if (node.constList().size() == 3 && first_node.nodeType() == NodeType::Symbol)
78✔
65
        {
66
            assert(node.list()[1].nodeType() == NodeType::List && "Invalid macro argument's list");
36✔
67
            m_macros.back().add(first_node.string(), node);
36✔
68
        }
36✔
69
    }
104✔
70

71
    void MacroProcessor::registerFuncDef(const Node& node)
7,525✔
72
    {
7,525✔
73
        if (node.nodeType() == NodeType::List && node.constList().size() == 3 && node.constList()[0].nodeType() == NodeType::Keyword)
7,525✔
74
        {
75
            Keyword kw = node.constList()[0].keyword();
738✔
76
            // checking for function definition, which can occur only inside an assignment node
77
            if (kw != Keyword::Let && kw != Keyword::Mut && kw != Keyword::Set)
738✔
78
                return;
239✔
79

80
            const Node inner = node.constList()[2];
499✔
81
            if (inner.nodeType() != NodeType::List)
499✔
82
                return;
183✔
83

84
            if (!inner.constList().empty() && inner.constList()[0].nodeType() == NodeType::Keyword && inner.constList()[0].keyword() == Keyword::Fun)
316✔
85
            {
86
                const Node symbol = node.constList()[1];
109✔
87
                if (symbol.nodeType() == NodeType::Symbol)
109✔
88
                    m_defined_functions.emplace(symbol.string(), inner.constList()[1]);
108✔
89
                else
90
                    throwMacroProcessingError(fmt::format("Can not use a {} to define a variable", typeToString(symbol)), symbol);
1✔
91
            }
109✔
92
        }
738✔
93
    }
7,525✔
94

95
    void MacroProcessor::processNode(Node& node, unsigned depth)
23,262✔
96
    {
23,262✔
97
        if (depth >= MaxMacroProcessingDepth)
23,262✔
98
            throwMacroProcessingError(
1✔
99
                fmt::format(
2✔
100
                    "Max recursion depth reached ({}). You most likely have a badly defined recursive macro calling itself without a proper exit condition",
1✔
101
                    MaxMacroProcessingDepth),
102
                node);
1✔
103

104
        if (node.nodeType() == NodeType::List)
23,261✔
105
        {
106
            bool has_created = false;
7,576✔
107
            // recursive call
108
            std::size_t i = 0;
7,576✔
109
            while (i < node.list().size())
30,872✔
110
            {
111
                if (node.list()[i].nodeType() == NodeType::Macro)
23,296✔
112
                {
113
                    // create a scope only if needed
114
                    if ((!m_macros.empty() && !m_macros.back().empty() && m_macros.back().depth() < depth) || !has_created)
101✔
115
                    {
116
                        has_created = true;
75✔
117
                        m_macros.emplace_back(depth);
75✔
118
                    }
75✔
119

120
                    const bool had = hadBegin(node.list()[i]);
101✔
121

122
                    // register the macro we encountered
123
                    registerMacro(node.list()[i]);
101✔
124
                    recurApply(node.list()[i]);
101✔
125

126
                    // if we now have a surrounding (begin ...) and didn't have one before, remove it
127
                    if (hadBegin(node.list()[i]) && !had)
101✔
128
                        removeBegin(node, i);
1✔
129
                    // if there is an unused node or a leftover macro need, we need to get rid of it in the final ast
130
                    else if (node.list()[i].nodeType() == NodeType::Macro || node.list()[i].nodeType() == NodeType::Unused)
100✔
131
                        node.list().erase(node.constList().begin() + static_cast<std::vector<Node>::difference_type>(i));
68✔
132
                }
101✔
133
                else  // running on non-macros
134
                {
135
                    bool added_begin = false;
23,195✔
136

137
                    const bool had = hadBegin(node.list()[i]);
23,195✔
138
                    applyMacro(node.list()[i], 0);
23,195✔
139
                    recurApply(node.list()[i]);
23,195✔
140

141
                    if (hadBegin(node.list()[i]) && !had)
23,195✔
142
                        added_begin = true;
319✔
143
                    else if (node.list()[i].nodeType() == NodeType::Unused)
22,876✔
144
                        node.list().erase(node.constList().begin() + static_cast<std::vector<Node>::difference_type>(i));
8✔
145

146
                    if (node.nodeType() == NodeType::List && i < node.constList().size())
23,195✔
147
                    {
148
                        processNode(node.list()[i], depth + 1);
22,921✔
149
                        // needed if we created a function node from a macro
150
                        registerFuncDef(node.list()[i]);
22,921✔
151
                    }
22,921✔
152

153
                    // remove begins in macros
154
                    if (added_begin && i < node.constList().size())
23,195✔
155
                        removeBegin(node, i);
318✔
156

157
                    // go forward only if it isn't a macro, because we delete macros
158
                    // while running on the AST
159
                    ++i;
23,195✔
160
                }
23,195✔
161
            }
162

163
            // delete a scope only if needed
164
            if (!m_macros.empty() && m_macros.back().depth() == depth)
7,576✔
165
                m_macros.pop_back();
56✔
166
        }
7,576✔
167
    }
23,262✔
168

169
    bool MacroProcessor::applyMacro(Node& node, const unsigned depth)
47,256✔
170
    {
47,256✔
171
        if (depth > MaxMacroProcessingDepth)
47,256✔
172
            throwMacroProcessingError(
1✔
173
                fmt::format(
2✔
174
                    "Max macro processing depth reached ({}). You may have a macro trying to evaluate itself, try splitting your code in multiple nodes.",
1✔
175
                    MaxMacroProcessingDepth),
176
                node);
1✔
177

178
        for (const auto& executor : m_executors)
188,378✔
179
        {
180
            if (executor->canHandle(node))
141,337✔
181
            {
182
                if (executor->applyMacro(node, depth))
27,067✔
183
                    return true;
586✔
184
            }
26,267✔
185
        }
141,123✔
186
        return false;
46,562✔
187
    }
47,042✔
188

189
    void MacroProcessor::unify(const std::unordered_map<std::string, Node>& map, Node& target, Node* parent, const std::size_t index, const std::size_t unify_depth)
9,323✔
190
    {
9,323✔
191
        if (unify_depth > MaxMacroUnificationDepth)
9,323✔
192
            throwMacroProcessingError(
1✔
193
                fmt::format(
2✔
194
                    "Max macro unification depth reached ({}). You may have a macro trying to evaluate itself, try splitting your code in multiple nodes.",
1✔
195
                    MaxMacroUnificationDepth),
196
                *parent);
1✔
197

198
        if (target.nodeType() == NodeType::Symbol)
9,322✔
199
        {
200
            if (const auto p = map.find(target.string()); p != map.end())
7,041✔
201
                target = p->second;
2,132✔
202
        }
4,909✔
203
        else if (target.isListLike())
4,413✔
204
        {
205
            for (std::size_t i = 0; i < target.list().size(); ++i)
12,348✔
206
                unify(map, target.list()[i], &target, i, unify_depth + 1);
9,101✔
207
        }
3,247✔
208
        else if (target.nodeType() == NodeType::Spread)
1,166✔
209
        {
210
            Node sub_node = target;
95✔
211
            sub_node.setNodeType(NodeType::Symbol);
95✔
212
            unify(map, sub_node, parent, 0, unify_depth + 1);
95✔
213

214
            if (sub_node.nodeType() != NodeType::List)
95✔
215
                throwMacroProcessingError(fmt::format("Can not unify a {} to a Spread", typeToString(sub_node)), sub_node);
×
216

217
            for (std::size_t i = 1, end = sub_node.list().size(); i < end; ++i)
282✔
218
                parent->list().insert(
374✔
219
                    parent->list().begin() + static_cast<std::vector<Node>::difference_type>(index + i),
187✔
220
                    sub_node.list()[i]);
187✔
221
            parent->list().erase(parent->list().begin() + static_cast<std::vector<Node>::difference_type>(index));  // remove the spread
95✔
222
        }
95✔
223
    }
9,323✔
224

225
    void MacroProcessor::setWithFileAttributes(const Node origin, Node& output, const Node& macro)
8,181✔
226
    {
8,181✔
227
        output = macro;
8,181✔
228
        output.setFilename(origin.filename());
8,181✔
229
        output.setPos(origin.line(), origin.col());
8,181✔
230
    }
8,181✔
231

232
    void MacroProcessor::checkMacroArgCount(const Node& node, std::size_t expected, const std::string& name, const std::string& kind)
103✔
233
    {
103✔
234
        const std::size_t argcount = node.constList().size();
103✔
235
        if (argcount != expected + 1)
103✔
236
            throwMacroProcessingError(
×
237
                fmt::format(
×
238
                    "Interpreting a `{}'{} with {} arguments, expected {}.",
×
239
                    name,
×
240
                    kind.empty() ? kind : " " + kind,
×
241
                    argcount,
242
                    expected),
243
                node);
×
244
    }
103✔
245

246
    Node MacroProcessor::evaluate(Node& node, const unsigned depth, const bool is_not_body)
8,675✔
247
    {
8,675✔
248
        if (node.nodeType() == NodeType::Symbol)
8,675✔
249
        {
250
            const Node* macro = findNearestMacro(node.string());
2,302✔
251
            if (macro != nullptr && macro->constList().size() == 2)
2,302✔
252
                return macro->constList()[1];
147✔
253
            return node;
2,155✔
254
        }
2,302✔
255
        if (node.nodeType() == NodeType::List && !node.constList().empty() && node.list()[0].nodeType() == NodeType::Symbol)
6,373✔
256
        {
257
            const std::string& name = node.list()[0].string();
3,464✔
258
            const std::size_t argcount = node.list().size() - 1;
3,464✔
259

260
            if (const Node* macro = findNearestMacro(name); macro != nullptr)
6,928✔
261
            {
262
                applyMacro(node.list()[0], depth + 1);
315✔
263
                if (node.list()[0].nodeType() == NodeType::Unused)
315✔
264
                    node.list().erase(node.constList().begin());
×
265
            }
315✔
266
            else if (name == "=" && is_not_body)
3,149✔
267
            {
268
                checkMacroArgCount(node, 2, "=", "condition");
8✔
269
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
8✔
270
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
8✔
271
                return (one == two) ? getTrueNode() : getFalseNode();
8✔
272
            }
8✔
273
            else if (name == "!=" && is_not_body)
3,141✔
274
            {
275
                checkMacroArgCount(node, 2, "!=", "condition");
2✔
276
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
2✔
277
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
2✔
278
                return (one != two) ? getTrueNode() : getFalseNode();
2✔
279
            }
2✔
280
            else if (name == "<" && is_not_body)
3,139✔
281
            {
282
                checkMacroArgCount(node, 2, "<", "condition");
2✔
283
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
2✔
284
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
2✔
285
                return (one < two) ? getTrueNode() : getFalseNode();
2✔
286
            }
2✔
287
            else if (name == ">" && is_not_body)
3,137✔
288
            {
289
                checkMacroArgCount(node, 2, ">", "condition");
15✔
290
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
15✔
291
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
15✔
292
                return !(one < two) && (one != two) ? getTrueNode() : getFalseNode();
15✔
293
            }
15✔
294
            else if (name == "<=" && is_not_body)
3,122✔
295
            {
296
                checkMacroArgCount(node, 2, "<=", "condition");
2✔
297
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
2✔
298
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
2✔
299
                return one < two || one == two ? getTrueNode() : getFalseNode();
2✔
300
            }
2✔
301
            else if (name == ">=" && is_not_body)
3,120✔
302
            {
303
                checkMacroArgCount(node, 2, ">=", "condition");
2✔
304
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
2✔
305
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
2✔
306
                return !(one < two) ? getTrueNode() : getFalseNode();
2✔
307
            }
2✔
308
            else if (name == "+" && is_not_body)
3,118✔
309
            {
310
                checkMacroArgCount(node, 2, "+", "operator");
2✔
311
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
2✔
312
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
2✔
313
                return (one.nodeType() == two.nodeType() && two.nodeType() == NodeType::Number) ? Node(one.number() + two.number()) : node;
2✔
314
            }
2✔
315
            else if (name == "-" && is_not_body)
3,116✔
316
            {
317
                checkMacroArgCount(node, 2, "-", "operator");
35✔
318
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
35✔
319
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
35✔
320
                return (one.nodeType() == two.nodeType() && two.nodeType() == NodeType::Number) ? Node(one.number() - two.number()) : node;
35✔
321
            }
35✔
322
            else if (name == "*" && is_not_body)
3,081✔
323
            {
324
                checkMacroArgCount(node, 2, "*", "operator");
×
325
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
×
326
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
×
327
                return (one.nodeType() == two.nodeType() && two.nodeType() == NodeType::Number) ? Node(one.number() * two.number()) : node;
×
328
            }
×
329
            else if (name == "/" && is_not_body)
3,081✔
330
            {
331
                checkMacroArgCount(node, 2, "/", "operator");
×
332
                const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
×
333
                const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
×
334
                return (one.nodeType() == two.nodeType() && two.nodeType() == NodeType::Number) ? Node(one.number() / two.number()) : node;
×
335
            }
×
336
            else if (name == "not" && is_not_body)
3,081✔
337
            {
338
                checkMacroArgCount(node, 1, "not", "condition");
2✔
339
                return (!isTruthy(evaluate(node.list()[1], depth + 1, is_not_body))) ? getTrueNode() : getFalseNode();
2✔
340
            }
341
            else if (name == Language::And && is_not_body)
3,079✔
342
            {
343
                if (node.list().size() < 3)
7✔
344
                    throwMacroProcessingError(fmt::format("Interpreting a `{}' chain with {} arguments, expected at least 2.", Language::And, argcount), node);
1✔
345

346
                for (std::size_t i = 1, end = node.list().size(); i < end; ++i)
16✔
347
                {
348
                    if (!isTruthy(evaluate(node.list()[i], depth + 1, is_not_body)))
10✔
349
                        return getFalseNode();
2✔
350
                }
8✔
351
                return getTrueNode();
4✔
352
            }
353
            else if (name == Language::Or && is_not_body)
3,072✔
354
            {
355
                if (node.list().size() < 3)
5✔
356
                    throwMacroProcessingError(fmt::format("Interpreting an `{}' chain with {} arguments, expected at least 2.", Language::Or, argcount), node);
1✔
357

358
                for (std::size_t i = 1, end = node.list().size(); i < end; ++i)
12✔
359
                {
360
                    if (isTruthy(evaluate(node.list()[i], depth + 1, is_not_body)))
8✔
361
                        return getTrueNode();
2✔
362
                }
6✔
363
                return getFalseNode();
2✔
364
            }
365
            else if (name == "len")
3,067✔
366
            {
367
                if (node.list().size() > 2)
29✔
368
                    throwMacroProcessingError(fmt::format("When expanding `len' inside a macro, got {} arguments, expected 1", argcount), node);
1✔
369
                if (Node& lst = node.list()[1]; lst.nodeType() == NodeType::List)  // only apply len at compile time if we can
53✔
370
                {
371
                    if (isConstEval(lst))
25✔
372
                    {
373
                        if (!lst.list().empty() && lst.list()[0] == getListNode())
24✔
374
                            setWithFileAttributes(node, node, Node(static_cast<long>(lst.list().size()) - 1));
22✔
375
                        else
376
                            setWithFileAttributes(node, node, Node(static_cast<long>(lst.list().size())));
2✔
377
                    }
24✔
378
                }
25✔
379
            }
28✔
380
            else if (name == "empty?")
3,038✔
381
            {
382
                if (node.list().size() > 2)
7✔
383
                    throwMacroProcessingError(fmt::format("When expanding `empty?' inside a macro, got {} arguments, expected 1", argcount), node);
1✔
384
                if (Node& lst = node.list()[1]; lst.nodeType() == NodeType::List && isConstEval(lst))
12✔
385
                {
386
                    // only apply len at compile time if we can
387
                    if (!lst.list().empty() && lst.list()[0] == getListNode())
3✔
388
                        setWithFileAttributes(node, node, lst.list().size() - 1 == 0 ? getTrueNode() : getFalseNode());
1✔
389
                    else
390
                        setWithFileAttributes(node, node, lst.list().empty() ? getTrueNode() : getFalseNode());
2✔
391
                }
3✔
392
                else if (lst == getNilNode())
3✔
393
                    setWithFileAttributes(node, node, getTrueNode());
2✔
394
            }
6✔
395
            else if (name == "@")
3,031✔
396
            {
397
                checkMacroArgCount(node, 2, "@");
33✔
398

399
                Node sublist = evaluate(node.list()[1], depth + 1, is_not_body);
33✔
400
                const Node idx = evaluate(node.list()[2], depth + 1, is_not_body);
33✔
401

402
                if (sublist.nodeType() == NodeType::List && idx.nodeType() == NodeType::Number)
33✔
403
                {
404
                    const std::size_t size = sublist.list().size();
22✔
405
                    std::size_t real_size = size;
22✔
406
                    long num_idx = static_cast<long>(idx.number());
22✔
407

408
                    // if the first node is the function call to "list", don't count it
409
                    if (size > 0 && sublist.list()[0] == getListNode())
22✔
410
                    {
411
                        real_size--;
20✔
412
                        if (num_idx >= 0)
20✔
413
                            ++num_idx;
9✔
414
                    }
20✔
415

416
                    Node output;
22✔
417
                    if (num_idx >= 0 && std::cmp_less(num_idx, size))
22✔
418
                        output = sublist.list()[static_cast<std::size_t>(num_idx)];
11✔
419
                    else if (const auto c = static_cast<long>(size) + num_idx; num_idx < 0 && std::cmp_less(c, size) && c >= 0)
22✔
420
                        output = sublist.list()[static_cast<std::size_t>(c)];
10✔
421
                    else
422
                        throwMacroProcessingError(fmt::format("Index ({}) out of range (list size: {})", num_idx, real_size), node);
1✔
423

424
                    output.setFilename(node.filename());
21✔
425
                    output.setPos(node.line(), node.col());
21✔
426
                    return output;
21✔
427
                }
22✔
428
            }
33✔
429
            else if (name == "head")
2,998✔
430
            {
431
                if (node.list().size() > 2)
13✔
432
                    throwMacroProcessingError(fmt::format("When expanding `head' inside a macro, got {} arguments, expected 1", argcount), node);
1✔
433
                if (node.list()[1].nodeType() == NodeType::List)
12✔
434
                {
435
                    Node& sublist = node.list()[1];
9✔
436
                    if (!sublist.constList().empty() && sublist.constList()[0] == getListNode())
9✔
437
                    {
438
                        if (sublist.constList().size() > 1)
9✔
439
                        {
440
                            const Node sublistCopy = sublist.constList()[1];
6✔
441
                            setWithFileAttributes(node, node, sublistCopy);
6✔
442
                        }
6✔
443
                        else
444
                            setWithFileAttributes(node, node, getNilNode());
3✔
445
                    }
9✔
446
                    else if (!sublist.list().empty())
×
447
                        setWithFileAttributes(node, node, sublist.constList()[0]);
×
448
                    else
449
                        setWithFileAttributes(node, node, getNilNode());
×
450
                }
9✔
451
            }
12✔
452
            else if (name == "tail")
2,985✔
453
            {
454
                if (node.list().size() > 2)
17✔
455
                    throwMacroProcessingError(fmt::format("When expanding `tail' inside a macro, got {} arguments, expected 1", argcount), node);
1✔
456
                if (node.list()[1].nodeType() == NodeType::List)
16✔
457
                {
458
                    Node sublist = node.list()[1];
13✔
459
                    if (!sublist.list().empty() && sublist.list()[0] == getListNode())
13✔
460
                    {
461
                        if (sublist.list().size() > 1)
11✔
462
                        {
463
                            sublist.list().erase(sublist.constList().begin() + 1);
8✔
464
                            setWithFileAttributes(node, node, sublist);
8✔
465
                        }
8✔
466
                        else
467
                        {
468
                            setWithFileAttributes(node, node, Node(NodeType::List));
3✔
469
                            node.push_back(getListNode());
3✔
470
                        }
471
                    }
11✔
472
                    else if (!sublist.list().empty())
2✔
473
                    {
474
                        sublist.list().erase(sublist.constList().begin());
2✔
475
                        sublist.list().insert(sublist.list().begin(), getListNode());
2✔
476
                        setWithFileAttributes(node, node, sublist);
2✔
477
                    }
2✔
478
                    else
479
                    {
480
                        setWithFileAttributes(node, node, Node(NodeType::List));
×
481
                        node.push_back(getListNode());
×
482
                    }
483
                }
13✔
484
            }
16✔
485
            else if (name == Language::Symcat)
2,968✔
486
            {
487
                if (node.list().size() <= 2)
41✔
488
                    throwMacroProcessingError(fmt::format("When expanding `{}', expected at least 2 arguments, got {} arguments", Language::Symcat, argcount), node);
1✔
489
                if (node.list()[1].nodeType() != NodeType::Symbol)
40✔
490
                    throwMacroProcessingError(fmt::format("When expanding `{}', expected the first argument to be a Symbol, got a {}", Language::Symcat, typeToString(node.list()[1])), node);
1✔
491

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

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

498
                    switch (ev.nodeType())
39✔
499
                    {
17✔
500
                        case NodeType::Number:
501
                            // we don't want '.' in identifiers
502
                            sym += std::to_string(static_cast<long int>(ev.number()));
17✔
503
                            break;
38✔
504

505
                        case NodeType::String:
506
                        case NodeType::Symbol:
507
                            sym += ev.string();
21✔
508
                            break;
22✔
509

510
                        default:
511
                            throwMacroProcessingError(fmt::format("When expanding `{}', expected either a Number, String or Symbol, got a {}", Language::Symcat, typeToString(ev)), ev);
1✔
512
                    }
38✔
513
                }
39✔
514

515
                node.setNodeType(NodeType::Symbol);
38✔
516
                node.setString(sym);
38✔
517
            }
39✔
518
            else if (name == Language::Argcount)
2,927✔
519
            {
520
                const Node sym = node.constList()[1];
13✔
521
                if (sym.nodeType() == NodeType::Symbol)
13✔
522
                {
523
                    if (const auto maybe_func = lookupDefinedFunction(sym.string()); maybe_func.has_value())
20✔
524
                        setWithFileAttributes(node, node, Node(static_cast<long>(maybe_func->constList().size())));
9✔
525
                    else
526
                        throwMacroProcessingError(fmt::format("When expanding `{}', expected a known function name, got unbound variable {}", Language::Argcount, sym.string()), sym);
1✔
527
                }
9✔
528
                else if (sym.nodeType() == NodeType::List && sym.constList().size() == 3 && sym.constList()[0].nodeType() == NodeType::Keyword && sym.constList()[0].keyword() == Keyword::Fun)
3✔
529
                    setWithFileAttributes(node, node, Node(static_cast<long>(sym.constList()[1].constList().size())));
3✔
530
                else
531
                    throwMacroProcessingError(fmt::format("When trying to apply `{}', got a {} instead of a Symbol or Function", Language::Argcount, typeToString(sym)), sym);
×
532
            }
13✔
533
            else if (name == Language::Repr)
2,914✔
534
            {
535
                const Node ast = node.constList()[1];
483✔
536
                setWithFileAttributes(node, node, Node(NodeType::String, ast.repr()));
483✔
537
            }
483✔
538
            else if (name == Language::Paste)
2,431✔
539
            {
540
                if (node.list().size() != 2)
946✔
541
                    throwMacroProcessingError(fmt::format("When expanding `{}', expected one argument, got {} arguments", Language::Paste, argcount), node);
1✔
542
                return node.constList()[1];
945✔
543
            }
544
            else if (name == Language::Undef)
1,485✔
545
            {
546
                if (node.list().size() != 2)
9✔
547
                    throwMacroProcessingError(fmt::format("When expanding `{}', expected one argument, got {} arguments", Language::Undef, argcount), node);
×
548

549
                const Node sym = node.constList()[1];
9✔
550
                if (sym.nodeType() == NodeType::Symbol)
9✔
551
                {
552
                    deleteNearestMacro(sym.string());
8✔
553
                    node.setNodeType(NodeType::Unused);
8✔
554
                    return node;
8✔
555
                }
556

557
                throwMacroProcessingError(
1✔
558
                    fmt::format(
2✔
559
                        "When expanding `{}', got a {}. Can not un-define a macro without a valid name",
1✔
560
                        Language::Undef, typeToString(sym)),
1✔
561
                    sym);
562
            }
9✔
563
        }
3,558✔
564

565
        if (node.nodeType() == NodeType::List && !node.constList().empty())
5,220✔
566
        {
567
            for (auto& child : node.list())
10,420✔
568
                setWithFileAttributes(child, child, evaluate(child, depth + 1, is_not_body));
7,821✔
569
        }
2,599✔
570

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

574
        return node;
5,126✔
575
    }
8,675✔
576

577
    bool MacroProcessor::isTruthy(const Node& node)
63✔
578
    {
63✔
579
        if (node.nodeType() == NodeType::Symbol)
63✔
580
        {
581
            if (node.string() == "true")
61✔
582
                return true;
38✔
583
            if (node.string() == "false" || node.string() == "nil")
23✔
584
                return false;
23✔
585
        }
×
586
        else if ((node.nodeType() == NodeType::Number && node.number() != 0.0) || (node.nodeType() == NodeType::String && !node.string().empty()))
2✔
587
            return true;
2✔
588
        else if (node.nodeType() == NodeType::Spread)
×
589
            throwMacroProcessingError("Can not determine the truth value of a spreaded symbol", node);
×
590
        return false;
×
591
    }
63✔
592

593
    std::optional<Node> MacroProcessor::lookupDefinedFunction(const std::string& name) const
10✔
594
    {
10✔
595
        if (m_defined_functions.contains(name))
10✔
596
            return m_defined_functions.at(name);
9✔
597
        return std::nullopt;
1✔
598
    }
10✔
599

600
    const Node* MacroProcessor::findNearestMacro(const std::string& name) const
32,705✔
601
    {
32,705✔
602
        if (m_macros.empty())
32,705✔
603
            return nullptr;
790✔
604

605
        for (const auto& m_macro : std::ranges::reverse_view(m_macros))
70,832✔
606
        {
607
            if (const auto res = m_macro.has(name); res != nullptr)
38,917✔
608
                return res;
1,577✔
609
        }
38,917✔
610
        return nullptr;
30,338✔
611
    }
32,705✔
612

613
    void MacroProcessor::deleteNearestMacro(const std::string& name)
8✔
614
    {
8✔
615
        if (m_macros.empty())
8✔
616
            return;
×
617

618
        for (auto& m_macro : std::ranges::reverse_view(m_macros))
21✔
619
        {
620
            if (m_macro.remove(name))
13✔
621
            {
622
                // stop right here because we found one matching macro
623
                return;
1✔
624
            }
625
        }
13✔
626
    }
8✔
627

628
    void MacroProcessor::recurApply(Node& node)
23,330✔
629
    {
23,330✔
630
        if (applyMacro(node, 0) && node.isListLike())
23,330✔
631
        {
632
            for (auto& child : node.list())
43✔
633
                recurApply(child);
32✔
634
        }
11✔
635
    }
23,330✔
636

637
    bool MacroProcessor::hadBegin(const Node& node)
46,611✔
638
    {
46,611✔
639
        return node.nodeType() == NodeType::List &&
62,103✔
640
            !node.constList().empty() &&
15,492✔
641
            node.constList()[0].nodeType() == NodeType::Keyword &&
19,312✔
642
            node.constList()[0].keyword() == Keyword::Begin;
3,878✔
643
    }
644

645
    void MacroProcessor::removeBegin(Node& node, std::size_t i)
319✔
646
    {
319✔
647
        if (node.isListLike() && node.list()[i].nodeType() == NodeType::List && !node.list()[i].list().empty())
319✔
648
        {
649
            Node lst = node.constList()[i];
319✔
650
            Node first = lst.constList()[0];
319✔
651

652
            if (first.nodeType() == NodeType::Keyword && first.keyword() == Keyword::Begin)
319✔
653
            {
654
                const std::size_t previous = i;
319✔
655

656
                for (std::size_t block_idx = 1, end = lst.constList().size(); block_idx < end; ++block_idx)
799✔
657
                    node.list().insert(
960✔
658
                        node.constList().begin() + static_cast<std::vector<Node>::difference_type>(i + block_idx),
480✔
659
                        lst.list()[block_idx]);
480✔
660

661
                node.list().erase(node.constList().begin() + static_cast<std::vector<Node>::difference_type>(previous));
319✔
662
            }
319✔
663
        }
319✔
664
    }
319✔
665

666
    bool MacroProcessor::isConstEval(const Node& node) const
92✔
667
    {
92✔
668
        switch (node.nodeType())
92✔
669
        {
27✔
670
            case NodeType::Symbol:
671
            {
672
                const auto it = std::ranges::find(Language::operators, node.string());
27✔
673
                const auto it2 = std::ranges::find_if(Builtins::builtins,
27✔
674
                                                      [&node](const std::pair<std::string, Value>& element) -> bool {
1,408✔
675
                                                          return node.string() == element.first;
1,381✔
676
                                                      });
677

678
                return it != Language::operators.end() ||
53✔
679
                    it2 != Builtins::builtins.end() ||
26✔
680
                    findNearestMacro(node.string()) != nullptr ||
24✔
681
                    node.string() == "list" ||
25✔
682
                    node.string() == "nil";
1✔
683
            }
59✔
684

685
            case NodeType::List:
686
                return std::ranges::all_of(node.constList(), [this](const Node& child) {
96✔
687
                    return isConstEval(child);
64✔
688
                });
689

690
            case NodeType::Capture:
691
            case NodeType::Field:
692
                return false;
33✔
693

694
            case NodeType::Keyword:
695
            case NodeType::String:
696
            case NodeType::Number:
697
            case NodeType::Macro:
698
            case NodeType::Spread:
699
            case NodeType::Unused:
700
                return true;
33✔
701
        }
×
702

703
        return false;
×
704
    }
92✔
705

706
    void MacroProcessor::throwMacroProcessingError(const std::string& message, const Node& node)
21✔
707
    {
21✔
708
        throw CodeError(message, node.filename(), node.line(), node.col(), node.repr());
21✔
709
    }
21✔
710
}
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