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

ArkScript-lang / Ark / 12164232004

04 Dec 2024 04:37PM UTC coverage: 77.815% (+0.6%) from 77.19%
12164232004

push

github

SuperFola
feat(name resolution): allow fqn if the scope is only exporting symbols

1 of 3 new or added lines in 2 files covered. (33.33%)

256 existing lines in 11 files now uncovered.

5412 of 6955 relevant lines covered (77.81%)

9300.67 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

105
            default:
106
                return false;
584✔
107
        }
108
    }
739✔
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,296✔
121
    {
7,296✔
122
        // register symbols
123
        if (x.nodeType() == NodeType::Symbol)
7,296✔
124
            compileSymbol(x, p, is_result_unused);
1,721✔
125
        else if (x.nodeType() == NodeType::Field)
5,575✔
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);
227✔
129
            for (auto it = x.constList().begin() + 1, end = x.constList().end(); it != end; ++it)
462✔
130
            {
131
                uint16_t i = addSymbol(*it);
235✔
132
                page(p).emplace_back(GET_FIELD, i);
235✔
133
            }
235✔
134
        }
227✔
135
        // register values
136
        else if (x.nodeType() == NodeType::String || x.nodeType() == NodeType::Number)
5,348✔
137
        {
138
            uint16_t i = addValue(x);
2,023✔
139

140
            if (!is_result_unused)
2,023✔
141
                page(p).emplace_back(LOAD_CONST, i);
2,023✔
142
        }
2,023✔
143
        // namespace nodes
144
        else if (x.nodeType() == NodeType::Namespace)
3,325✔
145
            compileExpression(*x.constArkNamespace().ast, p, is_result_unused, is_terminal, var_name);
19✔
146
        // empty code block should be nil
147
        else if (x.constList().empty())
3,306✔
148
        {
149
            if (!is_result_unused)
×
150
            {
151
                static const std::optional<uint16_t> nil = getBuiltin("nil");
35✔
UNCOV
152
                page(p).emplace_back(BUILTIN, nil.value());
×
UNCOV
153
            }
×
UNCOV
154
        }
×
155
        // list instructions
156
        else if (const auto c0 = x.constList()[0]; c0.nodeType() == NodeType::Symbol && getListInstruction(c0.string()).has_value())
6,612✔
157
            compileListInstruction(c0, x, p, is_result_unused);
492✔
158
        // registering structures
159
        else if (x.constList()[0].nodeType() == NodeType::Keyword)
2,814✔
160
        {
161
            switch (const Keyword keyword = x.constList()[0].keyword())
1,098✔
162
            {
297✔
163
                case Keyword::If:
164
                    compileIf(x, p, is_result_unused, is_terminal, var_name);
297✔
165
                    break;
702✔
166

167
                case Keyword::Set:
168
                    [[fallthrough]];
169
                case Keyword::Let:
170
                    [[fallthrough]];
171
                case Keyword::Mut:
172
                    compileLetMutSet(keyword, x, p);
405✔
173
                    break;
530✔
174

175
                case Keyword::Fun:
176
                    compileFunction(x, p, is_result_unused, var_name);
129✔
177
                    break;
361✔
178

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

193
                case Keyword::While:
194
                    compileWhile(x, p);
29✔
195
                    break;
28✔
196

197
                case Keyword::Import:
UNCOV
198
                    compilePluginImport(x, p);
×
199
                    break;
3✔
200

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

220
    void Compiler::compileSymbol(const Node& x, const Page p, const bool is_result_unused)
1,948✔
221
    {
1,948✔
222
        const std::string& name = x.string();
1,948✔
223

224
        if (const auto it_builtin = getBuiltin(name))
3,896✔
225
            page(p).emplace_back(Instruction::BUILTIN, it_builtin.value());
469✔
226
        else if (getOperator(name).has_value())
1,479✔
227
            throwCompilerError(fmt::format("Found a free standing operator: `{}`", name), x);
1✔
228
        else
229
            page(p).emplace_back(LOAD_SYMBOL, addSymbol(x));  // using the variable
1,478✔
230

231
        if (is_result_unused)
1,947✔
232
        {
UNCOV
233
            compilerWarning("Statement has no effect", x);
×
UNCOV
234
            page(p).emplace_back(POP);
×
UNCOV
235
        }
×
236
    }
1,948✔
237

238
    void Compiler::compileListInstruction(const Node& c0, const Node& x, const Page p, const bool is_result_unused)
492✔
239
    {
492✔
240
        std::string name = c0.string();
492✔
241
        Instruction inst = getListInstruction(name).value();
492✔
242

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

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

261
        // put inst and number of arguments
262
        std::size_t inst_argc;
491✔
263
        switch (inst)
491✔
264
        {
447✔
265
            case LIST:
266
                inst_argc = argc;
447✔
267
                break;
481✔
268

269
            case APPEND:
270
            case APPEND_IN_PLACE:
271
            case CONCAT:
272
            case CONCAT_IN_PLACE:
273
                inst_argc = argc - 1;
34✔
274
                break;
44✔
275

276
            default:
277
                inst_argc = 0;
10✔
278
                break;
10✔
279
        }
491✔
280
        page(p).emplace_back(inst, static_cast<uint16_t>(inst_argc));
491✔
281

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

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

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

298
        // else code
299
        if (x.constList().size() == 4)  // we have an else clause
297✔
300
            compileExpression(x.constList()[3], p, is_result_unused, is_terminal, var_name);
287✔
301

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

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

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

321
        // capture, if needed
322
        bool is_closure = false;
127✔
323
        for (const auto& node : x.constList()[1].constList())
342✔
324
        {
325
            if (node.nodeType() == NodeType::Capture)
215✔
326
            {
327
                page(p).emplace_back(CAPTURE, addSymbol(node));
54✔
328
                is_closure = true;
54✔
329
            }
54✔
330
        }
215✔
331

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

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

345
        // push body of the function
346
        compileExpression(x.constList()[2], function_body_page, false, true, var_name);
127✔
347

348
        // return last value on the stack
349
        page(function_body_page).emplace_back(RET);
127✔
350

351
        // if the computed function is unused, pop it
352
        if (is_result_unused)
127✔
353
        {
UNCOV
354
            compilerWarning("Unused declared function", x);
×
UNCOV
355
            page(p).emplace_back(POP);
×
UNCOV
356
        }
×
357
    }
129✔
358

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

366
        const std::string name = x.constList()[1].string();
404✔
367
        uint16_t i = addSymbol(x.constList()[1]);
404✔
368

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

374
        if (n == Keyword::Let || n == Keyword::Mut)
401✔
375
            page(p).emplace_back(STORE, i);
327✔
376
        else
377
            page(p).emplace_back(SET_VAL, i);
74✔
378
    }
405✔
379

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

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

396
        // loop, jump to the condition
397
        page(p).emplace_back(IR::Entity::Goto(label_loop));
28✔
398

399
        // absolute address to jump to if condition is false
400
        page(p).emplace_back(label_end);
28✔
401
    }
29✔
402

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

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

421
    void Compiler::handleCalls(const Node& x, const Page p, bool is_result_unused, const bool is_terminal, const std::string& var_name)
1,716✔
422
    {
1,716✔
423
        constexpr std::size_t start_index = 1;
1,716✔
424

425
        const auto node = x.constList()[0];
1,716✔
426
        const std::optional<Instruction> maybe_operator = node.nodeType() == NodeType::Symbol ? getOperator(node.string()) : std::nullopt;
1,716✔
427

428
        enum class ShortcircuitOp
429
        {
430
            And,
431
            Or
432
        };
433
        const std::optional<ShortcircuitOp> maybe_shortcircuit =
1,716✔
434
            node.nodeType() == NodeType::Symbol
3,303✔
435
            ? (node.string() == Language::And
3,160✔
436
                   ? std::make_optional(ShortcircuitOp::And)
14✔
437
                   : (node.string() == Language::Or
1,573✔
438
                          ? std::make_optional(ShortcircuitOp::Or)
6✔
439
                          : std::nullopt))
1,567✔
440
            : std::nullopt;
129✔
441

442
        if (maybe_shortcircuit.has_value())
1,716✔
443
        {
444
            // short circuit implementation
445

446
            compileExpression(x.constList()[1], p, false, false);
20✔
447
            page(p).emplace_back(DUP);
20✔
448

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

463
                compileExpression(x.constList()[i], p, false, false);
28✔
464
                if (i + 1 != end)
28✔
465
                    page(p).emplace_back(DUP);
8✔
466
            }
28✔
467

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

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

496
                // push arguments on current page
497
                for (auto exp = x.constList().begin() + start_index, exp_end = x.constList().end(); exp != exp_end; ++exp)
2,841✔
498
                {
499
                    if (nodeProducesOutput(*exp))
1,897✔
500
                        compileExpression(*exp, p, false, false);
1,896✔
501
                    else
502
                        throwCompilerError(fmt::format("Invalid node inside call to `{}'", node.repr()), x);
1✔
503
                }
1,893✔
504
                // push proc from temp page
505
                for (const auto& inst : m_temp_pages.back())
2,012✔
506
                    page(p).push_back(inst);
1,072✔
507
                m_temp_pages.pop_back();
940✔
508

509
                // number of arguments
510
                std::size_t args_count = 0;
940✔
511
                for (auto it = x.constList().begin() + 1, it_end = x.constList().end(); it != it_end; ++it)
2,831✔
512
                {
513
                    if (it->nodeType() != NodeType::Capture)
1,891✔
514
                        args_count++;
1,891✔
515
                }
1,891✔
516
                // call the procedure
517
                page(p).emplace_back(CALL, args_count);
940✔
518
            }
948✔
519
        }
940✔
520
        else  // operator
521
        {
522
            // retrieve operator
523
            auto op = maybe_operator.value();
740✔
524

525
            if (op == ASSERT)
740✔
526
                is_result_unused = false;
3✔
527

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

537
                if ((index + 1 < size && x.constList()[index + 1].nodeType() != NodeType::Capture) || index + 1 == size)
1,342✔
538
                    exp_count++;
1,342✔
539

540
                // in order to be able to handle things like (op A B C D...)
541
                // which should be transformed into A B op C op D op...
542
                if (exp_count >= 2)
1,342✔
543
                    page(p).emplace_back(op);
605✔
544
            }
1,342✔
545

546
            if (isUnaryInst(op))
739✔
547
            {
548
                if (exp_count != 1)
155✔
549
                    throwCompilerError(fmt::format("Operator needs one argument, but was called with {}", exp_count), x.constList()[0]);
1✔
550
                page(p).emplace_back(op);
154✔
551
            }
154✔
552
            else if (exp_count <= 1)
584✔
553
            {
554
                throwCompilerError(fmt::format("Operator needs two arguments, but was called with {}", exp_count), x.constList()[0]);
2✔
555
            }
556

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

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

581
        if (is_result_unused)
1,696✔
582
            page(p).emplace_back(POP);
658✔
583
    }
1,729✔
584

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

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

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

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

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

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