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

ArkScript-lang / Ark / 11629611787

01 Nov 2024 12:48PM UTC coverage: 77.319% (+0.3%) from 77.042%
11629611787

push

github

SuperFola
feat(tests): adding first test for IR generation and optimization

5209 of 6737 relevant lines covered (77.32%)

9473.78 hits per line

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

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

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

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

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

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

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

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

62
    std::optional<uint16_t> Compiler::getBuiltin(const std::string& name) noexcept
1,940✔
63
    {
1,940✔
64
        const auto it = std::ranges::find_if(Builtins::builtins,
1,940✔
65
                                             [&name](const std::pair<std::string, Value>& element) -> bool {
88,364✔
66
                                                 return name == element.first;
86,424✔
67
                                             });
68
        if (it != Builtins::builtins.end())
1,940✔
69
            return static_cast<uint16_t>(std::distance(Builtins::builtins.begin(), it));
445✔
70
        return std::nullopt;
1,495✔
71
    }
1,940✔
72

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

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

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

105
            default:
106
                return false;
599✔
107
        }
108
    }
765✔
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,119✔
121
    {
7,119✔
122
        // register symbols
123
        if (x.nodeType() == NodeType::Symbol)
7,119✔
124
            compileSymbol(x, p, is_result_unused);
1,726✔
125
        else if (x.nodeType() == NodeType::Field)
5,393✔
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);
214✔
129
            for (auto it = x.constList().begin() + 1, end = x.constList().end(); it != end; ++it)
433✔
130
            {
131
                uint16_t i = addSymbol(*it);
219✔
132
                page(p).emplace_back(GET_FIELD, i);
219✔
133
            }
219✔
134
        }
214✔
135
        // register values
136
        else if (x.nodeType() == NodeType::String || x.nodeType() == NodeType::Number)
5,179✔
137
        {
138
            uint16_t i = addValue(x);
1,955✔
139

140
            if (!is_result_unused)
1,955✔
141
                page(p).emplace_back(LOAD_CONST, i);
1,955✔
142
        }
1,955✔
143
        // empty code block should be nil
144
        else if (x.constList().empty())
3,224✔
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,448✔
154
            compileListInstruction(c0, x, p, is_result_unused);
491✔
155
        // registering structures
156
        else if (x.constList()[0].nodeType() == NodeType::Keyword)
2,733✔
157
        {
158
            switch (const Keyword keyword = x.constList()[0].keyword())
1,029✔
159
            {
298✔
160
                case Keyword::If:
161
                    compileIf(x, p, is_result_unused, is_terminal, var_name);
298✔
162
                    break;
677✔
163

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

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

176
                case Keyword::Begin:
177
                {
178
                    for (std::size_t i = 1, size = x.constList().size(); i < size; ++i)
1,157✔
179
                        compileExpression(
1,904✔
180
                            x.constList()[i],
952✔
181
                            p,
952✔
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,
952✔
184
                            // If the begin is a terminal node, only its last node is terminal.
185
                            is_terminal && (i == size - 1),
952✔
186
                            var_name);
952✔
187
                    break;
192✔
188
                }
32✔
189

190
                case Keyword::While:
191
                    compileWhile(x, p);
32✔
192
                    break;
31✔
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,029✔
202
        }
1,008✔
203
        else if (x.nodeType() == NodeType::List)
1,704✔
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,704✔
208
        }
1,691✔
209
        else
210
            throwCompilerError("boop", x);  // FIXME
×
211
    }
7,119✔
212

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

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

224
        if (is_result_unused)
1,939✔
225
        {
226
            compilerWarning("Statement has no effect", x);
×
227
            page(p).emplace_back(POP);
×
228
        }
×
229
    }
1,940✔
230

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

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

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

254
        // put inst and number of arguments
255
        std::size_t inst_argc;
490✔
256
        switch (inst)
490✔
257
        {
444✔
258
            case LIST:
259
                inst_argc = argc;
444✔
260
                break;
480✔
261

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

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

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

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

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

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

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

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

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

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

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

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

338
        // push body of the function
339
        compileExpression(x.constList()[2], function_body_page, false, true, var_name);
110✔
340

341
        // return last value on the stack
342
        page(function_body_page).emplace_back(RET);
110✔
343

344
        // if the computed function is unused, pop it
345
        if (is_result_unused)
110✔
346
        {
347
            compilerWarning("Unused declared function", x);
×
348
            page(p).emplace_back(POP);
×
349
        }
×
350
    }
112✔
351

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

359
        const std::string name = x.constList()[1].string();
378✔
360
        uint16_t i = addSymbol(x.constList()[1]);
378✔
361

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

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

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

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

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

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

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

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

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

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

421
        enum class ShortcircuitOp
422
        {
423
            And,
424
            Or
425
        };
426
        const std::optional<ShortcircuitOp> maybe_shortcircuit =
1,704✔
427
            node.nodeType() == NodeType::Symbol
3,285✔
428
            ? (node.string() == Language::And
3,146✔
429
                   ? std::make_optional(ShortcircuitOp::And)
16✔
430
                   : (node.string() == Language::Or
1,565✔
431
                          ? std::make_optional(ShortcircuitOp::Or)
6✔
432
                          : std::nullopt))
1,559✔
433
            : std::nullopt;
123✔
434

435
        if (maybe_shortcircuit.has_value())
1,704✔
436
        {
437
            // short circuit implementation
438

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

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

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

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

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

489
                // push arguments on current page
490
                for (auto exp = x.constList().begin() + start_index, exp_end = x.constList().end(); exp != exp_end; ++exp)
2,754✔
491
                {
492
                    if (nodeProducesOutput(*exp))
1,848✔
493
                        compileExpression(*exp, p, false, false);
1,847✔
494
                    else
495
                        throwCompilerError(fmt::format("Invalid node inside call to `{}'", node.repr()), x);
1✔
496
                }
1,844✔
497
                // push proc from temp page
498
                for (const auto& inst : m_temp_pages.back())
1,928✔
499
                    page(p).push_back(inst);
1,026✔
500
                m_temp_pages.pop_back();
902✔
501

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

518
            if (op == ASSERT)
766✔
519
                is_result_unused = false;
6✔
520

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

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

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

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

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

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

574
        if (is_result_unused)
1,686✔
575
            page(p).emplace_back(POP);
638✔
576
    }
1,717✔
577

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

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

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

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

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

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