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

ArkScript-lang / Ark / 14647441324

24 Apr 2025 05:07PM UTC coverage: 83.165% (+2.7%) from 80.417%
14647441324

push

github

SuperFola
feat(vm): compressing identical traces when displaying the stacktrace of an error

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

270 existing lines in 10 files now uncovered.

6580 of 7912 relevant lines covered (83.16%)

79046.5 hits per line

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

93.75
/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp
1
#include <Ark/Compiler/Lowerer/ASTLowerer.hpp>
2

3
#include <limits>
4
#include <utility>
5
#include <algorithm>
6
#include <fmt/core.h>
7
#include <fmt/color.h>
8

9
#include <Ark/Literals.hpp>
10
#include <Ark/Builtins/Builtins.hpp>
11

12
namespace Ark::internal
13
{
14
    using namespace literals;
15

16
    ASTLowerer::ASTLowerer(const unsigned debug) :
227✔
17
        m_logger("ASTLowerer", debug)
227✔
18
    {}
454✔
19

20
    void ASTLowerer::process(const Node& ast)
157✔
21
    {
157✔
22
        m_logger.traceStart("process");
191✔
23
        m_code_pages.emplace_back();  // create empty page
157✔
24

25
        // gather symbols, values, and start to create code segments
26
        compileExpression(
157✔
27
            ast,
157✔
28
            /* current_page */ Page { .index = 0, .is_temp = false },
157✔
29
            /* is_result_unused */ false,
30
            /* is_terminal */ false);
31
        m_logger.traceEnd();
123✔
32
    }
157✔
33

34
    const std::vector<IR::Block>& ASTLowerer::intermediateRepresentation() const noexcept
123✔
35
    {
123✔
36
        return m_code_pages;
123✔
37
    }
38

39
    const std::vector<std::string>& ASTLowerer::symbols() const noexcept
241✔
40
    {
241✔
41
        return m_symbols;
241✔
42
    }
43

44
    const std::vector<ValTableElem>& ASTLowerer::values() const noexcept
241✔
45
    {
241✔
46
        return m_values;
241✔
47
    }
48

49
    std::optional<Instruction> ASTLowerer::getOperator(const std::string& name) noexcept
17,866✔
50
    {
17,866✔
51
        const auto it = std::ranges::find(Language::operators, name);
17,866✔
52
        if (it != Language::operators.end())
17,866✔
53
            return static_cast<Instruction>(std::distance(Language::operators.begin(), it) + FIRST_OPERATOR);
5,044✔
54
        return std::nullopt;
12,822✔
55
    }
17,866✔
56

57
    std::optional<uint16_t> ASTLowerer::getBuiltin(const std::string& name) noexcept
10,998✔
58
    {
10,998✔
59
        const auto it = std::ranges::find_if(Builtins::builtins,
10,998✔
60
                                             [&name](const std::pair<std::string, Value>& element) -> bool {
588,950✔
61
                                                 return name == element.first;
577,952✔
62
                                             });
63
        if (it != Builtins::builtins.end())
10,998✔
64
            return static_cast<uint16_t>(std::distance(Builtins::builtins.begin(), it));
1,105✔
65
        return std::nullopt;
9,893✔
66
    }
10,998✔
67

68
    std::optional<Instruction> ASTLowerer::getListInstruction(const std::string& name) noexcept
11,609✔
69
    {
11,609✔
70
        const auto it = std::ranges::find(Language::listInstructions, name);
11,609✔
71
        if (it != Language::listInstructions.end())
11,609✔
72
            return static_cast<Instruction>(std::distance(Language::listInstructions.begin(), it) + LIST);
3,636✔
73
        return std::nullopt;
7,973✔
74
    }
11,609✔
75

76
    bool ASTLowerer::nodeProducesOutput(const Node& node)
17,015✔
77
    {
17,015✔
78
        if (node.nodeType() == NodeType::List && !node.constList().empty() && node.constList()[0].nodeType() == NodeType::Keyword)
17,015✔
79
            return (node.constList()[0].keyword() == Keyword::Begin && node.constList().size() > 1) ||
378✔
80
                node.constList()[0].keyword() == Keyword::Fun ||
224✔
81
                node.constList()[0].keyword() == Keyword::If;
35✔
82
        return true;  // any other node, function call, symbol, number...
16,826✔
83
    }
17,015✔
84

85
    bool ASTLowerer::isUnaryInst(const Instruction inst) noexcept
5,042✔
86
    {
5,042✔
87
        switch (inst)
5,042✔
88
        {
1,013✔
89
            case NOT: [[fallthrough]];
227✔
90
            case LEN: [[fallthrough]];
91
            case EMPTY: [[fallthrough]];
92
            case TAIL: [[fallthrough]];
93
            case HEAD: [[fallthrough]];
94
            case ISNIL: [[fallthrough]];
95
            case TO_NUM: [[fallthrough]];
96
            case TO_STR: [[fallthrough]];
97
            case TYPE:
98
                return true;
5,042✔
99

100
            default:
101
                return false;
4,029✔
102
        }
103
    }
5,042✔
104

105
    bool ASTLowerer::isTernaryInst(const Instruction inst) noexcept
8,148✔
106
    {
8,148✔
107
        switch (inst)
8,148✔
108
        {
89✔
109
            case AT_AT:
110
                return true;
8,148✔
111

112
            default:
113
                return false;
8,059✔
114
        }
115
    }
8,148✔
116

117
    void ASTLowerer::warning(const std::string& message, const Node& node)
×
118
    {
×
119
        fmt::println("{} {}", fmt::styled("Warning", fmt::fg(fmt::color::dark_orange)), Diagnostics::makeContextWithNode(message, node));
×
120
    }
×
121

122
    void ASTLowerer::buildAndThrowError(const std::string& message, const Node& node)
34✔
123
    {
34✔
124
        throw CodeError(message, node.filename(), node.line(), node.col(), node.repr());
34✔
125
    }
34✔
126

127
    void ASTLowerer::compileExpression(const Node& x, const Page p, const bool is_result_unused, const bool is_terminal, const std::string& var_name)
35,563✔
128
    {
35,563✔
129
        // register symbols
130
        if (x.nodeType() == NodeType::Symbol)
35,563✔
131
            compileSymbol(x, p, is_result_unused);
10,077✔
132
        else if (x.nodeType() == NodeType::Field)
25,486✔
133
        {
134
            // the parser guarantees us that there is at least 2 elements (eg: a.b)
135
            compileSymbol(x.constList()[0], p, is_result_unused);
921✔
136
            for (auto it = x.constList().begin() + 1, end = x.constList().end(); it != end; ++it)
1,850✔
137
            {
138
                uint16_t i = addSymbol(*it);
929✔
139
                page(p).emplace_back(GET_FIELD, i);
929✔
140
            }
929✔
141
            page(p).back().setSourceLocation(x.filename(), x.line());
921✔
142
        }
921✔
143
        // register values
144
        else if (x.nodeType() == NodeType::String || x.nodeType() == NodeType::Number)
24,565✔
145
        {
146
            uint16_t i = addValue(x);
6,460✔
147

148
            if (!is_result_unused)
6,460✔
149
                page(p).emplace_back(LOAD_CONST, i);
6,460✔
150
        }
6,460✔
151
        // namespace nodes
152
        else if (x.nodeType() == NodeType::Namespace)
18,105✔
153
            compileExpression(*x.constArkNamespace().ast, p, is_result_unused, is_terminal, var_name);
89✔
154
        else if (x.nodeType() == NodeType::Unused)
18,016✔
155
        {
156
            // do nothing, explicitly
UNCOV
157
        }
×
158
        // empty code block should be nil
159
        else if (x.constList().empty())
18,016✔
160
        {
UNCOV
161
            if (!is_result_unused)
×
162
            {
163
                static const std::optional<uint16_t> nil = getBuiltin("nil");
78✔
164
                page(p).emplace_back(BUILTIN, nil.value());
×
165
            }
×
UNCOV
166
        }
×
167
        // list instructions
168
        else if (const auto c0 = x.constList()[0]; c0.nodeType() == NodeType::Symbol && getListInstruction(c0.string()).has_value())
36,032✔
169
            compileListInstruction(c0, x, p, is_result_unused);
1,818✔
170
        // registering structures
171
        else if (x.constList()[0].nodeType() == NodeType::Keyword)
16,198✔
172
        {
173
            switch (const Keyword keyword = x.constList()[0].keyword())
7,986✔
174
            {
1,003✔
175
                case Keyword::If:
176
                    compileIf(x, p, is_result_unused, is_terminal, var_name);
1,003✔
177
                    break;
4,659✔
178

179
                case Keyword::Set:
180
                    [[fallthrough]];
181
                case Keyword::Let:
182
                    [[fallthrough]];
183
                case Keyword::Mut:
184
                    compileLetMutSet(keyword, x, p);
3,656✔
185
                    break;
4,939✔
186

187
                case Keyword::Fun:
188
                    compileFunction(x, p, is_result_unused, var_name);
1,287✔
189
                    break;
2,825✔
190

191
                case Keyword::Begin:
192
                {
193
                    for (std::size_t i = 1, size = x.constList().size(); i < size; ++i)
7,836✔
194
                        compileExpression(
12,590✔
195
                            x.constList()[i],
6,295✔
196
                            p,
6,295✔
197
                            // All the nodes in a begin (except for the last one) are producing a result that we want to drop.
198
                            (i != size - 1) || is_result_unused,
6,295✔
199
                            // If the begin is a terminal node, only its last node is terminal.
200
                            is_terminal && (i == size - 1),
6,295✔
201
                            var_name);
6,295✔
202
                    break;
1,507✔
203
                }
497✔
204

205
                case Keyword::While:
206
                    compileWhile(x, p);
497✔
207
                    break;
497✔
208

209
                case Keyword::Import:
210
                    compilePluginImport(x, p);
1✔
211
                    break;
2✔
212

213
                case Keyword::Del:
214
                    page(p).emplace_back(DEL, addSymbol(x.constList()[1]));
1✔
215
                    page(p).back().setSourceLocation(x.constList()[1].filename(), x.constList()[1].line());
1✔
216
                    break;
1✔
217
            }
7,986✔
218
        }
7,944✔
219
        else if (x.nodeType() == NodeType::List)
8,212✔
220
        {
221
            // if we are here, we should have a function name
222
            // push arguments first, then function name, then call it
223
            handleCalls(x, p, is_result_unused, is_terminal, var_name);
8,212✔
224
        }
8,186✔
225
        else
226
            buildAndThrowError(
×
227
                fmt::format(
×
228
                    "NodeType `{}' not handled in ASTLowerer::compileExpression. Please fill an issue on GitHub: https://github.com/ArkScript-lang/Ark",
×
UNCOV
229
                    typeToString(x)),
×
UNCOV
230
                x);
×
231
    }
35,563✔
232

233
    void ASTLowerer::compileSymbol(const Node& x, const Page p, const bool is_result_unused)
10,998✔
234
    {
10,998✔
235
        const std::string& name = x.string();
10,998✔
236

237
        if (const auto it_builtin = getBuiltin(name))
21,996✔
238
            page(p).emplace_back(Instruction::BUILTIN, it_builtin.value());
1,105✔
239
        else if (getOperator(name).has_value())
9,893✔
240
            buildAndThrowError(fmt::format("Found a free standing operator: `{}`", name), x);
1✔
241
        else
242
        {
243
            const std::optional<std::size_t> maybe_local_idx = m_locals_locator.lookupLastScopeByName(name);
9,892✔
244
            if (maybe_local_idx.has_value())
9,892✔
245
                page(p).emplace_back(LOAD_SYMBOL_BY_INDEX, static_cast<uint16_t>(maybe_local_idx.value()));
4,025✔
246
            else
247
                page(p).emplace_back(LOAD_SYMBOL, addSymbol(x));
5,867✔
248
        }
9,892✔
249

250
        if (is_result_unused)
10,997✔
251
        {
252
            warning("Statement has no effect", x);
×
UNCOV
253
            page(p).emplace_back(POP);
×
UNCOV
254
        }
×
255
    }
10,998✔
256

257
    void ASTLowerer::compileListInstruction(const Node& c0, const Node& x, const Page p, const bool is_result_unused)
1,818✔
258
    {
1,818✔
259
        std::string name = c0.string();
1,818✔
260
        Instruction inst = getListInstruction(name).value();
1,818✔
261

262
        // length of at least 1 since we got a symbol name
263
        const auto argc = x.constList().size() - 1u;
1,818✔
264
        // error, can not use append/concat/pop (and their in place versions) with a <2 length argument list
265
        if (argc < 2 && APPEND <= inst && inst <= POP)
1,818✔
266
            buildAndThrowError(fmt::format("Can not use {} with less than 2 arguments", name), c0);
6✔
267
        if (inst <= POP && std::cmp_greater(argc, std::numeric_limits<uint16_t>::max()))
1,812✔
268
            buildAndThrowError(fmt::format("Too many arguments ({}), exceeds 65'535", argc), x);
1✔
269
        if (argc != 3 && inst == SET_AT_INDEX)
1,811✔
270
            buildAndThrowError(fmt::format("Expected 3 arguments (list, index, value) for {}, got {}", name, argc), c0);
1✔
271
        if (argc != 4 && inst == SET_AT_2_INDEX)
1,810✔
272
            buildAndThrowError(fmt::format("Expected 4 arguments (list, y, x, value) for {}, got {}", name, argc), c0);
1✔
273

274
        // compile arguments in reverse order
275
        for (std::size_t i = x.constList().size() - 1u; i > 0; --i)
4,117✔
276
        {
277
            const auto node = x.constList()[i];
2,308✔
278
            if (nodeProducesOutput(node))
2,308✔
279
                compileExpression(node, p, false, false);
2,307✔
280
            else
281
                buildAndThrowError(fmt::format("Invalid node inside call to {}", name), node);
1✔
282
        }
2,308✔
283

284
        // put inst and number of arguments
285
        std::size_t inst_argc = 0;
1,808✔
286
        switch (inst)
1,808✔
287
        {
1,361✔
288
            case LIST:
289
                inst_argc = argc;
1,361✔
290
                break;
1,776✔
291

292
            case APPEND:
293
            case APPEND_IN_PLACE:
294
            case CONCAT:
295
            case CONCAT_IN_PLACE:
296
                inst_argc = argc - 1;
415✔
297
                break;
429✔
298

299
            case POP_LIST:
300
            case POP_LIST_IN_PLACE:
301
                inst_argc = 0;
14✔
302
                break;
32✔
303

304
            default:
305
                break;
18✔
306
        }
1,808✔
307
        page(p).emplace_back(inst, static_cast<uint16_t>(inst_argc));
1,808✔
308
        page(p).back().setSourceLocation(c0.filename(), c0.line());
1,808✔
309

310
        if (is_result_unused && name.back() != '!' && inst <= POP_LIST_IN_PLACE)  // in-place functions never push a value
1,808✔
311
        {
UNCOV
312
            warning("Ignoring return value of function", x);
×
UNCOV
313
            page(p).emplace_back(POP);
×
UNCOV
314
        }
×
315
    }
1,828✔
316

317
    void ASTLowerer::compileIf(const Node& x, const Page p, const bool is_result_unused, const bool is_terminal, const std::string& var_name)
1,003✔
318
    {
1,003✔
319
        // compile condition
320
        compileExpression(x.constList()[1], p, false, false);
1,003✔
321
        page(p).back().setSourceLocation(x.constList()[1].filename(), x.constList()[1].line());
1,003✔
322

323
        // jump only if needed to the "true" branch
324
        const auto label_then = IR::Entity::Label(m_current_label++);
1,003✔
325
        page(p).emplace_back(IR::Entity::GotoIf(label_then, true));
1,003✔
326

327
        // "false" branch code
328
        if (x.constList().size() == 4)  // we have an else clause
1,003✔
329
        {
330
            m_locals_locator.saveScopeLengthForBranch();
838✔
331
            compileExpression(x.constList()[3], p, is_result_unused, is_terminal, var_name);
838✔
332
            page(p).back().setSourceLocation(x.constList()[3].filename(), x.constList()[3].line());
838✔
333
            m_locals_locator.dropVarsForBranch();
838✔
334
        }
838✔
335

336
        // when else is finished, jump to end
337
        const auto label_end = IR::Entity::Label(m_current_label++);
1,003✔
338
        page(p).emplace_back(IR::Entity::Goto(label_end));
1,003✔
339

340
        // absolute address to jump to if condition is true
341
        page(p).emplace_back(label_then);
1,003✔
342
        // if code
343
        m_locals_locator.saveScopeLengthForBranch();
1,003✔
344
        compileExpression(x.constList()[2], p, is_result_unused, is_terminal, var_name);
1,003✔
345
        page(p).back().setSourceLocation(x.constList()[2].filename(), x.constList()[2].line());
1,003✔
346
        m_locals_locator.dropVarsForBranch();
1,003✔
347
        // set jump to end pos
348
        page(p).emplace_back(label_end);
1,003✔
349
    }
1,003✔
350

351
    void ASTLowerer::compileFunction(const Node& x, const Page p, const bool is_result_unused, const std::string& var_name)
1,287✔
352
    {
1,287✔
353
        if (const auto args = x.constList()[1]; args.nodeType() != NodeType::List)
1,289✔
354
            buildAndThrowError(fmt::format("Expected a well formed argument(s) list, got a {}", typeToString(args)), args);
1✔
355
        if (x.constList().size() != 3)
1,286✔
356
            buildAndThrowError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
1✔
357

358
        // capture, if needed
359
        std::size_t capture_inst_count = 0;
1,285✔
360
        for (const auto& node : x.constList()[1].constList())
3,418✔
361
        {
362
            if (node.nodeType() == NodeType::Capture)
2,133✔
363
            {
364
                page(p).emplace_back(CAPTURE, addSymbol(node));
167✔
365
                ++capture_inst_count;
167✔
366
            }
167✔
367
        }
2,133✔
368
        const bool is_closure = capture_inst_count > 0;
1,285✔
369

370
        m_locals_locator.createScope(
2,570✔
371
            is_closure
1,285✔
372
                ? LocalsLocator::ScopeType::Closure
373
                : LocalsLocator::ScopeType::Function);
374

375
        // create new page for function body
376
        m_code_pages.emplace_back();
1,285✔
377
        const auto function_body_page = Page { .index = m_code_pages.size() - 1, .is_temp = false };
1,285✔
378
        // save page_id into the constants table as PageAddr and load the const
379
        page(p).emplace_back(is_closure ? MAKE_CLOSURE : LOAD_CONST, addValue(function_body_page.index, x));
1,285✔
380

381
        // pushing arguments from the stack into variables in the new scope
382
        for (const auto& node : x.constList()[1].constList())
3,418✔
383
        {
384
            if (node.nodeType() == NodeType::Symbol)
2,133✔
385
            {
386
                page(function_body_page).emplace_back(STORE, addSymbol(node));
1,966✔
387
                m_locals_locator.addLocal(node.string());
1,966✔
388
            }
1,966✔
389
        }
2,133✔
390

391
        // push body of the function
392
        compileExpression(x.constList()[2], function_body_page, false, true, var_name);
1,285✔
393

394
        // return last value on the stack
395
        page(function_body_page).emplace_back(RET);
1,285✔
396
        m_locals_locator.deleteScope();
1,285✔
397

398
        // if the computed function is unused, pop it
399
        if (is_result_unused)
1,285✔
400
        {
UNCOV
401
            warning("Unused declared function", x);
×
UNCOV
402
            page(p).emplace_back(POP);
×
403
        }
×
404
    }
1,287✔
405

406
    void ASTLowerer::compileLetMutSet(const Keyword n, const Node& x, const Page p)
3,656✔
407
    {
3,656✔
408
        if (const auto sym = x.constList()[1]; sym.nodeType() != NodeType::Symbol)
3,660✔
UNCOV
409
            buildAndThrowError(fmt::format("Expected a symbol, got a {}", typeToString(sym)), sym);
×
410
        if (x.constList().size() != 3)
3,656✔
411
            buildAndThrowError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
1✔
412

413
        const std::string name = x.constList()[1].string();
3,655✔
414
        uint16_t i = addSymbol(x.constList()[1]);
3,655✔
415

416
        // put value before symbol id
417
        // starting at index = 2 because x is a (let|mut|set variable ...) node
418
        for (std::size_t idx = 2, end = x.constList().size(); idx < end; ++idx)
7,310✔
419
            compileExpression(x.constList()[idx], p, false, false, name);
3,655✔
420

421
        if (n == Keyword::Let || n == Keyword::Mut)
3,652✔
422
        {
423
            page(p).emplace_back(STORE, i);
2,735✔
424
            m_locals_locator.addLocal(name);
2,735✔
425
        }
2,735✔
426
        else
427
            page(p).emplace_back(SET_VAL, i);
917✔
428

429
        page(p).back().setSourceLocation(x.filename(), x.line());
3,652✔
430
    }
3,656✔
431

432
    void ASTLowerer::compileWhile(const Node& x, const Page p)
497✔
433
    {
497✔
434
        if (x.constList().size() != 3)
497✔
435
            buildAndThrowError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
1✔
436

437
        m_locals_locator.createScope();
496✔
438
        page(p).emplace_back(CREATE_SCOPE);
496✔
439
        page(p).back().setSourceLocation(x.filename(), x.line());
496✔
440

441
        // save current position to jump there at the end of the loop
442
        const auto label_loop = IR::Entity::Label(m_current_label++);
496✔
443
        page(p).emplace_back(label_loop);
496✔
444
        // push condition
445
        compileExpression(x.constList()[1], p, false, false);
496✔
446
        // absolute jump to end of block if condition is false
447
        const auto label_end = IR::Entity::Label(m_current_label++);
496✔
448
        page(p).emplace_back(IR::Entity::GotoIf(label_end, false));
496✔
449
        // push code to page
450
        compileExpression(x.constList()[2], p, true, false);
496✔
451

452
        // reset the scope at the end of the loop so that indices are still valid
453
        // otherwise, (while true { (let a 5) (print a) (let b 6) (print b) })
454
        // would print 5, 6, then only 6 as we emit LOAD_SYMBOL_FROM_INDEX 0 and b is the last in the scope
455
        page(p).emplace_back(RESET_SCOPE);
496✔
456
        // loop, jump to the condition
457
        page(p).emplace_back(IR::Entity::Goto(label_loop));
496✔
458

459
        // absolute address to jump to if condition is false
460
        page(p).emplace_back(label_end);
496✔
461

462
        page(p).emplace_back(POP_SCOPE);
496✔
463
        m_locals_locator.deleteScope();
496✔
464
    }
497✔
465

466
    void ASTLowerer::compilePluginImport(const Node& x, const Page p)
1✔
467
    {
1✔
468
        std::string path;
1✔
469
        const Node package_node = x.constList()[1];
1✔
470
        for (std::size_t i = 0, end = package_node.constList().size(); i < end; ++i)
2✔
471
        {
472
            path += package_node.constList()[i].string();
1✔
473
            if (i + 1 != end)
1✔
UNCOV
474
                path += "/";
×
475
        }
1✔
476
        path += ".arkm";
1✔
477

478
        // register plugin path in the constants table
479
        uint16_t id = addValue(Node(NodeType::String, path));
1✔
480
        // add plugin instruction + id of the constant referring to the plugin path
481
        page(p).emplace_back(PLUGIN, id);
1✔
482
        page(p).back().setSourceLocation(x.filename(), x.line());
1✔
483
    }
1✔
484

485
    void ASTLowerer::handleCalls(const Node& x, const Page p, bool is_result_unused, const bool is_terminal, const std::string& var_name)
8,212✔
486
    {
8,212✔
487
        constexpr std::size_t start_index = 1;
8,212✔
488

489
        const auto node = x.constList()[0];
8,212✔
490
        const std::optional<Instruction> maybe_operator = node.nodeType() == NodeType::Symbol ? getOperator(node.string()) : std::nullopt;
8,212✔
491

492
        enum class ShortcircuitOp
493
        {
494
            And,
495
            Or
496
        };
497
        const std::optional<ShortcircuitOp> maybe_shortcircuit =
8,212✔
498
            node.nodeType() == NodeType::Symbol
16,185✔
499
            ? (node.string() == Language::And
15,828✔
500
                   ? std::make_optional(ShortcircuitOp::And)
118✔
501
                   : (node.string() == Language::Or
7,855✔
502
                          ? std::make_optional(ShortcircuitOp::Or)
10✔
503
                          : std::nullopt))
7,845✔
504
            : std::nullopt;
239✔
505

506
        if (maybe_shortcircuit.has_value())
8,212✔
507
        {
508
            // short circuit implementation
509
            if (x.constList().size() < 3)
128✔
510
                buildAndThrowError(
2✔
511
                    fmt::format(
6✔
512
                        "Expected at least 2 arguments while compiling '{}', got {}",
2✔
513
                        node.string(),
2✔
514
                        x.constList().size() - 1),
2✔
515
                    x);
2✔
516

517
            compileExpression(x.constList()[1], p, false, false);
126✔
518
            page(p).emplace_back(DUP);
126✔
519

520
            const auto label_shortcircuit = IR::Entity::Label(m_current_label++);
126✔
521
            for (std::size_t i = 2, end = x.constList().size(); i < end; ++i)
261✔
522
            {
523
                switch (maybe_shortcircuit.value())
135✔
524
                {
122✔
525
                    case ShortcircuitOp::And:
526
                        page(p).emplace_back(IR::Entity::GotoIf(label_shortcircuit, false));
122✔
527
                        break;
135✔
528
                    case ShortcircuitOp::Or:
529
                        page(p).emplace_back(IR::Entity::GotoIf(label_shortcircuit, true));
13✔
530
                        break;
13✔
531
                }
135✔
532
                page(p).emplace_back(POP);
135✔
533

534
                compileExpression(x.constList()[i], p, false, false);
135✔
535
                if (i + 1 != end)
135✔
536
                    page(p).emplace_back(DUP);
9✔
537
            }
135✔
538

539
            page(p).emplace_back(label_shortcircuit);
126✔
540
        }
126✔
541
        else if (!maybe_operator.has_value())
8,084✔
542
        {
543
            if (is_terminal && x.constList()[0].nodeType() == NodeType::Symbol && var_name == x.constList()[0].string())
3,041✔
544
            {
545
                // push the arguments in reverse order
546
                for (std::size_t i = x.constList().size() - 1; i >= start_index; --i)
239✔
547
                {
548
                    if (nodeProducesOutput(x.constList()[i]))
172✔
549
                        compileExpression(x.constList()[i], p, false, false);
171✔
550
                    else
551
                        buildAndThrowError(fmt::format("Invalid node inside tail call to `{}'", node.repr()), x);
1✔
552
                }
171✔
553

554
                // jump to the top of the function
555
                page(p).emplace_back(JUMP, 0_u16);
66✔
556
                page(p).back().setSourceLocation(node.filename(), node.line());
66✔
557
                return;  // skip the potential Instruction::POP at the end
66✔
558
            }
559
            else
560
            {
561
                m_temp_pages.emplace_back();
2,974✔
562
                const auto proc_page = Page { .index = m_temp_pages.size() - 1u, .is_temp = true };
2,974✔
563
                // closure chains have been handled (eg: closure.field.field.function)
564
                compileExpression(node, proc_page, false, false);  // storing proc
2,974✔
565
                if (m_temp_pages.back().empty())
2,971✔
566
                    buildAndThrowError(fmt::format("Can not call {}", x.constList()[0].repr()), x);
1✔
567

568
                // push arguments on current page
569
                for (auto exp = x.constList().begin() + start_index, exp_end = x.constList().end(); exp != exp_end; ++exp)
8,345✔
570
                {
571
                    if (nodeProducesOutput(*exp))
5,375✔
572
                        compileExpression(*exp, p, false, false);
5,374✔
573
                    else
574
                        buildAndThrowError(fmt::format("Invalid node inside call to `{}'", node.repr()), x);
1✔
575
                }
5,370✔
576
                // push proc from temp page
577
                for (const auto& inst : m_temp_pages.back())
6,190✔
578
                    page(p).push_back(inst);
3,225✔
579
                m_temp_pages.pop_back();
2,965✔
580

581
                // number of arguments
582
                std::size_t args_count = 0;
2,965✔
583
                for (auto it = x.constList().begin() + 1, it_end = x.constList().end(); it != it_end; ++it)
8,333✔
584
                {
585
                    if (it->nodeType() != NodeType::Capture)
5,368✔
586
                        args_count++;
5,368✔
587
                }
5,368✔
588
                // call the procedure
589
                page(p).emplace_back(CALL, args_count);
2,965✔
590
                page(p).back().setSourceLocation(node.filename(), node.line());
2,965✔
591
            }
2,974✔
592
        }
2,965✔
593
        else  // operator
594
        {
595
            // retrieve operator
596
            auto op = maybe_operator.value();
5,043✔
597

598
            if (op == ASSERT)
5,043✔
599
                is_result_unused = false;
186✔
600

601
            // push arguments on current page
602
            std::size_t exp_count = 0;
5,043✔
603
            for (std::size_t index = start_index, size = x.constList().size(); index < size; ++index)
14,203✔
604
            {
605
                if (nodeProducesOutput(x.constList()[index]))
9,160✔
606
                    compileExpression(x.constList()[index], p, false, false);
9,159✔
607
                else
608
                    buildAndThrowError(fmt::format("Invalid node inside call to operator `{}'", node.repr()), x);
1✔
609

610
                if ((index + 1 < size && x.constList()[index + 1].nodeType() != NodeType::Capture) || index + 1 == size)
9,159✔
611
                    exp_count++;
9,159✔
612

613
                // in order to be able to handle things like (op A B C D...)
614
                // which should be transformed into A B op C op D op...
615
                if (exp_count >= 2 && !isTernaryInst(op))
9,159✔
616
                    page(p).emplace_back(op);
4,060✔
617
            }
9,159✔
618

619
            if (isUnaryInst(op))
5,042✔
620
            {
621
                if (exp_count != 1)
1,013✔
622
                    buildAndThrowError(fmt::format("Operator needs one argument, but was called with {}", exp_count), x.constList()[0]);
1✔
623
                page(p).emplace_back(op);
1,012✔
624
            }
1,012✔
625
            else if (isTernaryInst(op))
4,029✔
626
            {
627
                if (exp_count != 3)
30✔
628
                    buildAndThrowError(fmt::format("Operator needs three arguments, but was called with {}", exp_count), x.constList()[0]);
1✔
629
                page(p).emplace_back(op);
29✔
630
            }
29✔
631
            else if (exp_count <= 1)
3,999✔
632
                buildAndThrowError(fmt::format("Operator needs two arguments, but was called with {}", exp_count), x.constList()[0]);
2✔
633

634
            page(p).back().setSourceLocation(x.filename(), x.line());
5,038✔
635

636
            // need to check we didn't push the (op A B C D...) things for operators not supporting it
637
            if (exp_count > 2)
5,038✔
638
            {
639
                switch (op)
79✔
640
                {
70✔
641
                    // authorized instructions
642
                    case ADD: [[fallthrough]];
643
                    case SUB: [[fallthrough]];
644
                    case MUL: [[fallthrough]];
645
                    case DIV: [[fallthrough]];
646
                    case MOD: [[fallthrough]];
647
                    case AT_AT:
648
                        break;
79✔
649

650
                    default:
651
                        buildAndThrowError(
9✔
652
                            fmt::format(
18✔
653
                                "`{}' requires 2 arguments, but got {}.",
9✔
654
                                Language::operators[static_cast<std::size_t>(op - FIRST_OPERATOR)],
9✔
655
                                exp_count),
656
                            x);
9✔
657
                }
70✔
658
            }
70✔
659
        }
5,043✔
660

661
        if (is_result_unused)
8,120✔
662
            page(p).emplace_back(POP);
1,221✔
663
    }
8,238✔
664

665
    uint16_t ASTLowerer::addSymbol(const Node& sym)
12,585✔
666
    {
12,585✔
667
        // otherwise, add the symbol, and return its id in the table
668
        auto it = std::ranges::find(m_symbols, sym.string());
12,585✔
669
        if (it == m_symbols.end())
12,585✔
670
        {
671
            m_symbols.push_back(sym.string());
2,587✔
672
            it = m_symbols.begin() + static_cast<std::vector<std::string>::difference_type>(m_symbols.size() - 1);
2,587✔
673
        }
2,587✔
674

675
        const auto distance = std::distance(m_symbols.begin(), it);
12,585✔
676
        if (distance < std::numeric_limits<uint16_t>::max())
12,585✔
677
            return static_cast<uint16_t>(distance);
25,170✔
UNCOV
678
        buildAndThrowError("Too many symbols (exceeds 65'536), aborting compilation.", sym);
×
679
    }
12,585✔
680

681
    uint16_t ASTLowerer::addValue(const Node& x)
6,461✔
682
    {
6,461✔
683
        const ValTableElem v(x);
6,461✔
684
        auto it = std::ranges::find(m_values, v);
6,461✔
685
        if (it == m_values.end())
6,461✔
686
        {
687
            m_values.push_back(v);
1,883✔
688
            it = m_values.begin() + static_cast<std::vector<ValTableElem>::difference_type>(m_values.size() - 1);
1,883✔
689
        }
1,883✔
690

691
        const auto distance = std::distance(m_values.begin(), it);
6,461✔
692
        if (distance < std::numeric_limits<uint16_t>::max())
6,461✔
693
            return static_cast<uint16_t>(distance);
6,461✔
UNCOV
694
        buildAndThrowError("Too many values (exceeds 65'536), aborting compilation.", x);
×
695
    }
6,461✔
696

697
    uint16_t ASTLowerer::addValue(const std::size_t page_id, const Node& current)
1,285✔
698
    {
1,285✔
699
        const ValTableElem v(page_id);
1,285✔
700
        auto it = std::ranges::find(m_values, v);
1,285✔
701
        if (it == m_values.end())
1,285✔
702
        {
703
            m_values.push_back(v);
1,285✔
704
            it = m_values.begin() + static_cast<std::vector<ValTableElem>::difference_type>(m_values.size() - 1);
1,285✔
705
        }
1,285✔
706

707
        const auto distance = std::distance(m_values.begin(), it);
1,285✔
708
        if (distance < std::numeric_limits<uint16_t>::max())
1,285✔
709
            return static_cast<uint16_t>(distance);
1,285✔
UNCOV
710
        buildAndThrowError("Too many values (exceeds 65'536), aborting compilation.", current);
×
711
    }
1,285✔
712
}
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