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

ArkScript-lang / Ark / 11987163431

23 Nov 2024 12:31PM UTC coverage: 77.19% (-0.1%) from 77.285%
11987163431

push

github

SuperFola
refactor(macro processor): removing the need for a double apply macro + recursive apply in MacroProcessor::processNode

7 of 7 new or added lines in 4 files covered. (100.0%)

71 existing lines in 6 files now uncovered.

5191 of 6725 relevant lines covered (77.19%)

9553.21 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

105
            default:
106
                return false;
621✔
107
        }
108
    }
793✔
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)
13✔
116
    {
13✔
117
        throw CodeError(message, node.filename(), node.line(), node.col(), node.repr());
13✔
118
    }
13✔
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)
7,432✔
121
    {
7,432✔
122
        // register symbols
123
        if (x.nodeType() == NodeType::Symbol)
7,432✔
124
            compileSymbol(x, p, is_result_unused);
1,816✔
125
        else if (x.nodeType() == NodeType::Field)
5,616✔
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);
222✔
129
            for (auto it = x.constList().begin() + 1, end = x.constList().end(); it != end; ++it)
449✔
130
            {
131
                uint16_t i = addSymbol(*it);
227✔
132
                page(p).emplace_back(GET_FIELD, i);
227✔
133
            }
227✔
134
        }
222✔
135
        // register values
136
        else if (x.nodeType() == NodeType::String || x.nodeType() == NodeType::Number)
5,394✔
137
        {
138
            uint16_t i = addValue(x);
2,021✔
139

140
            if (!is_result_unused)
2,021✔
141
                page(p).emplace_back(LOAD_CONST, i);
2,021✔
142
        }
2,021✔
143
        // empty code block should be nil
144
        else if (x.constList().empty())
3,373✔
145
        {
146
            if (!is_result_unused)
×
147
            {
148
                static const std::optional<uint16_t> nil = getBuiltin("nil");
35✔
149
                page(p).emplace_back(BUILTIN, nil.value());
×
150
            }
×
151
        }
×
152
        // list instructions
153
        else if (const auto c0 = x.constList()[0]; c0.nodeType() == NodeType::Symbol && getListInstruction(c0.string()).has_value())
6,746✔
154
            compileListInstruction(c0, x, p, is_result_unused);
491✔
155
        // registering structures
156
        else if (x.constList()[0].nodeType() == NodeType::Keyword)
2,882✔
157
        {
158
            switch (const Keyword keyword = x.constList()[0].keyword())
1,105✔
159
            {
304✔
160
                case Keyword::If:
161
                    compileIf(x, p, is_result_unused, is_terminal, var_name);
304✔
162
                    break;
718✔
163

164
                case Keyword::Set:
165
                    [[fallthrough]];
166
                case Keyword::Let:
167
                    [[fallthrough]];
168
                case Keyword::Mut:
169
                    compileLetMutSet(keyword, x, p);
414✔
170
                    break;
535✔
171

172
                case Keyword::Fun:
173
                    compileFunction(x, p, is_result_unused, var_name);
125✔
174
                    break;
345✔
175

176
                case Keyword::Begin:
177
                {
178
                    for (std::size_t i = 1, size = x.constList().size(); i < size; ++i)
1,242✔
179
                        compileExpression(
2,038✔
180
                            x.constList()[i],
1,019✔
181
                            p,
1,019✔
182
                            // All the nodes in a begin (except for the last one) are producing a result that we want to drop.
183
                            (i != size - 1) || is_result_unused,
1,019✔
184
                            // If the begin is a terminal node, only its last node is terminal.
185
                            is_terminal && (i == size - 1),
1,019✔
186
                            var_name);
1,019✔
187
                    break;
210✔
188
                }
36✔
189

190
                case Keyword::While:
191
                    compileWhile(x, p);
36✔
192
                    break;
35✔
193

194
                case Keyword::Import:
195
                    compilePluginImport(x, p);
×
196
                    break;
3✔
197

198
                case Keyword::Del:
199
                    page(p).emplace_back(DEL, addSymbol(x.constList()[1]));
3✔
200
                    break;
3✔
201
            }
1,105✔
202
        }
1,084✔
203
        else if (x.nodeType() == NodeType::List)
1,777✔
204
        {
205
            // if we are here, we should have a function name
206
            // push arguments first, then function name, then call it
207
            handleCalls(x, p, is_result_unused, is_terminal, var_name);
1,777✔
208
        }
1,764✔
209
        else
210
            throwCompilerError(
×
UNCOV
211
                fmt::format(
×
UNCOV
212
                    "NodeType `{}' not handled in Compiler::compileExpression. Please fill an issue on GitHub: https://github.com/ArkScript-lang/Ark",
×
UNCOV
213
                    typeToString(x)),
×
UNCOV
214
                x);
×
215
    }
7,432✔
216

217
    void Compiler::compileSymbol(const Node& x, const Page p, const bool is_result_unused)
2,038✔
218
    {
2,038✔
219
        const std::string& name = x.string();
2,038✔
220

221
        if (const auto it_builtin = getBuiltin(name))
4,076✔
222
            page(p).emplace_back(Instruction::BUILTIN, it_builtin.value());
474✔
223
        else if (getOperator(name).has_value())
1,564✔
224
            throwCompilerError(fmt::format("Found a free standing operator: `{}`", name), x);
1✔
225
        else
226
            page(p).emplace_back(LOAD_SYMBOL, addSymbol(x));  // using the variable
1,563✔
227

228
        if (is_result_unused)
2,037✔
229
        {
UNCOV
230
            compilerWarning("Statement has no effect", x);
×
UNCOV
231
            page(p).emplace_back(POP);
×
UNCOV
232
        }
×
233
    }
2,038✔
234

235
    void Compiler::compileListInstruction(const Node& c0, const Node& x, const Page p, const bool is_result_unused)
491✔
236
    {
491✔
237
        std::string name = c0.string();
491✔
238
        Instruction inst = getListInstruction(name).value();
491✔
239

240
        // length of at least 1 since we got a symbol name
241
        const auto argc = x.constList().size() - 1u;
491✔
242
        // error, can not use append/concat/pop (and their in place versions) with a <2 length argument list
243
        if (argc < 2 && inst != LIST)
491✔
UNCOV
244
            throwCompilerError(fmt::format("Can not use {} with less than 2 arguments", name), c0);
×
245
        if (std::cmp_greater(argc, std::numeric_limits<uint16_t>::max()))
491✔
UNCOV
246
            throwCompilerError(fmt::format("Too many arguments ({}), exceeds 65'535", argc), x);
×
247

248
        // compile arguments in reverse order
249
        for (std::size_t i = x.constList().size() - 1u; i > 0; --i)
981✔
250
        {
251
            const auto node = x.constList()[i];
490✔
252
            if (nodeProducesOutput(node))
490✔
253
                compileExpression(node, p, false, false);
489✔
254
            else
255
                throwCompilerError(fmt::format("Invalid node inside call to {}", name), node);
1✔
256
        }
490✔
257

258
        // put inst and number of arguments
259
        std::size_t inst_argc;
490✔
260
        switch (inst)
490✔
261
        {
444✔
262
            case LIST:
263
                inst_argc = argc;
444✔
264
                break;
480✔
265

266
            case APPEND:
267
            case APPEND_IN_PLACE:
268
            case CONCAT:
269
            case CONCAT_IN_PLACE:
270
                inst_argc = argc - 1;
36✔
271
                break;
46✔
272

273
            default:
274
                inst_argc = 0;
10✔
275
                break;
10✔
276
        }
490✔
277
        page(p).emplace_back(inst, static_cast<uint16_t>(inst_argc));
490✔
278

279
        if (is_result_unused && name.back() != '!')  // in-place functions never push a value
490✔
280
        {
UNCOV
281
            compilerWarning("Ignoring return value of function", x);
×
UNCOV
282
            page(p).emplace_back(POP);
×
UNCOV
283
        }
×
284
    }
492✔
285

286
    void Compiler::compileIf(const Node& x, const Page p, const bool is_result_unused, const bool is_terminal, const std::string& var_name)
304✔
287
    {
304✔
288
        // compile condition
289
        compileExpression(x.constList()[1], p, false, false);
304✔
290

291
        // jump only if needed to the if
292
        const auto label_then = IR::Entity::Label(m_current_label++);
304✔
293
        page(p).emplace_back(IR::Entity::GotoIf(label_then, true));
304✔
294

295
        // else code
296
        if (x.constList().size() == 4)  // we have an else clause
304✔
297
            compileExpression(x.constList()[3], p, is_result_unused, is_terminal, var_name);
291✔
298

299
        // when else is finished, jump to end
300
        const auto label_end = IR::Entity::Label(m_current_label++);
304✔
301
        page(p).emplace_back(IR::Entity::Goto(label_end));
304✔
302

303
        // absolute address to jump to if condition is true
304
        page(p).emplace_back(label_then);
304✔
305
        // if code
306
        compileExpression(x.constList()[2], p, is_result_unused, is_terminal, var_name);
304✔
307
        // set jump to end pos
308
        page(p).emplace_back(label_end);
304✔
309
    }
304✔
310

311
    void Compiler::compileFunction(const Node& x, const Page p, const bool is_result_unused, const std::string& var_name)
125✔
312
    {
125✔
313
        if (const auto args = x.constList()[1]; args.nodeType() != NodeType::List)
127✔
314
            throwCompilerError(fmt::format("Expected a well formed argument(s) list, got a {}", typeToString(args)), args);
1✔
315
        if (x.constList().size() != 3)
124✔
316
            throwCompilerError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
1✔
317

318
        // capture, if needed
319
        bool is_closure = false;
123✔
320
        for (const auto& node : x.constList()[1].constList())
337✔
321
        {
322
            if (node.nodeType() == NodeType::Capture)
214✔
323
            {
324
                page(p).emplace_back(CAPTURE, addSymbol(node));
46✔
325
                is_closure = true;
46✔
326
            }
46✔
327
        }
214✔
328

329
        // create new page for function body
330
        m_code_pages.emplace_back();
123✔
331
        const auto function_body_page = Page { .index = m_code_pages.size() - 1, .is_temp = false };
123✔
332
        // save page_id into the constants table as PageAddr and load the const
333
        page(p).emplace_back(is_closure ? MAKE_CLOSURE : LOAD_CONST, addValue(function_body_page.index, x));
123✔
334

335
        // pushing arguments from the stack into variables in the new scope
336
        for (const auto& node : x.constList()[1].constList())
337✔
337
        {
338
            if (node.nodeType() == NodeType::Symbol)
214✔
339
                page(function_body_page).emplace_back(STORE, addSymbol(node));
168✔
340
        }
214✔
341

342
        // push body of the function
343
        compileExpression(x.constList()[2], function_body_page, false, true, var_name);
123✔
344

345
        // return last value on the stack
346
        page(function_body_page).emplace_back(RET);
123✔
347

348
        // if the computed function is unused, pop it
349
        if (is_result_unused)
123✔
350
        {
UNCOV
351
            compilerWarning("Unused declared function", x);
×
UNCOV
352
            page(p).emplace_back(POP);
×
UNCOV
353
        }
×
354
    }
125✔
355

356
    void Compiler::compileLetMutSet(const Keyword n, const Node& x, const Page p)
414✔
357
    {
414✔
358
        if (const auto sym = x.constList()[1]; sym.nodeType() != NodeType::Symbol)
418✔
UNCOV
359
            throwCompilerError(fmt::format("Expected a symbol, got a {}", typeToString(sym)), sym);
×
360
        if (x.constList().size() != 3)
414✔
361
            throwCompilerError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
1✔
362

363
        const std::string name = x.constList()[1].string();
413✔
364
        uint16_t i = addSymbol(x.constList()[1]);
413✔
365

366
        // put value before symbol id
367
        // starting at index = 2 because x is a (let|mut|set variable ...) node
368
        for (std::size_t idx = 2, end = x.constList().size(); idx < end; ++idx)
826✔
369
            compileExpression(x.constList()[idx], p, false, false, name);
413✔
370

371
        if (n == Keyword::Let || n == Keyword::Mut)
410✔
372
            page(p).emplace_back(STORE, i);
319✔
373
        else
374
            page(p).emplace_back(SET_VAL, i);
91✔
375
    }
414✔
376

377
    void Compiler::compileWhile(const Node& x, const Page p)
36✔
378
    {
36✔
379
        if (x.constList().size() != 3)
36✔
380
            throwCompilerError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
1✔
381

382
        // save current position to jump there at the end of the loop
383
        const auto label_loop = IR::Entity::Label(m_current_label++);
35✔
384
        page(p).emplace_back(label_loop);
35✔
385
        // push condition
386
        compileExpression(x.constList()[1], p, false, false);
35✔
387
        // absolute jump to end of block if condition is false
388
        const auto label_end = IR::Entity::Label(m_current_label++);
35✔
389
        page(p).emplace_back(IR::Entity::GotoIf(label_end, false));
35✔
390
        // push code to page
391
        compileExpression(x.constList()[2], p, true, false);
35✔
392

393
        // loop, jump to the condition
394
        page(p).emplace_back(IR::Entity::Goto(label_loop));
35✔
395

396
        // absolute address to jump to if condition is false
397
        page(p).emplace_back(label_end);
35✔
398
    }
36✔
399

400
    void Compiler::compilePluginImport(const Node& x, const Page p)
×
UNCOV
401
    {
×
402
        std::string path;
×
403
        const Node package_node = x.constList()[1];
×
404
        for (std::size_t i = 0, end = package_node.constList().size(); i < end; ++i)
×
405
        {
406
            path += package_node.constList()[i].string();
×
UNCOV
407
            if (i + 1 != end)
×
UNCOV
408
                path += "/";
×
409
        }
×
UNCOV
410
        path += ".arkm";
×
411

412
        // register plugin path in the constants table
UNCOV
413
        uint16_t id = addValue(Node(NodeType::String, path));
×
414
        // add plugin instruction + id of the constant referring to the plugin path
UNCOV
415
        page(p).emplace_back(PLUGIN, id);
×
UNCOV
416
    }
×
417

418
    void Compiler::handleCalls(const Node& x, const Page p, bool is_result_unused, const bool is_terminal, const std::string& var_name)
1,777✔
419
    {
1,777✔
420
        constexpr std::size_t start_index = 1;
1,777✔
421

422
        const auto node = x.constList()[0];
1,777✔
423
        const std::optional<Instruction> maybe_operator = node.nodeType() == NodeType::Symbol ? getOperator(node.string()) : std::nullopt;
1,777✔
424

425
        enum class ShortcircuitOp
426
        {
427
            And,
428
            Or
429
        };
430
        const std::optional<ShortcircuitOp> maybe_shortcircuit =
1,777✔
431
            node.nodeType() == NodeType::Symbol
3,429✔
432
            ? (node.string() == Language::And
3,288✔
433
                   ? std::make_optional(ShortcircuitOp::And)
16✔
434
                   : (node.string() == Language::Or
1,636✔
435
                          ? std::make_optional(ShortcircuitOp::Or)
6✔
436
                          : std::nullopt))
1,630✔
437
            : std::nullopt;
125✔
438

439
        if (maybe_shortcircuit.has_value())
1,777✔
440
        {
441
            // short circuit implementation
442

443
            compileExpression(x.constList()[1], p, false, false);
22✔
444
            page(p).emplace_back(DUP);
22✔
445

446
            const auto label_shortcircuit = IR::Entity::Label(m_current_label++);
22✔
447
            for (std::size_t i = 2, end = x.constList().size(); i < end; ++i)
52✔
448
            {
449
                switch (maybe_shortcircuit.value())
30✔
450
                {
20✔
451
                    case ShortcircuitOp::And:
452
                        page(p).emplace_back(IR::Entity::GotoIf(label_shortcircuit, false));
20✔
453
                        break;
30✔
454
                    case ShortcircuitOp::Or:
455
                        page(p).emplace_back(IR::Entity::GotoIf(label_shortcircuit, true));
10✔
456
                        break;
10✔
457
                }
30✔
458
                page(p).emplace_back(POP);
30✔
459

460
                compileExpression(x.constList()[i], p, false, false);
30✔
461
                if (i + 1 != end)
30✔
462
                    page(p).emplace_back(DUP);
8✔
463
            }
30✔
464

465
            page(p).emplace_back(label_shortcircuit);
22✔
466
        }
22✔
467
        else if (!maybe_operator.has_value())
1,755✔
468
        {
469
            if (is_terminal && x.constList()[0].nodeType() == NodeType::Symbol && var_name == x.constList()[0].string())
961✔
470
            {
471
                // push the arguments in reverse order
472
                for (std::size_t i = x.constList().size() - 1; i >= start_index; --i)
25✔
473
                {
474
                    if (nodeProducesOutput(x.constList()[i]))
17✔
475
                        compileExpression(x.constList()[i], p, false, false);
16✔
476
                    else
477
                        throwCompilerError(fmt::format("Invalid node inside tail call to `{}'", node.repr()), x);
1✔
478
                }
16✔
479

480
                // jump to the top of the function
481
                page(p).emplace_back(JUMP, 0_u16);
7✔
482
                return;  // skip the potential Instruction::POP at the end
7✔
483
            }
484
            else
485
            {
486
                m_temp_pages.emplace_back();
953✔
487
                const auto proc_page = Page { .index = m_temp_pages.size() - 1u, .is_temp = true };
953✔
488
                // closure chains have been handled (eg: closure.field.field.function)
489
                compileExpression(node, proc_page, false, false);  // storing proc
953✔
490
                if (m_temp_pages.back().empty())
950✔
491
                    throwCompilerError(fmt::format("Can not call {}", x.constList()[0].repr()), x);
1✔
492

493
                // push arguments on current page
494
                for (auto exp = x.constList().begin() + start_index, exp_end = x.constList().end(); exp != exp_end; ++exp)
2,865✔
495
                {
496
                    if (nodeProducesOutput(*exp))
1,916✔
497
                        compileExpression(*exp, p, false, false);
1,915✔
498
                    else
499
                        throwCompilerError(fmt::format("Invalid node inside call to `{}'", node.repr()), x);
1✔
500
                }
1,912✔
501
                // push proc from temp page
502
                for (const auto& inst : m_temp_pages.back())
2,016✔
503
                    page(p).push_back(inst);
1,071✔
504
                m_temp_pages.pop_back();
945✔
505

506
                // number of arguments
507
                std::size_t args_count = 0;
945✔
508
                for (auto it = x.constList().begin() + 1, it_end = x.constList().end(); it != it_end; ++it)
2,855✔
509
                {
510
                    if (it->nodeType() != NodeType::Capture)
1,910✔
511
                        args_count++;
1,910✔
512
                }
1,910✔
513
                // call the procedure
514
                page(p).emplace_back(CALL, args_count);
945✔
515
            }
953✔
516
        }
945✔
517
        else  // operator
518
        {
519
            // retrieve operator
520
            auto op = maybe_operator.value();
794✔
521

522
            if (op == ASSERT)
794✔
523
                is_result_unused = false;
6✔
524

525
            // push arguments on current page
526
            std::size_t exp_count = 0;
794✔
527
            for (std::size_t index = start_index, size = x.constList().size(); index < size; ++index)
2,231✔
528
            {
529
                if (nodeProducesOutput(x.constList()[index]))
1,437✔
530
                    compileExpression(x.constList()[index], p, false, false);
1,436✔
531
                else
532
                    throwCompilerError(fmt::format("Invalid node inside call to operator `{}'", node.repr()), x);
1✔
533

534
                if ((index + 1 < size && x.constList()[index + 1].nodeType() != NodeType::Capture) || index + 1 == size)
1,436✔
535
                    exp_count++;
1,436✔
536

537
                // in order to be able to handle things like (op A B C D...)
538
                // which should be transformed into A B op C op D op...
539
                if (exp_count >= 2)
1,436✔
540
                    page(p).emplace_back(op);
645✔
541
            }
1,436✔
542

543
            if (isUnaryInst(op))
793✔
544
            {
545
                if (exp_count != 1)
172✔
546
                    throwCompilerError(fmt::format("Operator needs one argument, but was called with {}", exp_count), x.constList()[0]);
1✔
547
                page(p).emplace_back(op);
171✔
548
            }
171✔
549
            else if (exp_count <= 1)
621✔
550
            {
551
                throwCompilerError(fmt::format("Operator needs two arguments, but was called with {}", exp_count), x.constList()[0]);
2✔
552
            }
553

554
            // need to check we didn't push the (op A B C D...) things for operators not supporting it
555
            if (exp_count > 2)
790✔
556
            {
557
                switch (op)
21✔
558
                {
21✔
559
                    // authorized instructions
560
                    case ADD: [[fallthrough]];
561
                    case SUB: [[fallthrough]];
562
                    case MUL: [[fallthrough]];
563
                    case DIV: [[fallthrough]];
564
                    case MOD:
565
                        break;
21✔
566

567
                    default:
568
                        throwCompilerError(
×
569
                            fmt::format(
×
UNCOV
570
                                "can not create a chained expression (of length {}) for operator `{}'. You most likely forgot a `)'.",
×
571
                                exp_count,
UNCOV
572
                                Language::operators[static_cast<std::size_t>(op - FIRST_OPERATOR)]),
×
UNCOV
573
                            x);
×
574
                }
21✔
575
            }
21✔
576
        }
794✔
577

578
        if (is_result_unused)
1,757✔
579
            page(p).emplace_back(POP);
656✔
580
    }
1,790✔
581

582
    uint16_t Compiler::addSymbol(const Node& sym)
2,420✔
583
    {
2,420✔
584
        // otherwise, add the symbol, and return its id in the table
585
        auto it = std::ranges::find(m_symbols, sym.string());
2,420✔
586
        if (it == m_symbols.end())
2,420✔
587
        {
588
            m_symbols.push_back(sym.string());
323✔
589
            it = m_symbols.begin() + static_cast<std::vector<std::string>::difference_type>(m_symbols.size() - 1);
323✔
590
        }
323✔
591

592
        const auto distance = std::distance(m_symbols.begin(), it);
2,420✔
593
        if (distance < std::numeric_limits<uint16_t>::max())
2,420✔
594
            return static_cast<uint16_t>(distance);
4,840✔
UNCOV
595
        throwCompilerError("Too many symbols (exceeds 65'536), aborting compilation.", sym);
×
596
    }
2,420✔
597

598
    uint16_t Compiler::addValue(const Node& x)
2,021✔
599
    {
2,021✔
600
        const ValTableElem v(x);
2,021✔
601
        auto it = std::ranges::find(m_values, v);
2,021✔
602
        if (it == m_values.end())
2,021✔
603
        {
604
            m_values.push_back(v);
605✔
605
            it = m_values.begin() + static_cast<std::vector<ValTableElem>::difference_type>(m_values.size() - 1);
605✔
606
        }
605✔
607

608
        const auto distance = std::distance(m_values.begin(), it);
2,021✔
609
        if (distance < std::numeric_limits<uint16_t>::max())
2,021✔
610
            return static_cast<uint16_t>(distance);
2,021✔
UNCOV
611
        throwCompilerError("Too many values (exceeds 65'536), aborting compilation.", x);
×
612
    }
2,021✔
613

614
    uint16_t Compiler::addValue(const std::size_t page_id, const Node& current)
123✔
615
    {
123✔
616
        const ValTableElem v(page_id);
123✔
617
        auto it = std::ranges::find(m_values, v);
123✔
618
        if (it == m_values.end())
123✔
619
        {
620
            m_values.push_back(v);
123✔
621
            it = m_values.begin() + static_cast<std::vector<ValTableElem>::difference_type>(m_values.size() - 1);
123✔
622
        }
123✔
623

624
        const auto distance = std::distance(m_values.begin(), it);
123✔
625
        if (distance < std::numeric_limits<uint16_t>::max())
123✔
626
            return static_cast<uint16_t>(distance);
123✔
UNCOV
627
        throwCompilerError("Too many values (exceeds 65'536), aborting compilation.", current);
×
628
    }
123✔
629
}
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