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

ArkScript-lang / Ark / 12382576351

17 Dec 2024 10:31PM UTC coverage: 79.406% (+0.07%) from 79.337%
12382576351

push

github

SuperFola
chore: format examples/macros.ark

5726 of 7211 relevant lines covered (79.41%)

14919.23 hits per line

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

90.12
/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) :
154✔
22
        m_debug(debug), m_logger("Compiler", debug)
154✔
23
    {}
308✔
24

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

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

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

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

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

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

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

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

81
    bool Compiler::nodeProducesOutput(const Node& node)
4,466✔
82
    {
4,466✔
83
        if (node.nodeType() == NodeType::List && !node.constList().empty() && node.constList()[0].nodeType() == NodeType::Keyword)
4,466✔
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;
162✔
87
        return true;  // any other node, function call, symbol, number...
4,449✔
88
    }
4,466✔
89

90
    bool Compiler::isUnaryInst(const Instruction inst) noexcept
857✔
91
    {
857✔
92
        switch (inst)
857✔
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;
857✔
104

105
            default:
106
                return false;
678✔
107
        }
108
    }
857✔
109

110
    bool Compiler::isTernaryInst(const Instruction inst) noexcept
1,417✔
111
    {
1,417✔
112
        switch (inst)
1,417✔
113
        {
86✔
114
            case AT_AT:
115
                return true;
1,417✔
116

117
            default:
118
                return false;
1,331✔
119
        }
120
    }
1,417✔
121

122
    void Compiler::compilerWarning(const std::string& message, const Node& node)
×
123
    {
×
124
        fmt::println("{} {}", fmt::styled("Warning", fmt::fg(fmt::color::dark_orange)), Diagnostics::makeContextWithNode(message, node));
×
125
    }
×
126

127
    void Compiler::throwCompilerError(const std::string& message, const Node& node)
34✔
128
    {
34✔
129
        throw CodeError(message, node.filename(), node.line(), node.col(), node.repr());
34✔
130
    }
34✔
131

132
    void Compiler::compileExpression(const Node& x, const Page p, const bool is_result_unused, const bool is_terminal, const std::string& var_name)
8,489✔
133
    {
8,489✔
134
        // register symbols
135
        if (x.nodeType() == NodeType::Symbol)
8,489✔
136
            compileSymbol(x, p, is_result_unused);
1,913✔
137
        else if (x.nodeType() == NodeType::Field)
6,576✔
138
        {
139
            // the parser guarantees us that there is at least 2 elements (eg: a.b)
140
            compileSymbol(x.constList()[0], p, is_result_unused);
267✔
141
            for (auto it = x.constList().begin() + 1, end = x.constList().end(); it != end; ++it)
542✔
142
            {
143
                uint16_t i = addSymbol(*it);
275✔
144
                page(p).emplace_back(GET_FIELD, i);
275✔
145
            }
275✔
146
        }
267✔
147
        // register values
148
        else if (x.nodeType() == NodeType::String || x.nodeType() == NodeType::Number)
6,309✔
149
        {
150
            uint16_t i = addValue(x);
2,509✔
151

152
            if (!is_result_unused)
2,509✔
153
                page(p).emplace_back(LOAD_CONST, i);
2,509✔
154
        }
2,509✔
155
        // namespace nodes
156
        else if (x.nodeType() == NodeType::Namespace)
3,800✔
157
            compileExpression(*x.constArkNamespace().ast, p, is_result_unused, is_terminal, var_name);
21✔
158
        else if (x.nodeType() == NodeType::Unused)
3,779✔
159
        {
160
            // do nothing, explicitly
161
        }
×
162
        // empty code block should be nil
163
        else if (x.constList().empty())
3,779✔
164
        {
165
            if (!is_result_unused)
×
166
            {
167
                static const std::optional<uint16_t> nil = getBuiltin("nil");
78✔
168
                page(p).emplace_back(BUILTIN, nil.value());
×
169
            }
×
170
        }
×
171
        // list instructions
172
        else if (const auto c0 = x.constList()[0]; c0.nodeType() == NodeType::Symbol && getListInstruction(c0.string()).has_value())
7,558✔
173
            compileListInstruction(c0, x, p, is_result_unused);
595✔
174
        // registering structures
175
        else if (x.constList()[0].nodeType() == NodeType::Keyword)
3,184✔
176
        {
177
            switch (const Keyword keyword = x.constList()[0].keyword())
1,228✔
178
            {
333✔
179
                case Keyword::If:
180
                    compileIf(x, p, is_result_unused, is_terminal, var_name);
333✔
181
                    break;
774✔
182

183
                case Keyword::Set:
184
                    [[fallthrough]];
185
                case Keyword::Let:
186
                    [[fallthrough]];
187
                case Keyword::Mut:
188
                    compileLetMutSet(keyword, x, p);
441✔
189
                    break;
571✔
190

191
                case Keyword::Fun:
192
                    compileFunction(x, p, is_result_unused, var_name);
134✔
193
                    break;
421✔
194

195
                case Keyword::Begin:
196
                {
197
                    for (std::size_t i = 1, size = x.constList().size(); i < size; ++i)
1,480✔
198
                        compileExpression(
2,380✔
199
                            x.constList()[i],
1,190✔
200
                            p,
1,190✔
201
                            // All the nodes in a begin (except for the last one) are producing a result that we want to drop.
202
                            (i != size - 1) || is_result_unused,
1,190✔
203
                            // If the begin is a terminal node, only its last node is terminal.
204
                            is_terminal && (i == size - 1),
1,190✔
205
                            var_name);
1,190✔
206
                    break;
256✔
207
                }
29✔
208

209
                case Keyword::While:
210
                    compileWhile(x, p);
29✔
211
                    break;
28✔
212

213
                case Keyword::Import:
214
                    compilePluginImport(x, p);
×
215
                    break;
1✔
216

217
                case Keyword::Del:
218
                    page(p).emplace_back(DEL, addSymbol(x.constList()[1]));
1✔
219
                    break;
1✔
220
            }
1,228✔
221
        }
1,186✔
222
        else if (x.nodeType() == NodeType::List)
1,956✔
223
        {
224
            // if we are here, we should have a function name
225
            // push arguments first, then function name, then call it
226
            handleCalls(x, p, is_result_unused, is_terminal, var_name);
1,956✔
227
        }
1,930✔
228
        else
229
            throwCompilerError(
×
230
                fmt::format(
×
231
                    "NodeType `{}' not handled in Compiler::compileExpression. Please fill an issue on GitHub: https://github.com/ArkScript-lang/Ark",
×
232
                    typeToString(x)),
×
233
                x);
×
234
    }
8,489✔
235

236
    void Compiler::compileSymbol(const Node& x, const Page p, const bool is_result_unused)
2,180✔
237
    {
2,180✔
238
        const std::string& name = x.string();
2,180✔
239

240
        if (const auto it_builtin = getBuiltin(name))
4,360✔
241
            page(p).emplace_back(Instruction::BUILTIN, it_builtin.value());
496✔
242
        else if (getOperator(name).has_value())
1,684✔
243
            throwCompilerError(fmt::format("Found a free standing operator: `{}`", name), x);
1✔
244
        else
245
            page(p).emplace_back(LOAD_SYMBOL, addSymbol(x));  // using the variable
1,683✔
246

247
        if (is_result_unused)
2,179✔
248
        {
249
            compilerWarning("Statement has no effect", x);
×
250
            page(p).emplace_back(POP);
×
251
        }
×
252
    }
2,180✔
253

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

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

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

281
        // put inst and number of arguments
282
        std::size_t inst_argc = 0;
585✔
283
        switch (inst)
585✔
284
        {
533✔
285
            case LIST:
286
                inst_argc = argc;
533✔
287
                break;
560✔
288

289
            case APPEND:
290
            case APPEND_IN_PLACE:
291
            case CONCAT:
292
            case CONCAT_IN_PLACE:
293
                inst_argc = argc - 1;
27✔
294
                break;
38✔
295

296
            case POP_LIST:
297
            case POP_LIST_IN_PLACE:
298
                inst_argc = 0;
11✔
299
                break;
25✔
300

301
            default:
302
                break;
14✔
303
        }
585✔
304
        page(p).emplace_back(inst, static_cast<uint16_t>(inst_argc));
585✔
305

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

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

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

322
        // else code
323
        if (x.constList().size() == 4)  // we have an else clause
333✔
324
            compileExpression(x.constList()[3], p, is_result_unused, is_terminal, var_name);
323✔
325

326
        // when else is finished, jump to end
327
        const auto label_end = IR::Entity::Label(m_current_label++);
333✔
328
        page(p).emplace_back(IR::Entity::Goto(label_end));
333✔
329

330
        // absolute address to jump to if condition is true
331
        page(p).emplace_back(label_then);
333✔
332
        // if code
333
        compileExpression(x.constList()[2], p, is_result_unused, is_terminal, var_name);
333✔
334
        // set jump to end pos
335
        page(p).emplace_back(label_end);
333✔
336
    }
333✔
337

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

345
        // capture, if needed
346
        bool is_closure = false;
132✔
347
        for (const auto& node : x.constList()[1].constList())
351✔
348
        {
349
            if (node.nodeType() == NodeType::Capture)
219✔
350
            {
351
                page(p).emplace_back(CAPTURE, addSymbol(node));
55✔
352
                is_closure = true;
55✔
353
            }
55✔
354
        }
219✔
355

356
        // create new page for function body
357
        m_code_pages.emplace_back();
132✔
358
        const auto function_body_page = Page { .index = m_code_pages.size() - 1, .is_temp = false };
132✔
359
        // save page_id into the constants table as PageAddr and load the const
360
        page(p).emplace_back(is_closure ? MAKE_CLOSURE : LOAD_CONST, addValue(function_body_page.index, x));
132✔
361

362
        // pushing arguments from the stack into variables in the new scope
363
        for (const auto& node : x.constList()[1].constList())
351✔
364
        {
365
            if (node.nodeType() == NodeType::Symbol)
219✔
366
                page(function_body_page).emplace_back(STORE, addSymbol(node));
164✔
367
        }
219✔
368

369
        // push body of the function
370
        compileExpression(x.constList()[2], function_body_page, false, true, var_name);
132✔
371

372
        // return last value on the stack
373
        page(function_body_page).emplace_back(RET);
132✔
374

375
        // if the computed function is unused, pop it
376
        if (is_result_unused)
132✔
377
        {
378
            compilerWarning("Unused declared function", x);
×
379
            page(p).emplace_back(POP);
×
380
        }
×
381
    }
134✔
382

383
    void Compiler::compileLetMutSet(const Keyword n, const Node& x, const Page p)
441✔
384
    {
441✔
385
        if (const auto sym = x.constList()[1]; sym.nodeType() != NodeType::Symbol)
445✔
386
            throwCompilerError(fmt::format("Expected a symbol, got a {}", typeToString(sym)), sym);
×
387
        if (x.constList().size() != 3)
441✔
388
            throwCompilerError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
1✔
389

390
        const std::string name = x.constList()[1].string();
440✔
391
        uint16_t i = addSymbol(x.constList()[1]);
440✔
392

393
        // put value before symbol id
394
        // starting at index = 2 because x is a (let|mut|set variable ...) node
395
        for (std::size_t idx = 2, end = x.constList().size(); idx < end; ++idx)
880✔
396
            compileExpression(x.constList()[idx], p, false, false, name);
440✔
397

398
        if (n == Keyword::Let || n == Keyword::Mut)
437✔
399
            page(p).emplace_back(STORE, i);
364✔
400
        else
401
            page(p).emplace_back(SET_VAL, i);
73✔
402
    }
441✔
403

404
    void Compiler::compileWhile(const Node& x, const Page p)
29✔
405
    {
29✔
406
        if (x.constList().size() != 3)
29✔
407
            throwCompilerError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
1✔
408

409
        page(p).emplace_back(CREATE_SCOPE);
28✔
410

411
        // save current position to jump there at the end of the loop
412
        const auto label_loop = IR::Entity::Label(m_current_label++);
28✔
413
        page(p).emplace_back(label_loop);
28✔
414
        // push condition
415
        compileExpression(x.constList()[1], p, false, false);
28✔
416
        // absolute jump to end of block if condition is false
417
        const auto label_end = IR::Entity::Label(m_current_label++);
28✔
418
        page(p).emplace_back(IR::Entity::GotoIf(label_end, false));
28✔
419
        // push code to page
420
        compileExpression(x.constList()[2], p, true, false);
28✔
421

422
        // loop, jump to the condition
423
        page(p).emplace_back(IR::Entity::Goto(label_loop));
28✔
424

425
        // absolute address to jump to if condition is false
426
        page(p).emplace_back(label_end);
28✔
427

428
        page(p).emplace_back(POP_SCOPE);
28✔
429
    }
29✔
430

431
    void Compiler::compilePluginImport(const Node& x, const Page p)
×
432
    {
×
433
        std::string path;
×
434
        const Node package_node = x.constList()[1];
×
435
        for (std::size_t i = 0, end = package_node.constList().size(); i < end; ++i)
×
436
        {
437
            path += package_node.constList()[i].string();
×
438
            if (i + 1 != end)
×
439
                path += "/";
×
440
        }
×
441
        path += ".arkm";
×
442

443
        // register plugin path in the constants table
444
        uint16_t id = addValue(Node(NodeType::String, path));
×
445
        // add plugin instruction + id of the constant referring to the plugin path
446
        page(p).emplace_back(PLUGIN, id);
×
447
    }
×
448

449
    void Compiler::handleCalls(const Node& x, const Page p, bool is_result_unused, const bool is_terminal, const std::string& var_name)
1,956✔
450
    {
1,956✔
451
        constexpr std::size_t start_index = 1;
1,956✔
452

453
        const auto node = x.constList()[0];
1,956✔
454
        const std::optional<Instruction> maybe_operator = node.nodeType() == NodeType::Symbol ? getOperator(node.string()) : std::nullopt;
1,956✔
455

456
        enum class ShortcircuitOp
457
        {
458
            And,
459
            Or
460
        };
461
        const std::optional<ShortcircuitOp> maybe_shortcircuit =
1,956✔
462
            node.nodeType() == NodeType::Symbol
3,753✔
463
            ? (node.string() == Language::And
3,579✔
464
                   ? std::make_optional(ShortcircuitOp::And)
15✔
465
                   : (node.string() == Language::Or
1,782✔
466
                          ? std::make_optional(ShortcircuitOp::Or)
7✔
467
                          : std::nullopt))
1,775✔
468
            : std::nullopt;
159✔
469

470
        if (maybe_shortcircuit.has_value())
1,956✔
471
        {
472
            // short circuit implementation
473
            if (x.constList().size() < 3)
22✔
474
                throwCompilerError(
2✔
475
                    fmt::format(
6✔
476
                        "Expected at least 2 arguments while compiling '{}', got {}",
2✔
477
                        node.string(),
2✔
478
                        x.constList().size() - 1),
2✔
479
                    x);
2✔
480

481
            compileExpression(x.constList()[1], p, false, false);
20✔
482
            page(p).emplace_back(DUP);
20✔
483

484
            const auto label_shortcircuit = IR::Entity::Label(m_current_label++);
20✔
485
            for (std::size_t i = 2, end = x.constList().size(); i < end; ++i)
48✔
486
            {
487
                switch (maybe_shortcircuit.value())
28✔
488
                {
18✔
489
                    case ShortcircuitOp::And:
490
                        page(p).emplace_back(IR::Entity::GotoIf(label_shortcircuit, false));
18✔
491
                        break;
28✔
492
                    case ShortcircuitOp::Or:
493
                        page(p).emplace_back(IR::Entity::GotoIf(label_shortcircuit, true));
10✔
494
                        break;
10✔
495
                }
28✔
496
                page(p).emplace_back(POP);
28✔
497

498
                compileExpression(x.constList()[i], p, false, false);
28✔
499
                if (i + 1 != end)
28✔
500
                    page(p).emplace_back(DUP);
8✔
501
            }
28✔
502

503
            page(p).emplace_back(label_shortcircuit);
20✔
504
        }
20✔
505
        else if (!maybe_operator.has_value())
1,934✔
506
        {
507
            if (is_terminal && x.constList()[0].nodeType() == NodeType::Symbol && var_name == x.constList()[0].string())
1,076✔
508
            {
509
                // push the arguments in reverse order
510
                for (std::size_t i = x.constList().size() - 1; i >= start_index; --i)
25✔
511
                {
512
                    if (nodeProducesOutput(x.constList()[i]))
17✔
513
                        compileExpression(x.constList()[i], p, false, false);
16✔
514
                    else
515
                        throwCompilerError(fmt::format("Invalid node inside tail call to `{}'", node.repr()), x);
1✔
516
                }
16✔
517

518
                // jump to the top of the function
519
                page(p).emplace_back(JUMP, 0_u16);
7✔
520
                return;  // skip the potential Instruction::POP at the end
7✔
521
            }
522
            else
523
            {
524
                m_temp_pages.emplace_back();
1,068✔
525
                const auto proc_page = Page { .index = m_temp_pages.size() - 1u, .is_temp = true };
1,068✔
526
                // closure chains have been handled (eg: closure.field.field.function)
527
                compileExpression(node, proc_page, false, false);  // storing proc
1,068✔
528
                if (m_temp_pages.back().empty())
1,065✔
529
                    throwCompilerError(fmt::format("Can not call {}", x.constList()[0].repr()), x);
1✔
530

531
                // push arguments on current page
532
                for (auto exp = x.constList().begin() + start_index, exp_end = x.constList().end(); exp != exp_end; ++exp)
3,196✔
533
                {
534
                    if (nodeProducesOutput(*exp))
2,132✔
535
                        compileExpression(*exp, p, false, false);
2,131✔
536
                    else
537
                        throwCompilerError(fmt::format("Invalid node inside call to `{}'", node.repr()), x);
1✔
538
                }
2,127✔
539
                // push proc from temp page
540
                for (const auto& inst : m_temp_pages.back())
2,280✔
541
                    page(p).push_back(inst);
1,221✔
542
                m_temp_pages.pop_back();
1,059✔
543

544
                // number of arguments
545
                std::size_t args_count = 0;
1,059✔
546
                for (auto it = x.constList().begin() + 1, it_end = x.constList().end(); it != it_end; ++it)
3,184✔
547
                {
548
                    if (it->nodeType() != NodeType::Capture)
2,125✔
549
                        args_count++;
2,125✔
550
                }
2,125✔
551
                // call the procedure
552
                page(p).emplace_back(CALL, args_count);
1,059✔
553
            }
1,068✔
554
        }
1,059✔
555
        else  // operator
556
        {
557
            // retrieve operator
558
            auto op = maybe_operator.value();
858✔
559

560
            if (op == ASSERT)
858✔
561
                is_result_unused = false;
4✔
562

563
            // push arguments on current page
564
            std::size_t exp_count = 0;
858✔
565
            for (std::size_t index = start_index, size = x.constList().size(); index < size; ++index)
2,453✔
566
            {
567
                if (nodeProducesOutput(x.constList()[index]))
1,595✔
568
                    compileExpression(x.constList()[index], p, false, false);
1,594✔
569
                else
570
                    throwCompilerError(fmt::format("Invalid node inside call to operator `{}'", node.repr()), x);
1✔
571

572
                if ((index + 1 < size && x.constList()[index + 1].nodeType() != NodeType::Capture) || index + 1 == size)
1,594✔
573
                    exp_count++;
1,594✔
574

575
                // in order to be able to handle things like (op A B C D...)
576
                // which should be transformed into A B op C op D op...
577
                if (exp_count >= 2 && !isTernaryInst(op))
1,594✔
578
                    page(p).emplace_back(op);
682✔
579
            }
1,594✔
580

581
            if (isUnaryInst(op))
857✔
582
            {
583
                if (exp_count != 1)
179✔
584
                    throwCompilerError(fmt::format("Operator needs one argument, but was called with {}", exp_count), x.constList()[0]);
1✔
585
                page(p).emplace_back(op);
178✔
586
            }
178✔
587
            else if (isTernaryInst(op))
678✔
588
            {
589
                if (exp_count != 3)
29✔
590
                    throwCompilerError(fmt::format("Operator needs three arguments, but was called with {}", exp_count), x.constList()[0]);
1✔
591
                page(p).emplace_back(op);
28✔
592
            }
28✔
593
            else if (exp_count <= 1)
649✔
594
                throwCompilerError(fmt::format("Operator needs two arguments, but was called with {}", exp_count), x.constList()[0]);
2✔
595

596
            // need to check we didn't push the (op A B C D...) things for operators not supporting it
597
            if (exp_count > 2)
853✔
598
            {
599
                switch (op)
55✔
600
                {
46✔
601
                    // authorized instructions
602
                    case ADD: [[fallthrough]];
603
                    case SUB: [[fallthrough]];
604
                    case MUL: [[fallthrough]];
605
                    case DIV: [[fallthrough]];
606
                    case MOD: [[fallthrough]];
607
                    case AT_AT:
608
                        break;
55✔
609

610
                    default:
611
                        throwCompilerError(
9✔
612
                            fmt::format(
18✔
613
                                "`{}' requires 2 arguments, but got {}.",
9✔
614
                                Language::operators[static_cast<std::size_t>(op - FIRST_OPERATOR)],
9✔
615
                                exp_count),
616
                            x);
9✔
617
                }
46✔
618
            }
46✔
619
        }
858✔
620

621
        if (is_result_unused)
1,923✔
622
            page(p).emplace_back(POP);
760✔
623
    }
1,982✔
624

625
    uint16_t Compiler::addSymbol(const Node& sym)
2,618✔
626
    {
2,618✔
627
        // otherwise, add the symbol, and return its id in the table
628
        auto it = std::ranges::find(m_symbols, sym.string());
2,618✔
629
        if (it == m_symbols.end())
2,618✔
630
        {
631
            m_symbols.push_back(sym.string());
378✔
632
            it = m_symbols.begin() + static_cast<std::vector<std::string>::difference_type>(m_symbols.size() - 1);
378✔
633
        }
378✔
634

635
        const auto distance = std::distance(m_symbols.begin(), it);
2,618✔
636
        if (distance < std::numeric_limits<uint16_t>::max())
2,618✔
637
            return static_cast<uint16_t>(distance);
5,236✔
638
        throwCompilerError("Too many symbols (exceeds 65'536), aborting compilation.", sym);
×
639
    }
2,618✔
640

641
    uint16_t Compiler::addValue(const Node& x)
2,509✔
642
    {
2,509✔
643
        const ValTableElem v(x);
2,509✔
644
        auto it = std::ranges::find(m_values, v);
2,509✔
645
        if (it == m_values.end())
2,509✔
646
        {
647
            m_values.push_back(v);
780✔
648
            it = m_values.begin() + static_cast<std::vector<ValTableElem>::difference_type>(m_values.size() - 1);
780✔
649
        }
780✔
650

651
        const auto distance = std::distance(m_values.begin(), it);
2,509✔
652
        if (distance < std::numeric_limits<uint16_t>::max())
2,509✔
653
            return static_cast<uint16_t>(distance);
2,509✔
654
        throwCompilerError("Too many values (exceeds 65'536), aborting compilation.", x);
×
655
    }
2,509✔
656

657
    uint16_t Compiler::addValue(const std::size_t page_id, const Node& current)
132✔
658
    {
132✔
659
        const ValTableElem v(page_id);
132✔
660
        auto it = std::ranges::find(m_values, v);
132✔
661
        if (it == m_values.end())
132✔
662
        {
663
            m_values.push_back(v);
132✔
664
            it = m_values.begin() + static_cast<std::vector<ValTableElem>::difference_type>(m_values.size() - 1);
132✔
665
        }
132✔
666

667
        const auto distance = std::distance(m_values.begin(), it);
132✔
668
        if (distance < std::numeric_limits<uint16_t>::max())
132✔
669
            return static_cast<uint16_t>(distance);
132✔
670
        throwCompilerError("Too many values (exceeds 65'536), aborting compilation.", current);
×
671
    }
132✔
672
}
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