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

ArkScript-lang / Ark / 11304355606

12 Oct 2024 08:53AM UTC coverage: 76.391% (+1.2%) from 75.225%
11304355606

push

github

SuperFola
feat(ir, compiler): implementing the IR optimizer

62 of 75 new or added lines in 3 files covered. (82.67%)

402 existing lines in 9 files now uncovered.

5054 of 6616 relevant lines covered (76.39%)

9231.22 hits per line

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

88.65
/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) :
87✔
22
        m_debug(debug)
87✔
23
    {}
174✔
24

25
    void Compiler::process(const Node& ast)
39✔
26
    {
39✔
27
        m_code_pages.emplace_back();  // create empty page
39✔
28

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

37
    const std::vector<IR::Block>& Compiler::intermediateRepresentation() const noexcept
26✔
38
    {
26✔
39
        return m_code_pages;
26✔
40
    }
41

42
    const std::vector<std::string>& Compiler::symbols() const noexcept
52✔
43
    {
52✔
44
        return m_symbols;
52✔
45
    }
46

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

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

60
    std::optional<uint16_t> Compiler::getBuiltin(const std::string& name) noexcept
1,896✔
61
    {
1,896✔
62
        const auto it = std::ranges::find_if(Builtins::builtins,
1,896✔
63
                                             [&name](const std::pair<std::string, Value>& element) -> bool {
86,373✔
64
                                                 return name == element.first;
84,477✔
65
                                             });
66
        if (it != Builtins::builtins.end())
1,896✔
67
            return static_cast<uint16_t>(std::distance(Builtins::builtins.begin(), it));
436✔
68
        return std::nullopt;
1,460✔
69
    }
1,896✔
70

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

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

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

103
            default:
104
                return false;
587✔
105
        }
106
    }
749✔
107

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

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

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

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

162
                case Keyword::Set:
163
                    [[fallthrough]];
164
                case Keyword::Let:
165
                    [[fallthrough]];
166
                case Keyword::Mut:
167
                    compileLetMutSet(keyword, x, p);
375✔
168
                    break;
480✔
169

170
                case Keyword::Fun:
171
                    compileFunction(x, p, is_result_unused, var_name);
109✔
172
                    break;
306✔
173

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

188
                case Keyword::While:
189
                    compileWhile(x, p);
32✔
190
                    break;
31✔
191

192
                case Keyword::Import:
UNCOV
193
                    compilePluginImport(x, p);
×
194
                    break;
3✔
195

196
                case Keyword::Del:
197
                    page(p).emplace_back(DEL, addSymbol(x.constList()[1]));
3✔
198
                    break;
3✔
199
            }
1,008✔
200
        }
987✔
201
        else if (x.nodeType() == NodeType::List)
1,667✔
202
        {
203
            // if we are here, we should have a function name
204
            // push arguments first, then function name, then call it
205
            handleCalls(x, p, is_result_unused, is_terminal, var_name);
1,667✔
206
        }
1,654✔
207
        else
UNCOV
208
            throwCompilerError("boop", x);  // FIXME
×
209
    }
6,965✔
210

211
    void Compiler::compileSymbol(const Node& x, const Page p, const bool is_result_unused)
1,896✔
212
    {
1,896✔
213
        const std::string& name = x.string();
1,896✔
214

215
        if (const auto it_builtin = getBuiltin(name))
3,792✔
216
            page(p).emplace_back(Instruction::BUILTIN, it_builtin.value());
436✔
217
        else if (getOperator(name).has_value())
1,460✔
218
            throwCompilerError(fmt::format("Found a free standing operator: `{}`", name), x);
1✔
219
        else
220
            page(p).emplace_back(LOAD_SYMBOL, addSymbol(x));  // using the variable
1,459✔
221

222
        if (is_result_unused)
1,895✔
223
        {
UNCOV
224
            compilerWarning("Statement has no effect", x);
×
UNCOV
225
            page(p).emplace_back(POP);
×
UNCOV
226
        }
×
227
    }
1,896✔
228

229
    void Compiler::compileListInstruction(const Node& c0, const Node& x, const Page p, const bool is_result_unused)
482✔
230
    {
482✔
231
        std::string name = c0.string();
482✔
232
        Instruction inst = getListInstruction(name).value();
482✔
233

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

242
        // compile arguments in reverse order
243
        for (std::size_t i = x.constList().size() - 1u; i > 0; --i)
960✔
244
        {
245
            const auto node = x.constList()[i];
478✔
246
            if (nodeProducesOutput(node))
478✔
247
                compileExpression(node, p, false, false);
477✔
248
            else
249
                throwCompilerError(fmt::format("Invalid node inside call to {}", name), node);
1✔
250
        }
478✔
251

252
        // put inst and number of arguments
253
        std::size_t inst_argc;
481✔
254
        switch (inst)
481✔
255
        {
435✔
256
            case LIST:
257
                inst_argc = argc;
435✔
258
                break;
471✔
259

260
            case APPEND:
261
            case APPEND_IN_PLACE:
262
            case CONCAT:
263
            case CONCAT_IN_PLACE:
264
                inst_argc = argc - 1;
36✔
265
                break;
46✔
266

267
            default:
268
                inst_argc = 0;
10✔
269
                break;
10✔
270
        }
481✔
271
        page(p).emplace_back(inst, static_cast<uint16_t>(inst_argc));
481✔
272

273
        if (is_result_unused && name.back() != '!')  // in-place functions never push a value
481✔
274
        {
UNCOV
275
            compilerWarning("Ignoring return value of function", x);
×
UNCOV
276
            page(p).emplace_back(POP);
×
UNCOV
277
        }
×
278
    }
483✔
279

280
    void Compiler::compileIf(const Node& x, const Page p, const bool is_result_unused, const bool is_terminal, const std::string& var_name)
289✔
281
    {
289✔
282
        // compile condition
283
        compileExpression(x.constList()[1], p, false, false);
289✔
284

285
        // jump only if needed to the if
286
        const auto label_then = IR::Entity::Label();
289✔
287
        page(p).emplace_back(IR::Entity::GotoIf(label_then, true));
289✔
288

289
        // else code
290
        if (x.constList().size() == 4)  // we have an else clause
289✔
291
            compileExpression(x.constList()[3], p, is_result_unused, is_terminal, var_name);
276✔
292

293
        // when else is finished, jump to end
294
        const auto label_end = IR::Entity::Label();
289✔
295
        page(p).emplace_back(IR::Entity::Goto(label_end));
289✔
296

297
        // absolute address to jump to if condition is true
298
        page(p).emplace_back(label_then);
289✔
299
        // if code
300
        compileExpression(x.constList()[2], p, is_result_unused, is_terminal, var_name);
289✔
301
        // set jump to end pos
302
        page(p).emplace_back(label_end);
289✔
303
    }
289✔
304

305
    void Compiler::compileFunction(const Node& x, const Page p, const bool is_result_unused, const std::string& var_name)
109✔
306
    {
109✔
307
        if (const auto args = x.constList()[1]; args.nodeType() != NodeType::List)
111✔
308
            throwCompilerError(fmt::format("Expected a well formed argument(s) list, got a {}", typeToString(args)), args);
1✔
309
        if (x.constList().size() != 3)
108✔
310
            throwCompilerError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
1✔
311

312
        // capture, if needed
313
        bool is_closure = false;
107✔
314
        for (const auto& node : x.constList()[1].constList())
290✔
315
        {
316
            if (node.nodeType() == NodeType::Capture)
183✔
317
            {
318
                page(p).emplace_back(CAPTURE, addSymbol(node));
36✔
319
                is_closure = true;
36✔
320
            }
36✔
321
        }
183✔
322

323
        // create new page for function body
324
        m_code_pages.emplace_back();
107✔
325
        const auto function_body_page = Page { .index = m_code_pages.size() - 1, .is_temp = false };
107✔
326
        // save page_id into the constants table as PageAddr and load the const
327
        page(p).emplace_back(is_closure ? MAKE_CLOSURE : LOAD_CONST, addValue(function_body_page.index, x));
107✔
328

329
        // pushing arguments from the stack into variables in the new scope
330
        for (const auto& node : x.constList()[1].constList())
290✔
331
        {
332
            if (node.nodeType() == NodeType::Symbol)
183✔
333
                page(function_body_page).emplace_back(STORE, addSymbol(node));
147✔
334
        }
183✔
335

336
        // push body of the function
337
        compileExpression(x.constList()[2], function_body_page, false, true, var_name);
107✔
338

339
        // return last value on the stack
340
        page(function_body_page).emplace_back(RET);
107✔
341

342
        // if the computed function is unused, pop it
343
        if (is_result_unused)
107✔
344
        {
UNCOV
345
            compilerWarning("Unused declared function", x);
×
UNCOV
346
            page(p).emplace_back(POP);
×
UNCOV
347
        }
×
348
    }
109✔
349

350
    void Compiler::compileLetMutSet(const Keyword n, const Node& x, const Page p)
375✔
351
    {
375✔
352
        if (const auto sym = x.constList()[1]; sym.nodeType() != NodeType::Symbol)
379✔
UNCOV
353
            throwCompilerError(fmt::format("Expected a symbol, got a {}", typeToString(sym)), sym);
×
354
        if (x.constList().size() != 3)
375✔
355
            throwCompilerError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
1✔
356

357
        const std::string name = x.constList()[1].string();
374✔
358
        uint16_t i = addSymbol(x.constList()[1]);
374✔
359

360
        // put value before symbol id
361
        // starting at index = 2 because x is a (let|mut|set variable ...) node
362
        for (std::size_t idx = 2, end = x.constList().size(); idx < end; ++idx)
748✔
363
            compileExpression(x.constList()[idx], p, false, false, name);
374✔
364

365
        if (n == Keyword::Let || n == Keyword::Mut)
371✔
366
            page(p).emplace_back(STORE, i);
290✔
367
        else
368
            page(p).emplace_back(SET_VAL, i);
81✔
369
    }
375✔
370

371
    void Compiler::compileWhile(const Node& x, const Page p)
32✔
372
    {
32✔
373
        if (x.constList().size() != 3)
32✔
374
            throwCompilerError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
1✔
375

376
        // save current position to jump there at the end of the loop
377
        const auto label_loop = IR::Entity::Label();
31✔
378
        page(p).emplace_back(label_loop);
31✔
379
        // push condition
380
        compileExpression(x.constList()[1], p, false, false);
31✔
381
        // absolute jump to end of block if condition is false
382
        const auto label_end = IR::Entity::Label();
31✔
383
        page(p).emplace_back(IR::Entity::GotoIf(label_end, false));
31✔
384
        // push code to page
385
        compileExpression(x.constList()[2], p, true, false);
31✔
386

387
        // loop, jump to the condition
388
        page(p).emplace_back(IR::Entity::Goto(label_loop));
31✔
389

390
        // absolute address to jump to if condition is false
391
        page(p).emplace_back(label_end);
31✔
392
    }
32✔
393

UNCOV
394
    void Compiler::compilePluginImport(const Node& x, const Page p)
×
UNCOV
395
    {
×
UNCOV
396
        std::string path;
×
UNCOV
397
        const Node package_node = x.constList()[1];
×
UNCOV
398
        for (std::size_t i = 0, end = package_node.constList().size(); i < end; ++i)
×
399
        {
UNCOV
400
            path += package_node.constList()[i].string();
×
UNCOV
401
            if (i + 1 != end)
×
UNCOV
402
                path += "/";
×
UNCOV
403
        }
×
UNCOV
404
        path += ".arkm";
×
405

406
        // register plugin path in the constants table
UNCOV
407
        uint16_t id = addValue(Node(NodeType::String, path));
×
408
        // add plugin instruction + id of the constant referring to the plugin path
UNCOV
409
        page(p).emplace_back(PLUGIN, id);
×
UNCOV
410
    }
×
411

412
    void Compiler::handleCalls(const Node& x, const Page p, bool is_result_unused, const bool is_terminal, const std::string& var_name)
1,667✔
413
    {
1,667✔
414
        constexpr std::size_t start_index = 1;
1,667✔
415

416
        const auto node = x.constList()[0];
1,667✔
417
        const std::optional<Instruction> maybe_operator = node.nodeType() == NodeType::Symbol ? getOperator(node.string()) : std::nullopt;
1,667✔
418

419
        enum class ShortcircuitOp
420
        {
421
            And,
422
            Or
423
        };
424
        const std::optional<ShortcircuitOp> maybe_shortcircuit =
1,667✔
425
            node.nodeType() == NodeType::Symbol
3,214✔
426
            ? (node.string() == Language::And
3,078✔
427
                   ? std::make_optional(ShortcircuitOp::And)
16✔
428
                   : (node.string() == Language::Or
1,531✔
429
                          ? std::make_optional(ShortcircuitOp::Or)
6✔
430
                          : std::nullopt))
1,525✔
431
            : std::nullopt;
120✔
432

433
        if (maybe_shortcircuit.has_value())
1,667✔
434
        {
435
            // short circuit implementation
436

437
            compileExpression(x.constList()[1], p, false, false);
22✔
438
            page(p).emplace_back(DUP);
22✔
439

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

454
                compileExpression(x.constList()[i], p, false, false);
30✔
455
                if (i + 1 != end)
30✔
456
                    page(p).emplace_back(DUP);
8✔
457
            }
30✔
458

459
            page(p).emplace_back(label_shortcircuit);
22✔
460
        }
22✔
461
        else if (!maybe_operator.has_value())
1,645✔
462
        {
463
            if (is_terminal && x.constList()[0].nodeType() == NodeType::Symbol && var_name == x.constList()[0].string())
895✔
464
            {
465
                // push the arguments in reverse order
466
                for (std::size_t i = x.constList().size() - 1; i >= start_index; --i)
9✔
467
                {
468
                    if (nodeProducesOutput(x.constList()[i]))
6✔
469
                        compileExpression(x.constList()[i], p, false, false);
5✔
470
                    else
471
                        throwCompilerError(fmt::format("Invalid node inside tail call to `{}'", node.repr()), x);
1✔
472
                }
5✔
473

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

487
                // push arguments on current page
488
                for (auto exp = x.constList().begin() + start_index, exp_end = x.constList().end(); exp != exp_end; ++exp)
2,699✔
489
                {
490
                    if (nodeProducesOutput(*exp))
1,811✔
491
                        compileExpression(*exp, p, false, false);
1,810✔
492
                    else
493
                        throwCompilerError(fmt::format("Invalid node inside call to `{}'", node.repr()), x);
1✔
494
                }
1,807✔
495
                // push proc from temp page
496
                for (const auto& inst : m_temp_pages.back())
1,889✔
497
                    page(p).push_back(inst);
1,005✔
498
                m_temp_pages.pop_back();
884✔
499

500
                // number of arguments
501
                std::size_t args_count = 0;
884✔
502
                for (auto it = x.constList().begin() + 1, it_end = x.constList().end(); it != it_end; ++it)
2,689✔
503
                {
504
                    if (it->nodeType() != NodeType::Capture)
1,805✔
505
                        args_count++;
1,805✔
506
                }
1,805✔
507
                // call the procedure
508
                page(p).emplace_back(CALL, args_count);
884✔
509
            }
892✔
510
        }
884✔
511
        else  // operator
512
        {
513
            // retrieve operator
514
            auto op = maybe_operator.value();
750✔
515

516
            if (op == ASSERT)
750✔
517
                is_result_unused = false;
6✔
518

519
            // push arguments on current page
520
            std::size_t exp_count = 0;
750✔
521
            for (std::size_t index = start_index, size = x.constList().size(); index < size; ++index)
2,109✔
522
            {
523
                if (nodeProducesOutput(x.constList()[index]))
1,359✔
524
                    compileExpression(x.constList()[index], p, false, false);
1,358✔
525
                else
526
                    throwCompilerError(fmt::format("Invalid node inside call to operator `{}'", node.repr()), x);
1✔
527

528
                if ((index + 1 < size && x.constList()[index + 1].nodeType() != NodeType::Capture) || index + 1 == size)
1,358✔
529
                    exp_count++;
1,358✔
530

531
                // in order to be able to handle things like (op A B C D...)
532
                // which should be transformed into A B op C op D op...
533
                if (exp_count >= 2)
1,358✔
534
                    page(p).emplace_back(op);
611✔
535
            }
1,358✔
536

537
            if (isUnaryInst(op))
749✔
538
            {
539
                if (exp_count != 1)
162✔
540
                    throwCompilerError(fmt::format("Operator needs one argument, but was called with {}", exp_count), x.constList()[0]);
1✔
541
                page(p).emplace_back(op);
161✔
542
            }
161✔
543
            else if (exp_count <= 1)
587✔
544
            {
545
                throwCompilerError(fmt::format("Operator needs two arguments, but was called with {}", exp_count), x.constList()[0]);
2✔
546
            }
547

548
            // need to check we didn't push the (op A B C D...) things for operators not supporting it
549
            if (exp_count > 2)
746✔
550
            {
551
                switch (op)
21✔
552
                {
21✔
553
                    // authorized instructions
554
                    case ADD: [[fallthrough]];
555
                    case SUB: [[fallthrough]];
556
                    case MUL: [[fallthrough]];
557
                    case DIV: [[fallthrough]];
558
                    case MOD:
559
                        break;
21✔
560

561
                    default:
UNCOV
562
                        throwCompilerError(
×
UNCOV
563
                            fmt::format(
×
UNCOV
564
                                "can not create a chained expression (of length {}) for operator `{}'. You most likely forgot a `)'.",
×
565
                                exp_count,
UNCOV
566
                                Language::operators[static_cast<std::size_t>(op - FIRST_OPERATOR)]),
×
UNCOV
567
                            x);
×
568
                }
21✔
569
            }
21✔
570
        }
750✔
571

572
        if (is_result_unused)
1,652✔
573
            page(p).emplace_back(POP);
625✔
574
    }
1,680✔
575

576
    uint16_t Compiler::addSymbol(const Node& sym)
2,234✔
577
    {
2,234✔
578
        // otherwise, add the symbol, and return its id in the table
579
        auto it = std::ranges::find(m_symbols, sym.string());
2,234✔
580
        if (it == m_symbols.end())
2,234✔
581
        {
582
            m_symbols.push_back(sym.string());
275✔
583
            it = m_symbols.begin() + static_cast<std::vector<std::string>::difference_type>(m_symbols.size() - 1);
275✔
584
        }
275✔
585

586
        const auto distance = std::distance(m_symbols.begin(), it);
2,234✔
587
        if (distance < std::numeric_limits<uint16_t>::max())
2,234✔
588
            return static_cast<uint16_t>(distance);
4,468✔
UNCOV
589
        throwCompilerError("Too many symbols (exceeds 65'536), aborting compilation.", sym);
×
590
    }
2,234✔
591

592
    uint16_t Compiler::addValue(const Node& x)
1,912✔
593
    {
1,912✔
594
        const ValTableElem v(x);
1,912✔
595
        auto it = std::ranges::find(m_values, v);
1,912✔
596
        if (it == m_values.end())
1,912✔
597
        {
598
            m_values.push_back(v);
543✔
599
            it = m_values.begin() + static_cast<std::vector<ValTableElem>::difference_type>(m_values.size() - 1);
543✔
600
        }
543✔
601

602
        const auto distance = std::distance(m_values.begin(), it);
1,912✔
603
        if (distance < std::numeric_limits<uint16_t>::max())
1,912✔
604
            return static_cast<uint16_t>(distance);
1,912✔
UNCOV
605
        throwCompilerError("Too many values (exceeds 65'536), aborting compilation.", x);
×
606
    }
1,912✔
607

608
    uint16_t Compiler::addValue(const std::size_t page_id, const Node& current)
107✔
609
    {
107✔
610
        const ValTableElem v(page_id);
107✔
611
        auto it = std::ranges::find(m_values, v);
107✔
612
        if (it == m_values.end())
107✔
613
        {
614
            m_values.push_back(v);
107✔
615
            it = m_values.begin() + static_cast<std::vector<ValTableElem>::difference_type>(m_values.size() - 1);
107✔
616
        }
107✔
617

618
        const auto distance = std::distance(m_values.begin(), it);
107✔
619
        if (distance < std::numeric_limits<uint16_t>::max())
107✔
620
            return static_cast<uint16_t>(distance);
107✔
UNCOV
621
        throwCompilerError("Too many values (exceeds 65'536), aborting compilation.", current);
×
622
    }
107✔
623
}
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