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

ArkScript-lang / Ark / 12320388836

13 Dec 2024 05:39PM UTC coverage: 79.237% (+1.9%) from 77.378%
12320388836

push

github

SuperFola
feat(ast optimizer): adding AST optimization tests and fixing the json compiler to accept (if cond then) and not just (if cond then else)

59 of 68 new or added lines in 3 files covered. (86.76%)

14 existing lines in 5 files now uncovered.

5667 of 7152 relevant lines covered (79.24%)

14746.86 hits per line

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

89.78
/src/arkreactor/Compiler/Compiler.cpp
1
#include <Ark/Compiler/Compiler.hpp>
2

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

11
#include <Ark/Constants.hpp>
12
#include <Ark/Literals.hpp>
13
#include <Ark/Utils.hpp>
14
#include <Ark/Builtins/Builtins.hpp>
15
#include <Ark/Compiler/Macros/Processor.hpp>
16

17
namespace Ark::internal
18
{
19
    using namespace literals;
20

21
    Compiler::Compiler(const unsigned debug) :
150✔
22
        m_debug(debug), m_logger("Compiler", debug)
150✔
23
    {}
300✔
24

25
    void Compiler::process(const Node& ast)
80✔
26
    {
80✔
27
        m_logger.traceStart("process");
113✔
28
        m_code_pages.emplace_back();  // create empty page
80✔
29

30
        // gather symbols, values, and start to create code segments
31
        compileExpression(
80✔
32
            ast,
80✔
33
            /* current_page */ Page { .index = 0, .is_temp = false },
80✔
34
            /* is_result_unused */ false,
35
            /* is_terminal */ false);
36
        m_logger.traceEnd();
47✔
37
    }
80✔
38

39
    const std::vector<IR::Block>& Compiler::intermediateRepresentation() const noexcept
47✔
40
    {
47✔
41
        return m_code_pages;
47✔
42
    }
43

44
    const std::vector<std::string>& Compiler::symbols() const noexcept
90✔
45
    {
90✔
46
        return m_symbols;
90✔
47
    }
48

49
    const std::vector<ValTableElem>& Compiler::values() const noexcept
90✔
50
    {
90✔
51
        return m_values;
90✔
52
    }
53

54
    std::optional<Instruction> Compiler::getOperator(const std::string& name) noexcept
3,312✔
55
    {
3,312✔
56
        const auto it = std::ranges::find(Language::operators, name);
3,312✔
57
        if (it != Language::operators.end())
3,312✔
58
            return static_cast<Instruction>(std::distance(Language::operators.begin(), it) + FIRST_OPERATOR);
805✔
59
        return std::nullopt;
2,507✔
60
    }
3,312✔
61

62
    std::optional<uint16_t> Compiler::getBuiltin(const std::string& name) noexcept
2,099✔
63
    {
2,099✔
64
        const auto it = std::ranges::find_if(Builtins::builtins,
2,099✔
65
                                             [&name](const std::pair<std::string, Value>& element) -> bool {
96,677✔
66
                                                 return name == element.first;
94,578✔
67
                                             });
68
        if (it != Builtins::builtins.end())
2,099✔
69
            return static_cast<uint16_t>(std::distance(Builtins::builtins.begin(), it));
493✔
70
        return std::nullopt;
1,606✔
71
    }
2,099✔
72

73
    std::optional<Instruction> Compiler::getListInstruction(const std::string& name) noexcept
2,828✔
74
    {
2,828✔
75
        const auto it = std::ranges::find(Language::listInstructions, name);
2,828✔
76
        if (it != Language::listInstructions.end())
2,828✔
77
            return static_cast<Instruction>(std::distance(Language::listInstructions.begin(), it) + LIST);
1,122✔
78
        return std::nullopt;
1,706✔
79
    }
2,828✔
80

81
    bool Compiler::nodeProducesOutput(const Node& node)
4,176✔
82
    {
4,176✔
83
        if (node.nodeType() == NodeType::List && !node.constList().empty() && node.constList()[0].nodeType() == NodeType::Keyword)
4,176✔
84
            return (node.constList()[0].keyword() == Keyword::Begin && node.constList().size() > 1) ||
34✔
85
                node.constList()[0].keyword() == Keyword::Fun ||
25✔
86
                node.constList()[0].keyword() == Keyword::If;
158✔
87
        return true;  // any other node, function call, symbol, number...
4,159✔
88
    }
4,176✔
89

90
    bool Compiler::isUnaryInst(const Instruction inst) noexcept
803✔
91
    {
803✔
92
        switch (inst)
803✔
93
        {
179✔
94
            case NOT: [[fallthrough]];
95
            case LEN: [[fallthrough]];
96
            case EMPTY: [[fallthrough]];
97
            case TAIL: [[fallthrough]];
98
            case HEAD: [[fallthrough]];
99
            case ISNIL: [[fallthrough]];
100
            case TO_NUM: [[fallthrough]];
101
            case TO_STR: [[fallthrough]];
102
            case TYPE:
103
                return true;
803✔
104

105
            default:
106
                return false;
624✔
107
        }
108
    }
803✔
109

110
    void Compiler::compilerWarning(const std::string& message, const Node& node)
×
111
    {
×
112
        fmt::println("{} {}", fmt::styled("Warning", fmt::fg(fmt::color::dark_orange)), Diagnostics::makeContextWithNode(message, node));
×
113
    }
×
114

115
    void Compiler::throwCompilerError(const std::string& message, const Node& node)
33✔
116
    {
33✔
117
        throw CodeError(message, node.filename(), node.line(), node.col(), node.repr());
33✔
118
    }
33✔
119

120
    void Compiler::compileExpression(const Node& x, const Page p, const bool is_result_unused, const bool is_terminal, const std::string& var_name)
8,048✔
121
    {
8,048✔
122
        // register symbols
123
        if (x.nodeType() == NodeType::Symbol)
8,048✔
124
            compileSymbol(x, p, is_result_unused);
1,844✔
125
        else if (x.nodeType() == NodeType::Field)
6,204✔
126
        {
127
            // the parser guarantees us that there is at least 2 elements (eg: a.b)
128
            compileSymbol(x.constList()[0], p, is_result_unused);
255✔
129
            for (auto it = x.constList().begin() + 1, end = x.constList().end(); it != end; ++it)
518✔
130
            {
131
                uint16_t i = addSymbol(*it);
263✔
132
                page(p).emplace_back(GET_FIELD, i);
263✔
133
            }
263✔
134
        }
255✔
135
        // register values
136
        else if (x.nodeType() == NodeType::String || x.nodeType() == NodeType::Number)
5,949✔
137
        {
138
            uint16_t i = addValue(x);
2,317✔
139

140
            if (!is_result_unused)
2,317✔
141
                page(p).emplace_back(LOAD_CONST, i);
2,317✔
142
        }
2,317✔
143
        // namespace nodes
144
        else if (x.nodeType() == NodeType::Namespace)
3,632✔
145
            compileExpression(*x.constArkNamespace().ast, p, is_result_unused, is_terminal, var_name);
21✔
146
        else if (x.nodeType() == NodeType::Unused)
3,611✔
147
        {
148
            // do nothing, explicitly
NEW
149
        }
×
150
        // empty code block should be nil
151
        else if (x.constList().empty())
3,611✔
152
        {
153
            if (!is_result_unused)
×
154
            {
155
                static const std::optional<uint16_t> nil = getBuiltin("nil");
75✔
156
                page(p).emplace_back(BUILTIN, nil.value());
×
157
            }
×
158
        }
×
159
        // list instructions
160
        else if (const auto c0 = x.constList()[0]; c0.nodeType() == NodeType::Symbol && getListInstruction(c0.string()).has_value())
7,222✔
161
            compileListInstruction(c0, x, p, is_result_unused);
561✔
162
        // registering structures
163
        else if (x.constList()[0].nodeType() == NodeType::Keyword)
3,050✔
164
        {
165
            switch (const Keyword keyword = x.constList()[0].keyword())
1,194✔
166
            {
316✔
167
                case Keyword::If:
168
                    compileIf(x, p, is_result_unused, is_terminal, var_name);
316✔
169
                    break;
749✔
170

171
                case Keyword::Set:
172
                    [[fallthrough]];
173
                case Keyword::Let:
174
                    [[fallthrough]];
175
                case Keyword::Mut:
176
                    compileLetMutSet(keyword, x, p);
433✔
177
                    break;
563✔
178

179
                case Keyword::Fun:
180
                    compileFunction(x, p, is_result_unused, var_name);
134✔
181
                    break;
412✔
182

183
                case Keyword::Begin:
184
                {
185
                    for (std::size_t i = 1, size = x.constList().size(); i < size; ++i)
1,428✔
186
                        compileExpression(
2,294✔
187
                            x.constList()[i],
1,147✔
188
                            p,
1,147✔
189
                            // All the nodes in a begin (except for the last one) are producing a result that we want to drop.
190
                            (i != size - 1) || is_result_unused,
1,147✔
191
                            // If the begin is a terminal node, only its last node is terminal.
192
                            is_terminal && (i == size - 1),
1,147✔
193
                            var_name);
1,147✔
194
                    break;
248✔
195
                }
29✔
196

197
                case Keyword::While:
198
                    compileWhile(x, p);
29✔
199
                    break;
28✔
200

201
                case Keyword::Import:
202
                    compilePluginImport(x, p);
×
203
                    break;
1✔
204

205
                case Keyword::Del:
206
                    page(p).emplace_back(DEL, addSymbol(x.constList()[1]));
1✔
207
                    break;
1✔
208
            }
1,194✔
209
        }
1,153✔
210
        else if (x.nodeType() == NodeType::List)
1,856✔
211
        {
212
            // if we are here, we should have a function name
213
            // push arguments first, then function name, then call it
214
            handleCalls(x, p, is_result_unused, is_terminal, var_name);
1,856✔
215
        }
1,832✔
216
        else
217
            throwCompilerError(
×
218
                fmt::format(
×
219
                    "NodeType `{}' not handled in Compiler::compileExpression. Please fill an issue on GitHub: https://github.com/ArkScript-lang/Ark",
×
220
                    typeToString(x)),
×
221
                x);
×
222
    }
8,048✔
223

224
    void Compiler::compileSymbol(const Node& x, const Page p, const bool is_result_unused)
2,099✔
225
    {
2,099✔
226
        const std::string& name = x.string();
2,099✔
227

228
        if (const auto it_builtin = getBuiltin(name))
4,198✔
229
            page(p).emplace_back(Instruction::BUILTIN, it_builtin.value());
493✔
230
        else if (getOperator(name).has_value())
1,606✔
231
            throwCompilerError(fmt::format("Found a free standing operator: `{}`", name), x);
1✔
232
        else
233
            page(p).emplace_back(LOAD_SYMBOL, addSymbol(x));  // using the variable
1,605✔
234

235
        if (is_result_unused)
2,098✔
236
        {
237
            compilerWarning("Statement has no effect", x);
×
238
            page(p).emplace_back(POP);
×
239
        }
×
240
    }
2,099✔
241

242
    void Compiler::compileListInstruction(const Node& c0, const Node& x, const Page p, const bool is_result_unused)
561✔
243
    {
561✔
244
        std::string name = c0.string();
561✔
245
        Instruction inst = getListInstruction(name).value();
561✔
246

247
        // length of at least 1 since we got a symbol name
248
        const auto argc = x.constList().size() - 1u;
561✔
249
        // error, can not use append/concat/pop (and their in place versions) with a <2 length argument list
250
        if (argc < 2 && APPEND <= inst && inst <= POP)
561✔
251
            throwCompilerError(fmt::format("Can not use {} with less than 2 arguments", name), c0);
6✔
252
        if (inst <= POP && std::cmp_greater(argc, std::numeric_limits<uint16_t>::max()))
555✔
253
            throwCompilerError(fmt::format("Too many arguments ({}), exceeds 65'535", argc), x);
1✔
254
        if (argc != 3 && inst == SET_AT_INDEX)
554✔
255
            throwCompilerError(fmt::format("Expected 3 arguments (list, index, value) for {}, got {}", name, argc), c0);
1✔
256
        if (argc != 4 && inst == SET_AT_2_INDEX)
553✔
257
            throwCompilerError(fmt::format("Expected 4 arguments (list, y, x, value) for {}, got {}", name, argc), c0);
1✔
258

259
        // compile arguments in reverse order
260
        for (std::size_t i = x.constList().size() - 1u; i > 0; --i)
1,214✔
261
        {
262
            const auto node = x.constList()[i];
662✔
263
            if (nodeProducesOutput(node))
662✔
264
                compileExpression(node, p, false, false);
661✔
265
            else
266
                throwCompilerError(fmt::format("Invalid node inside call to {}", name), node);
1✔
267
        }
662✔
268

269
        // put inst and number of arguments
270
        std::size_t inst_argc = 0;
551✔
271
        switch (inst)
551✔
272
        {
499✔
273
            case LIST:
274
                inst_argc = argc;
499✔
275
                break;
526✔
276

277
            case APPEND:
278
            case APPEND_IN_PLACE:
279
            case CONCAT:
280
            case CONCAT_IN_PLACE:
281
                inst_argc = argc - 1;
27✔
282
                break;
38✔
283

284
            case POP_LIST:
285
            case POP_LIST_IN_PLACE:
286
                inst_argc = 0;
11✔
287
                break;
25✔
288

289
            default:
290
                break;
14✔
291
        }
551✔
292
        page(p).emplace_back(inst, static_cast<uint16_t>(inst_argc));
551✔
293

294
        if (is_result_unused && name.back() != '!' && inst <= POP_LIST_IN_PLACE)  // in-place functions never push a value
551✔
295
        {
296
            compilerWarning("Ignoring return value of function", x);
×
297
            page(p).emplace_back(POP);
×
298
        }
×
299
    }
571✔
300

301
    void Compiler::compileIf(const Node& x, const Page p, const bool is_result_unused, const bool is_terminal, const std::string& var_name)
316✔
302
    {
316✔
303
        // compile condition
304
        compileExpression(x.constList()[1], p, false, false);
316✔
305

306
        // jump only if needed to the if
307
        const auto label_then = IR::Entity::Label(m_current_label++);
316✔
308
        page(p).emplace_back(IR::Entity::GotoIf(label_then, true));
316✔
309

310
        // else code
311
        if (x.constList().size() == 4)  // we have an else clause
316✔
312
            compileExpression(x.constList()[3], p, is_result_unused, is_terminal, var_name);
306✔
313

314
        // when else is finished, jump to end
315
        const auto label_end = IR::Entity::Label(m_current_label++);
316✔
316
        page(p).emplace_back(IR::Entity::Goto(label_end));
316✔
317

318
        // absolute address to jump to if condition is true
319
        page(p).emplace_back(label_then);
316✔
320
        // if code
321
        compileExpression(x.constList()[2], p, is_result_unused, is_terminal, var_name);
316✔
322
        // set jump to end pos
323
        page(p).emplace_back(label_end);
316✔
324
    }
316✔
325

326
    void Compiler::compileFunction(const Node& x, const Page p, const bool is_result_unused, const std::string& var_name)
134✔
327
    {
134✔
328
        if (const auto args = x.constList()[1]; args.nodeType() != NodeType::List)
136✔
329
            throwCompilerError(fmt::format("Expected a well formed argument(s) list, got a {}", typeToString(args)), args);
1✔
330
        if (x.constList().size() != 3)
133✔
331
            throwCompilerError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
1✔
332

333
        // capture, if needed
334
        bool is_closure = false;
132✔
335
        for (const auto& node : x.constList()[1].constList())
351✔
336
        {
337
            if (node.nodeType() == NodeType::Capture)
219✔
338
            {
339
                page(p).emplace_back(CAPTURE, addSymbol(node));
55✔
340
                is_closure = true;
55✔
341
            }
55✔
342
        }
219✔
343

344
        // create new page for function body
345
        m_code_pages.emplace_back();
132✔
346
        const auto function_body_page = Page { .index = m_code_pages.size() - 1, .is_temp = false };
132✔
347
        // save page_id into the constants table as PageAddr and load the const
348
        page(p).emplace_back(is_closure ? MAKE_CLOSURE : LOAD_CONST, addValue(function_body_page.index, x));
132✔
349

350
        // pushing arguments from the stack into variables in the new scope
351
        for (const auto& node : x.constList()[1].constList())
351✔
352
        {
353
            if (node.nodeType() == NodeType::Symbol)
219✔
354
                page(function_body_page).emplace_back(STORE, addSymbol(node));
164✔
355
        }
219✔
356

357
        // push body of the function
358
        compileExpression(x.constList()[2], function_body_page, false, true, var_name);
132✔
359

360
        // return last value on the stack
361
        page(function_body_page).emplace_back(RET);
132✔
362

363
        // if the computed function is unused, pop it
364
        if (is_result_unused)
132✔
365
        {
366
            compilerWarning("Unused declared function", x);
×
367
            page(p).emplace_back(POP);
×
368
        }
×
369
    }
134✔
370

371
    void Compiler::compileLetMutSet(const Keyword n, const Node& x, const Page p)
433✔
372
    {
433✔
373
        if (const auto sym = x.constList()[1]; sym.nodeType() != NodeType::Symbol)
437✔
374
            throwCompilerError(fmt::format("Expected a symbol, got a {}", typeToString(sym)), sym);
×
375
        if (x.constList().size() != 3)
433✔
376
            throwCompilerError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
1✔
377

378
        const std::string name = x.constList()[1].string();
432✔
379
        uint16_t i = addSymbol(x.constList()[1]);
432✔
380

381
        // put value before symbol id
382
        // starting at index = 2 because x is a (let|mut|set variable ...) node
383
        for (std::size_t idx = 2, end = x.constList().size(); idx < end; ++idx)
864✔
384
            compileExpression(x.constList()[idx], p, false, false, name);
432✔
385

386
        if (n == Keyword::Let || n == Keyword::Mut)
429✔
387
            page(p).emplace_back(STORE, i);
356✔
388
        else
389
            page(p).emplace_back(SET_VAL, i);
73✔
390
    }
433✔
391

392
    void Compiler::compileWhile(const Node& x, const Page p)
29✔
393
    {
29✔
394
        if (x.constList().size() != 3)
29✔
395
            throwCompilerError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
1✔
396

397
        // save current position to jump there at the end of the loop
398
        const auto label_loop = IR::Entity::Label(m_current_label++);
28✔
399
        page(p).emplace_back(label_loop);
28✔
400
        // push condition
401
        compileExpression(x.constList()[1], p, false, false);
28✔
402
        // absolute jump to end of block if condition is false
403
        const auto label_end = IR::Entity::Label(m_current_label++);
28✔
404
        page(p).emplace_back(IR::Entity::GotoIf(label_end, false));
28✔
405
        // push code to page
406
        compileExpression(x.constList()[2], p, true, false);
28✔
407

408
        // loop, jump to the condition
409
        page(p).emplace_back(IR::Entity::Goto(label_loop));
28✔
410

411
        // absolute address to jump to if condition is false
412
        page(p).emplace_back(label_end);
28✔
413
    }
29✔
414

415
    void Compiler::compilePluginImport(const Node& x, const Page p)
×
416
    {
×
417
        std::string path;
×
418
        const Node package_node = x.constList()[1];
×
419
        for (std::size_t i = 0, end = package_node.constList().size(); i < end; ++i)
×
420
        {
421
            path += package_node.constList()[i].string();
×
422
            if (i + 1 != end)
×
423
                path += "/";
×
424
        }
×
425
        path += ".arkm";
×
426

427
        // register plugin path in the constants table
428
        uint16_t id = addValue(Node(NodeType::String, path));
×
429
        // add plugin instruction + id of the constant referring to the plugin path
430
        page(p).emplace_back(PLUGIN, id);
×
431
    }
×
432

433
    void Compiler::handleCalls(const Node& x, const Page p, bool is_result_unused, const bool is_terminal, const std::string& var_name)
1,856✔
434
    {
1,856✔
435
        constexpr std::size_t start_index = 1;
1,856✔
436

437
        const auto node = x.constList()[0];
1,856✔
438
        const std::optional<Instruction> maybe_operator = node.nodeType() == NodeType::Symbol ? getOperator(node.string()) : std::nullopt;
1,856✔
439

440
        enum class ShortcircuitOp
441
        {
442
            And,
443
            Or
444
        };
445
        const std::optional<ShortcircuitOp> maybe_shortcircuit =
1,856✔
446
            node.nodeType() == NodeType::Symbol
3,562✔
447
            ? (node.string() == Language::And
3,397✔
448
                   ? std::make_optional(ShortcircuitOp::And)
15✔
449
                   : (node.string() == Language::Or
1,691✔
450
                          ? std::make_optional(ShortcircuitOp::Or)
7✔
451
                          : std::nullopt))
1,684✔
452
            : std::nullopt;
150✔
453

454
        if (maybe_shortcircuit.has_value())
1,856✔
455
        {
456
            // short circuit implementation
457
            if (x.constList().size() < 3)
22✔
458
                throwCompilerError(
2✔
459
                    fmt::format(
6✔
460
                        "Expected at least 2 arguments while compiling '{}', got {}",
2✔
461
                        node.string(),
2✔
462
                        x.constList().size() - 1),
2✔
463
                    x);
2✔
464

465
            compileExpression(x.constList()[1], p, false, false);
20✔
466
            page(p).emplace_back(DUP);
20✔
467

468
            const auto label_shortcircuit = IR::Entity::Label(m_current_label++);
20✔
469
            for (std::size_t i = 2, end = x.constList().size(); i < end; ++i)
48✔
470
            {
471
                switch (maybe_shortcircuit.value())
28✔
472
                {
18✔
473
                    case ShortcircuitOp::And:
474
                        page(p).emplace_back(IR::Entity::GotoIf(label_shortcircuit, false));
18✔
475
                        break;
28✔
476
                    case ShortcircuitOp::Or:
477
                        page(p).emplace_back(IR::Entity::GotoIf(label_shortcircuit, true));
10✔
478
                        break;
10✔
479
                }
28✔
480
                page(p).emplace_back(POP);
28✔
481

482
                compileExpression(x.constList()[i], p, false, false);
28✔
483
                if (i + 1 != end)
28✔
484
                    page(p).emplace_back(DUP);
8✔
485
            }
28✔
486

487
            page(p).emplace_back(label_shortcircuit);
20✔
488
        }
20✔
489
        else if (!maybe_operator.has_value())
1,834✔
490
        {
491
            if (is_terminal && x.constList()[0].nodeType() == NodeType::Symbol && var_name == x.constList()[0].string())
1,030✔
492
            {
493
                // push the arguments in reverse order
494
                for (std::size_t i = x.constList().size() - 1; i >= start_index; --i)
25✔
495
                {
496
                    if (nodeProducesOutput(x.constList()[i]))
17✔
497
                        compileExpression(x.constList()[i], p, false, false);
16✔
498
                    else
499
                        throwCompilerError(fmt::format("Invalid node inside tail call to `{}'", node.repr()), x);
1✔
500
                }
16✔
501

502
                // jump to the top of the function
503
                page(p).emplace_back(JUMP, 0_u16);
7✔
504
                return;  // skip the potential Instruction::POP at the end
7✔
505
            }
506
            else
507
            {
508
                m_temp_pages.emplace_back();
1,022✔
509
                const auto proc_page = Page { .index = m_temp_pages.size() - 1u, .is_temp = true };
1,022✔
510
                // closure chains have been handled (eg: closure.field.field.function)
511
                compileExpression(node, proc_page, false, false);  // storing proc
1,022✔
512
                if (m_temp_pages.back().empty())
1,019✔
513
                    throwCompilerError(fmt::format("Can not call {}", x.constList()[0].repr()), x);
1✔
514

515
                // push arguments on current page
516
                for (auto exp = x.constList().begin() + start_index, exp_end = x.constList().end(); exp != exp_end; ++exp)
3,056✔
517
                {
518
                    if (nodeProducesOutput(*exp))
2,038✔
519
                        compileExpression(*exp, p, false, false);
2,037✔
520
                    else
521
                        throwCompilerError(fmt::format("Invalid node inside call to `{}'", node.repr()), x);
1✔
522
                }
2,034✔
523
                // push proc from temp page
524
                for (const auto& inst : m_temp_pages.back())
2,181✔
525
                    page(p).push_back(inst);
1,167✔
526
                m_temp_pages.pop_back();
1,014✔
527

528
                // number of arguments
529
                std::size_t args_count = 0;
1,014✔
530
                for (auto it = x.constList().begin() + 1, it_end = x.constList().end(); it != it_end; ++it)
3,046✔
531
                {
532
                    if (it->nodeType() != NodeType::Capture)
2,032✔
533
                        args_count++;
2,032✔
534
                }
2,032✔
535
                // call the procedure
536
                page(p).emplace_back(CALL, args_count);
1,014✔
537
            }
1,022✔
538
        }
1,014✔
539
        else  // operator
540
        {
541
            // retrieve operator
542
            auto op = maybe_operator.value();
804✔
543

544
            if (op == ASSERT)
804✔
545
                is_result_unused = false;
4✔
546

547
            // push arguments on current page
548
            std::size_t exp_count = 0;
804✔
549
            for (std::size_t index = start_index, size = x.constList().size(); index < size; ++index)
2,263✔
550
            {
551
                if (nodeProducesOutput(x.constList()[index]))
1,459✔
552
                    compileExpression(x.constList()[index], p, false, false);
1,458✔
553
                else
554
                    throwCompilerError(fmt::format("Invalid node inside call to operator `{}'", node.repr()), x);
1✔
555

556
                if ((index + 1 < size && x.constList()[index + 1].nodeType() != NodeType::Capture) || index + 1 == size)
1,458✔
557
                    exp_count++;
1,458✔
558

559
                // in order to be able to handle things like (op A B C D...)
560
                // which should be transformed into A B op C op D op...
561
                if (exp_count >= 2)
1,458✔
562
                    page(p).emplace_back(op);
657✔
563
            }
1,458✔
564

565
            if (isUnaryInst(op))
803✔
566
            {
567
                if (exp_count != 1)
179✔
568
                    throwCompilerError(fmt::format("Operator needs one argument, but was called with {}", exp_count), x.constList()[0]);
1✔
569
                page(p).emplace_back(op);
178✔
570
            }
178✔
571
            else if (exp_count <= 1)
624✔
572
                throwCompilerError(fmt::format("Operator needs two arguments, but was called with {}", exp_count), x.constList()[0]);
2✔
573

574
            // need to check we didn't push the (op A B C D...) things for operators not supporting it
575
            if (exp_count > 2)
800✔
576
            {
577
                switch (op)
27✔
578
                {
18✔
579
                    // authorized instructions
580
                    case ADD: [[fallthrough]];
581
                    case SUB: [[fallthrough]];
582
                    case MUL: [[fallthrough]];
583
                    case DIV: [[fallthrough]];
584
                    case MOD:
585
                        break;
27✔
586

587
                    default:
588
                        throwCompilerError(
9✔
589
                            fmt::format(
18✔
590
                                "`{}' requires 2 arguments, but got {}.",
9✔
591
                                Language::operators[static_cast<std::size_t>(op - FIRST_OPERATOR)],
9✔
592
                                exp_count),
593
                            x);
9✔
594
                }
18✔
595
            }
18✔
596
        }
804✔
597

598
        if (is_result_unused)
1,825✔
599
            page(p).emplace_back(POP);
717✔
600
    }
1,880✔
601

602
    uint16_t Compiler::addSymbol(const Node& sym)
2,520✔
603
    {
2,520✔
604
        // otherwise, add the symbol, and return its id in the table
605
        auto it = std::ranges::find(m_symbols, sym.string());
2,520✔
606
        if (it == m_symbols.end())
2,520✔
607
        {
608
            m_symbols.push_back(sym.string());
373✔
609
            it = m_symbols.begin() + static_cast<std::vector<std::string>::difference_type>(m_symbols.size() - 1);
373✔
610
        }
373✔
611

612
        const auto distance = std::distance(m_symbols.begin(), it);
2,520✔
613
        if (distance < std::numeric_limits<uint16_t>::max())
2,520✔
614
            return static_cast<uint16_t>(distance);
5,040✔
UNCOV
615
        throwCompilerError("Too many symbols (exceeds 65'536), aborting compilation.", sym);
×
616
    }
2,520✔
617

618
    uint16_t Compiler::addValue(const Node& x)
2,317✔
619
    {
2,317✔
620
        const ValTableElem v(x);
2,317✔
621
        auto it = std::ranges::find(m_values, v);
2,317✔
622
        if (it == m_values.end())
2,317✔
623
        {
624
            m_values.push_back(v);
721✔
625
            it = m_values.begin() + static_cast<std::vector<ValTableElem>::difference_type>(m_values.size() - 1);
721✔
626
        }
721✔
627

628
        const auto distance = std::distance(m_values.begin(), it);
2,317✔
629
        if (distance < std::numeric_limits<uint16_t>::max())
2,317✔
630
            return static_cast<uint16_t>(distance);
2,317✔
UNCOV
631
        throwCompilerError("Too many values (exceeds 65'536), aborting compilation.", x);
×
632
    }
2,317✔
633

634
    uint16_t Compiler::addValue(const std::size_t page_id, const Node& current)
132✔
635
    {
132✔
636
        const ValTableElem v(page_id);
132✔
637
        auto it = std::ranges::find(m_values, v);
132✔
638
        if (it == m_values.end())
132✔
639
        {
640
            m_values.push_back(v);
132✔
641
            it = m_values.begin() + static_cast<std::vector<ValTableElem>::difference_type>(m_values.size() - 1);
132✔
642
        }
132✔
643

644
        const auto distance = std::distance(m_values.begin(), it);
132✔
645
        if (distance < std::numeric_limits<uint16_t>::max())
132✔
646
            return static_cast<uint16_t>(distance);
132✔
UNCOV
647
        throwCompilerError("Too many values (exceeds 65'536), aborting compilation.", current);
×
648
    }
132✔
649
}
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