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

ArkScript-lang / Ark / 14012476381

22 Mar 2025 09:21PM UTC coverage: 79.378% (+0.5%) from 78.852%
14012476381

Pull #519

github

web-flow
Merge 8dcea74d7 into 4aa4303da
Pull Request #519: Feat/better locals

273 of 307 new or added lines in 14 files covered. (88.93%)

27 existing lines in 2 files now uncovered.

6047 of 7618 relevant lines covered (79.38%)

77956.04 hits per line

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

93.56
/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) :
198✔
17
        m_logger("ASTLowerer", debug)
198✔
18
    {}
396✔
19

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

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

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

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

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

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

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

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

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

85
    bool ASTLowerer::isUnaryInst(const Instruction inst) noexcept
4,774✔
86
    {
4,774✔
87
        switch (inst)
4,774✔
88
        {
961✔
89
            case NOT: [[fallthrough]];
198✔
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;
4,774✔
99

100
            default:
101
                return false;
3,813✔
102
        }
103
    }
4,774✔
104

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

112
            default:
113
                return false;
7,629✔
114
        }
115
    }
7,715✔
116

NEW
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)
33,957✔
128
    {
33,957✔
129
        // register symbols
130
        if (x.nodeType() == NodeType::Symbol)
33,957✔
131
            compileSymbol(x, p, is_result_unused);
9,592✔
132
        else if (x.nodeType() == NodeType::Field)
24,365✔
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);
893✔
136
            for (auto it = x.constList().begin() + 1, end = x.constList().end(); it != end; ++it)
1,794✔
137
            {
138
                uint16_t i = addSymbol(*it);
901✔
139
                page(p).emplace_back(GET_FIELD, i);
901✔
140
            }
901✔
141
        }
893✔
142
        // register values
143
        else if (x.nodeType() == NodeType::String || x.nodeType() == NodeType::Number)
23,472✔
144
        {
145
            uint16_t i = addValue(x);
6,221✔
146

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

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

186
                case Keyword::Fun:
187
                    compileFunction(x, p, is_result_unused, var_name);
1,232✔
188
                    break;
2,678✔
189

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

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

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

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

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

235
        if (const auto it_builtin = getBuiltin(name))
20,970✔
236
            page(p).emplace_back(Instruction::BUILTIN, it_builtin.value());
1,080✔
237
        else if (getOperator(name).has_value())
9,405✔
238
            buildAndThrowError(fmt::format("Found a free standing operator: `{}`", name), x);
1✔
239
        else
240
        {
241
            const std::optional<std::size_t> maybe_local_idx = m_locals_locator.lookupLastScopeByName(name);
9,404✔
242
            if (maybe_local_idx.has_value())
9,404✔
243
                page(p).emplace_back(LOAD_SYMBOL_BY_INDEX, static_cast<uint16_t>(maybe_local_idx.value()));
3,812✔
244
            else
245
                page(p).emplace_back(LOAD_SYMBOL, addSymbol(x));
5,592✔
246
        }
9,404✔
247

248
        if (is_result_unused)
10,484✔
249
        {
NEW
250
            warning("Statement has no effect", x);
×
251
            page(p).emplace_back(POP);
×
252
        }
×
253
    }
10,485✔
254

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

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

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

282
        // put inst and number of arguments
283
        std::size_t inst_argc = 0;
1,748✔
284
        switch (inst)
1,748✔
285
        {
1,332✔
286
            case LIST:
287
                inst_argc = argc;
1,332✔
288
                break;
1,721✔
289

290
            case APPEND:
291
            case APPEND_IN_PLACE:
292
            case CONCAT:
293
            case CONCAT_IN_PLACE:
294
                inst_argc = argc - 1;
389✔
295
                break;
401✔
296

297
            case POP_LIST:
298
            case POP_LIST_IN_PLACE:
299
                inst_argc = 0;
12✔
300
                break;
27✔
301

302
            default:
303
                break;
15✔
304
        }
1,748✔
305
        page(p).emplace_back(inst, static_cast<uint16_t>(inst_argc));
1,748✔
306

307
        if (is_result_unused && name.back() != '!' && inst <= POP_LIST_IN_PLACE)  // in-place functions never push a value
1,748✔
308
        {
NEW
309
            warning("Ignoring return value of function", x);
×
310
            page(p).emplace_back(POP);
×
311
        }
×
312
    }
1,768✔
313

314
    void ASTLowerer::compileIf(const Node& x, const Page p, const bool is_result_unused, const bool is_terminal, const std::string& var_name)
964✔
315
    {
964✔
316
        // compile condition
317
        compileExpression(x.constList()[1], p, false, false);
964✔
318

319
        // jump only if needed to the if
320
        const auto label_then = IR::Entity::Label(m_current_label++);
964✔
321
        page(p).emplace_back(IR::Entity::GotoIf(label_then, true));
964✔
322

323
        // else code
324
        if (x.constList().size() == 4)  // we have an else clause
964✔
325
        {
326
            m_locals_locator.saveScopeLengthForBranch();
811✔
327
            compileExpression(x.constList()[3], p, is_result_unused, is_terminal, var_name);
811✔
328
            m_locals_locator.dropVarsForBranch();
811✔
329
        }
811✔
330

331
        // when else is finished, jump to end
332
        const auto label_end = IR::Entity::Label(m_current_label++);
964✔
333
        page(p).emplace_back(IR::Entity::Goto(label_end));
964✔
334

335
        // absolute address to jump to if condition is true
336
        page(p).emplace_back(label_then);
964✔
337
        // if code
338
        m_locals_locator.saveScopeLengthForBranch();
964✔
339
        compileExpression(x.constList()[2], p, is_result_unused, is_terminal, var_name);
964✔
340
        m_locals_locator.dropVarsForBranch();
964✔
341
        // set jump to end pos
342
        page(p).emplace_back(label_end);
964✔
343
    }
964✔
344

345
    void ASTLowerer::compileFunction(const Node& x, const Page p, const bool is_result_unused, const std::string& var_name)
1,232✔
346
    {
1,232✔
347
        if (const auto args = x.constList()[1]; args.nodeType() != NodeType::List)
1,234✔
348
            buildAndThrowError(fmt::format("Expected a well formed argument(s) list, got a {}", typeToString(args)), args);
1✔
349
        if (x.constList().size() != 3)
1,231✔
350
            buildAndThrowError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
1✔
351

352
        // capture, if needed
353
        std::size_t capture_inst_count = 0;
1,230✔
354
        for (const auto& node : x.constList()[1].constList())
3,272✔
355
        {
356
            if (node.nodeType() == NodeType::Capture)
2,042✔
357
            {
358
                page(p).emplace_back(CAPTURE, addSymbol(node));
165✔
359
                ++capture_inst_count;
165✔
360
            }
165✔
361
        }
2,042✔
362
        const bool is_closure = capture_inst_count > 0;
1,230✔
363

364
        m_locals_locator.createScope(
2,460✔
365
            is_closure
1,230✔
366
                ? LocalsLocator::ScopeType::Closure
367
                : LocalsLocator::ScopeType::Function);
368

369
        // create new page for function body
370
        m_code_pages.emplace_back();
1,230✔
371
        const auto function_body_page = Page { .index = m_code_pages.size() - 1, .is_temp = false };
1,230✔
372
        // save page_id into the constants table as PageAddr and load the const
373
        page(p).emplace_back(is_closure ? MAKE_CLOSURE : LOAD_CONST, addValue(function_body_page.index, x));
1,230✔
374

375
        // pushing arguments from the stack into variables in the new scope
376
        for (const auto& node : x.constList()[1].constList())
3,272✔
377
        {
378
            if (node.nodeType() == NodeType::Symbol)
2,042✔
379
            {
380
                page(function_body_page).emplace_back(STORE, addSymbol(node));
1,877✔
381
                m_locals_locator.addLocal(node.string());
1,877✔
382
            }
1,877✔
383
        }
2,042✔
384

385
        // push body of the function
386
        compileExpression(x.constList()[2], function_body_page, false, true, var_name);
1,230✔
387

388
        // return last value on the stack
389
        page(function_body_page).emplace_back(RET);
1,230✔
390
        m_locals_locator.deleteScope();
1,230✔
391

392
        // if the computed function is unused, pop it
393
        if (is_result_unused)
1,230✔
394
        {
NEW
395
            warning("Unused declared function", x);
×
396
            page(p).emplace_back(POP);
×
397
        }
×
398
    }
1,232✔
399

400
    void ASTLowerer::compileLetMutSet(const Keyword n, const Node& x, const Page p)
3,439✔
401
    {
3,439✔
402
        if (const auto sym = x.constList()[1]; sym.nodeType() != NodeType::Symbol)
3,443✔
NEW
403
            buildAndThrowError(fmt::format("Expected a symbol, got a {}", typeToString(sym)), sym);
×
404
        if (x.constList().size() != 3)
3,439✔
405
            buildAndThrowError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
1✔
406

407
        const std::string name = x.constList()[1].string();
3,438✔
408
        uint16_t i = addSymbol(x.constList()[1]);
3,438✔
409

410
        // put value before symbol id
411
        // starting at index = 2 because x is a (let|mut|set variable ...) node
412
        for (std::size_t idx = 2, end = x.constList().size(); idx < end; ++idx)
6,876✔
413
            compileExpression(x.constList()[idx], p, false, false, name);
3,438✔
414

415
        if (n == Keyword::Let || n == Keyword::Mut)
3,435✔
416
        {
417
            page(p).emplace_back(STORE, i);
2,580✔
418
            m_locals_locator.addLocal(name);
2,580✔
419
        }
2,580✔
420
        else
421
            page(p).emplace_back(SET_VAL, i);
855✔
422
    }
3,439✔
423

424
    void ASTLowerer::compileWhile(const Node& x, const Page p)
468✔
425
    {
468✔
426
        if (x.constList().size() != 3)
468✔
427
            buildAndThrowError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
1✔
428

429
        m_locals_locator.createScope();
467✔
430
        page(p).emplace_back(CREATE_SCOPE);
467✔
431

432
        // save current position to jump there at the end of the loop
433
        const auto label_loop = IR::Entity::Label(m_current_label++);
467✔
434
        page(p).emplace_back(label_loop);
467✔
435
        // push condition
436
        compileExpression(x.constList()[1], p, false, false);
467✔
437
        // absolute jump to end of block if condition is false
438
        const auto label_end = IR::Entity::Label(m_current_label++);
467✔
439
        page(p).emplace_back(IR::Entity::GotoIf(label_end, false));
467✔
440
        // push code to page
441
        compileExpression(x.constList()[2], p, true, false);
467✔
442

443
        // loop, jump to the condition
444
        page(p).emplace_back(IR::Entity::Goto(label_loop));
467✔
445

446
        // absolute address to jump to if condition is false
447
        page(p).emplace_back(label_end);
467✔
448

449
        page(p).emplace_back(POP_SCOPE);
467✔
450
        m_locals_locator.deleteScope();
467✔
451
    }
468✔
452

453
    void ASTLowerer::compilePluginImport(const Node& x, const Page p)
1✔
454
    {
1✔
455
        std::string path;
1✔
456
        const Node package_node = x.constList()[1];
1✔
457
        for (std::size_t i = 0, end = package_node.constList().size(); i < end; ++i)
2✔
458
        {
459
            path += package_node.constList()[i].string();
1✔
460
            if (i + 1 != end)
1✔
461
                path += "/";
×
462
        }
1✔
463
        path += ".arkm";
1✔
464

465
        // register plugin path in the constants table
466
        uint16_t id = addValue(Node(NodeType::String, path));
1✔
467
        // add plugin instruction + id of the constant referring to the plugin path
468
        page(p).emplace_back(PLUGIN, id);
1✔
469
    }
1✔
470

471
    void ASTLowerer::handleCalls(const Node& x, const Page p, bool is_result_unused, const bool is_terminal, const std::string& var_name)
7,852✔
472
    {
7,852✔
473
        constexpr std::size_t start_index = 1;
7,852✔
474

475
        const auto node = x.constList()[0];
7,852✔
476
        const std::optional<Instruction> maybe_operator = node.nodeType() == NodeType::Symbol ? getOperator(node.string()) : std::nullopt;
7,852✔
477

478
        enum class ShortcircuitOp
479
        {
480
            And,
481
            Or
482
        };
483
        const std::optional<ShortcircuitOp> maybe_shortcircuit =
7,852✔
484
            node.nodeType() == NodeType::Symbol
15,465✔
485
            ? (node.string() == Language::And
15,113✔
486
                   ? std::make_optional(ShortcircuitOp::And)
113✔
487
                   : (node.string() == Language::Or
7,500✔
488
                          ? std::make_optional(ShortcircuitOp::Or)
10✔
489
                          : std::nullopt))
7,490✔
490
            : std::nullopt;
239✔
491

492
        if (maybe_shortcircuit.has_value())
7,852✔
493
        {
494
            // short circuit implementation
495
            if (x.constList().size() < 3)
123✔
496
                buildAndThrowError(
2✔
497
                    fmt::format(
6✔
498
                        "Expected at least 2 arguments while compiling '{}', got {}",
2✔
499
                        node.string(),
2✔
500
                        x.constList().size() - 1),
2✔
501
                    x);
2✔
502

503
            compileExpression(x.constList()[1], p, false, false);
121✔
504
            page(p).emplace_back(DUP);
121✔
505

506
            const auto label_shortcircuit = IR::Entity::Label(m_current_label++);
121✔
507
            for (std::size_t i = 2, end = x.constList().size(); i < end; ++i)
251✔
508
            {
509
                switch (maybe_shortcircuit.value())
130✔
510
                {
117✔
511
                    case ShortcircuitOp::And:
512
                        page(p).emplace_back(IR::Entity::GotoIf(label_shortcircuit, false));
117✔
513
                        break;
130✔
514
                    case ShortcircuitOp::Or:
515
                        page(p).emplace_back(IR::Entity::GotoIf(label_shortcircuit, true));
13✔
516
                        break;
13✔
517
                }
130✔
518
                page(p).emplace_back(POP);
130✔
519

520
                compileExpression(x.constList()[i], p, false, false);
130✔
521
                if (i + 1 != end)
130✔
522
                    page(p).emplace_back(DUP);
9✔
523
            }
130✔
524

525
            page(p).emplace_back(label_shortcircuit);
121✔
526
        }
121✔
527
        else if (!maybe_operator.has_value())
7,729✔
528
        {
529
            if (is_terminal && x.constList()[0].nodeType() == NodeType::Symbol && var_name == x.constList()[0].string())
2,954✔
530
            {
531
                // push the arguments in reverse order
532
                for (std::size_t i = x.constList().size() - 1; i >= start_index; --i)
224✔
533
                {
534
                    if (nodeProducesOutput(x.constList()[i]))
161✔
535
                        compileExpression(x.constList()[i], p, false, false);
160✔
536
                    else
537
                        buildAndThrowError(fmt::format("Invalid node inside tail call to `{}'", node.repr()), x);
1✔
538
                }
160✔
539

540
                // jump to the top of the function
541
                page(p).emplace_back(JUMP, 0_u16);
62✔
542
                return;  // skip the potential Instruction::POP at the end
62✔
543
            }
544
            else
545
            {
546
                m_temp_pages.emplace_back();
2,891✔
547
                const auto proc_page = Page { .index = m_temp_pages.size() - 1u, .is_temp = true };
2,891✔
548
                // closure chains have been handled (eg: closure.field.field.function)
549
                compileExpression(node, proc_page, false, false);  // storing proc
2,891✔
550
                if (m_temp_pages.back().empty())
2,888✔
551
                    buildAndThrowError(fmt::format("Can not call {}", x.constList()[0].repr()), x);
1✔
552

553
                // push arguments on current page
554
                for (auto exp = x.constList().begin() + start_index, exp_end = x.constList().end(); exp != exp_end; ++exp)
8,117✔
555
                {
556
                    if (nodeProducesOutput(*exp))
5,230✔
557
                        compileExpression(*exp, p, false, false);
5,229✔
558
                    else
559
                        buildAndThrowError(fmt::format("Invalid node inside call to `{}'", node.repr()), x);
1✔
560
                }
5,225✔
561
                // push proc from temp page
562
                for (const auto& inst : m_temp_pages.back())
6,024✔
563
                    page(p).push_back(inst);
3,142✔
564
                m_temp_pages.pop_back();
2,882✔
565

566
                // number of arguments
567
                std::size_t args_count = 0;
2,882✔
568
                for (auto it = x.constList().begin() + 1, it_end = x.constList().end(); it != it_end; ++it)
8,105✔
569
                {
570
                    if (it->nodeType() != NodeType::Capture)
5,223✔
571
                        args_count++;
5,223✔
572
                }
5,223✔
573
                // call the procedure
574
                page(p).emplace_back(CALL, args_count);
2,882✔
575
            }
2,891✔
576
        }
2,882✔
577
        else  // operator
578
        {
579
            // retrieve operator
580
            auto op = maybe_operator.value();
4,775✔
581

582
            if (op == ASSERT)
4,775✔
583
                is_result_unused = false;
177✔
584

585
            // push arguments on current page
586
            std::size_t exp_count = 0;
4,775✔
587
            for (std::size_t index = start_index, size = x.constList().size(); index < size; ++index)
13,450✔
588
            {
589
                if (nodeProducesOutput(x.constList()[index]))
8,675✔
590
                    compileExpression(x.constList()[index], p, false, false);
8,674✔
591
                else
592
                    buildAndThrowError(fmt::format("Invalid node inside call to operator `{}'", node.repr()), x);
1✔
593

594
                if ((index + 1 < size && x.constList()[index + 1].nodeType() != NodeType::Capture) || index + 1 == size)
8,674✔
595
                    exp_count++;
8,674✔
596

597
                // in order to be able to handle things like (op A B C D...)
598
                // which should be transformed into A B op C op D op...
599
                if (exp_count >= 2 && !isTernaryInst(op))
8,674✔
600
                    page(p).emplace_back(op);
3,845✔
601
            }
8,674✔
602

603
            if (isUnaryInst(op))
4,774✔
604
            {
605
                if (exp_count != 1)
961✔
606
                    buildAndThrowError(fmt::format("Operator needs one argument, but was called with {}", exp_count), x.constList()[0]);
1✔
607
                page(p).emplace_back(op);
960✔
608
            }
960✔
609
            else if (isTernaryInst(op))
3,813✔
610
            {
611
                if (exp_count != 3)
29✔
612
                    buildAndThrowError(fmt::format("Operator needs three arguments, but was called with {}", exp_count), x.constList()[0]);
1✔
613
                page(p).emplace_back(op);
28✔
614
            }
28✔
615
            else if (exp_count <= 1)
3,784✔
616
                buildAndThrowError(fmt::format("Operator needs two arguments, but was called with {}", exp_count), x.constList()[0]);
2✔
617

618
            // need to check we didn't push the (op A B C D...) things for operators not supporting it
619
            if (exp_count > 2)
4,770✔
620
            {
621
                switch (op)
78✔
622
                {
69✔
623
                    // authorized instructions
624
                    case ADD: [[fallthrough]];
625
                    case SUB: [[fallthrough]];
626
                    case MUL: [[fallthrough]];
627
                    case DIV: [[fallthrough]];
628
                    case MOD: [[fallthrough]];
629
                    case AT_AT:
630
                        break;
78✔
631

632
                    default:
633
                        buildAndThrowError(
9✔
634
                            fmt::format(
18✔
635
                                "`{}' requires 2 arguments, but got {}.",
9✔
636
                                Language::operators[static_cast<std::size_t>(op - FIRST_OPERATOR)],
9✔
637
                                exp_count),
638
                            x);
9✔
639
                }
69✔
640
            }
69✔
641
        }
4,775✔
642

643
        if (is_result_unused)
7,764✔
644
            page(p).emplace_back(POP);
1,210✔
645
    }
7,878✔
646

647
    uint16_t ASTLowerer::addSymbol(const Node& sym)
11,974✔
648
    {
11,974✔
649
        // otherwise, add the symbol, and return its id in the table
650
        auto it = std::ranges::find(m_symbols, sym.string());
11,974✔
651
        if (it == m_symbols.end())
11,974✔
652
        {
653
            m_symbols.push_back(sym.string());
2,454✔
654
            it = m_symbols.begin() + static_cast<std::vector<std::string>::difference_type>(m_symbols.size() - 1);
2,454✔
655
        }
2,454✔
656

657
        const auto distance = std::distance(m_symbols.begin(), it);
11,974✔
658
        if (distance < std::numeric_limits<uint16_t>::max())
11,974✔
659
            return static_cast<uint16_t>(distance);
23,948✔
NEW
660
        buildAndThrowError("Too many symbols (exceeds 65'536), aborting compilation.", sym);
×
661
    }
11,974✔
662

663
    uint16_t ASTLowerer::addValue(const Node& x)
6,222✔
664
    {
6,222✔
665
        const ValTableElem v(x);
6,222✔
666
        auto it = std::ranges::find(m_values, v);
6,222✔
667
        if (it == m_values.end())
6,222✔
668
        {
669
            m_values.push_back(v);
1,788✔
670
            it = m_values.begin() + static_cast<std::vector<ValTableElem>::difference_type>(m_values.size() - 1);
1,788✔
671
        }
1,788✔
672

673
        const auto distance = std::distance(m_values.begin(), it);
6,222✔
674
        if (distance < std::numeric_limits<uint16_t>::max())
6,222✔
675
            return static_cast<uint16_t>(distance);
6,222✔
NEW
676
        buildAndThrowError("Too many values (exceeds 65'536), aborting compilation.", x);
×
677
    }
6,222✔
678

679
    uint16_t ASTLowerer::addValue(const std::size_t page_id, const Node& current)
1,230✔
680
    {
1,230✔
681
        const ValTableElem v(page_id);
1,230✔
682
        auto it = std::ranges::find(m_values, v);
1,230✔
683
        if (it == m_values.end())
1,230✔
684
        {
685
            m_values.push_back(v);
1,230✔
686
            it = m_values.begin() + static_cast<std::vector<ValTableElem>::difference_type>(m_values.size() - 1);
1,230✔
687
        }
1,230✔
688

689
        const auto distance = std::distance(m_values.begin(), it);
1,230✔
690
        if (distance < std::numeric_limits<uint16_t>::max())
1,230✔
691
            return static_cast<uint16_t>(distance);
1,230✔
NEW
692
        buildAndThrowError("Too many values (exceeds 65'536), aborting compilation.", current);
×
693
    }
1,230✔
694
}
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