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

ArkScript-lang / Ark / 12304789397

10 Dec 2024 10:04PM UTC coverage: 77.378% (-0.5%) from 77.864%
12304789397

push

github

SuperFola
fix(parser): fixing row/col computation while backtracking in the parser, leading to finally correct symbol underlined in errors

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

258 existing lines in 6 files now uncovered.

5490 of 7095 relevant lines covered (77.38%)

9291.12 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

105
            default:
106
                return false;
589✔
107
        }
108
    }
744✔
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,460✔
121
    {
7,460✔
122
        // register symbols
123
        if (x.nodeType() == NodeType::Symbol)
7,460✔
124
            compileSymbol(x, p, is_result_unused);
1,747✔
125
        else if (x.nodeType() == NodeType::Field)
5,713✔
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,486✔
137
        {
138
            uint16_t i = addValue(x);
2,111✔
139

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

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

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

179
                case Keyword::Begin:
180
                {
181
                    for (std::size_t i = 1, size = x.constList().size(); i < size; ++i)
1,273✔
182
                        compileExpression(
2,068✔
183
                            x.constList()[i],
1,034✔
184
                            p,
1,034✔
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,034✔
187
                            // If the begin is a terminal node, only its last node is terminal.
188
                            is_terminal && (i == size - 1),
1,034✔
189
                            var_name);
1,034✔
190
                    break;
226✔
191
                }
29✔
192

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

197
                case Keyword::Import:
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,104✔
205
        }
1,083✔
206
        else if (x.nodeType() == NodeType::List)
1,733✔
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,733✔
211
        }
1,720✔
212
        else
213
            throwCompilerError(
×
214
                fmt::format(
×
215
                    "NodeType `{}' not handled in Compiler::compileExpression. Please fill an issue on GitHub: https://github.com/ArkScript-lang/Ark",
×
216
                    typeToString(x)),
×
217
                x);
×
218
    }
7,460✔
219

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

224
        if (const auto it_builtin = getBuiltin(name))
3,948✔
225
            page(p).emplace_back(Instruction::BUILTIN, it_builtin.value());
471✔
226
        else if (getOperator(name).has_value())
1,503✔
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,502✔
230

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

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

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

255
        // compile arguments in reverse order
256
        for (std::size_t i = x.constList().size() - 1u; i > 0; --i)
1,097✔
257
        {
258
            const auto node = x.constList()[i];
580✔
259
            if (nodeProducesOutput(node))
580✔
260
                compileExpression(node, p, false, false);
579✔
261
            else
262
                throwCompilerError(fmt::format("Invalid node inside call to {}", name), node);
1✔
263
        }
580✔
264

265
        // put inst and number of arguments
266
        std::size_t inst_argc = 0;
516✔
267
        switch (inst)
516✔
268
        {
465✔
269
            case LIST:
270
                inst_argc = argc;
465✔
271
                break;
499✔
272

273
            case APPEND:
274
            case APPEND_IN_PLACE:
275
            case CONCAT:
276
            case CONCAT_IN_PLACE:
277
                inst_argc = argc - 1;
34✔
278
                break;
44✔
279

280
            case POP_LIST:
281
            case POP_LIST_IN_PLACE:
282
                inst_argc = 0;
10✔
283
                break;
17✔
284

285
            default:
286
                break;
7✔
287
        }
516✔
288
        page(p).emplace_back(inst, static_cast<uint16_t>(inst_argc));
516✔
289

290
        if (is_result_unused && name.back() != '!' && inst <= POP_LIST_IN_PLACE)  // in-place functions never push a value
516✔
291
        {
UNCOV
292
            compilerWarning("Ignoring return value of function", x);
×
UNCOV
293
            page(p).emplace_back(POP);
×
UNCOV
294
        }
×
295
    }
518✔
296

297
    void Compiler::compileIf(const Node& x, const Page p, const bool is_result_unused, const bool is_terminal, const std::string& var_name)
300✔
298
    {
300✔
299
        // compile condition
300
        compileExpression(x.constList()[1], p, false, false);
300✔
301

302
        // jump only if needed to the if
303
        const auto label_then = IR::Entity::Label(m_current_label++);
300✔
304
        page(p).emplace_back(IR::Entity::GotoIf(label_then, true));
300✔
305

306
        // else code
307
        if (x.constList().size() == 4)  // we have an else clause
300✔
308
            compileExpression(x.constList()[3], p, is_result_unused, is_terminal, var_name);
290✔
309

310
        // when else is finished, jump to end
311
        const auto label_end = IR::Entity::Label(m_current_label++);
300✔
312
        page(p).emplace_back(IR::Entity::Goto(label_end));
300✔
313

314
        // absolute address to jump to if condition is true
315
        page(p).emplace_back(label_then);
300✔
316
        // if code
317
        compileExpression(x.constList()[2], p, is_result_unused, is_terminal, var_name);
300✔
318
        // set jump to end pos
319
        page(p).emplace_back(label_end);
300✔
320
    }
300✔
321

322
    void Compiler::compileFunction(const Node& x, const Page p, const bool is_result_unused, const std::string& var_name)
132✔
323
    {
132✔
324
        if (const auto args = x.constList()[1]; args.nodeType() != NodeType::List)
134✔
325
            throwCompilerError(fmt::format("Expected a well formed argument(s) list, got a {}", typeToString(args)), args);
1✔
326
        if (x.constList().size() != 3)
131✔
327
            throwCompilerError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
1✔
328

329
        // capture, if needed
330
        bool is_closure = false;
130✔
331
        for (const auto& node : x.constList()[1].constList())
348✔
332
        {
333
            if (node.nodeType() == NodeType::Capture)
218✔
334
            {
335
                page(p).emplace_back(CAPTURE, addSymbol(node));
54✔
336
                is_closure = true;
54✔
337
            }
54✔
338
        }
218✔
339

340
        // create new page for function body
341
        m_code_pages.emplace_back();
130✔
342
        const auto function_body_page = Page { .index = m_code_pages.size() - 1, .is_temp = false };
130✔
343
        // save page_id into the constants table as PageAddr and load the const
344
        page(p).emplace_back(is_closure ? MAKE_CLOSURE : LOAD_CONST, addValue(function_body_page.index, x));
130✔
345

346
        // pushing arguments from the stack into variables in the new scope
347
        for (const auto& node : x.constList()[1].constList())
348✔
348
        {
349
            if (node.nodeType() == NodeType::Symbol)
218✔
350
                page(function_body_page).emplace_back(STORE, addSymbol(node));
164✔
351
        }
218✔
352

353
        // push body of the function
354
        compileExpression(x.constList()[2], function_body_page, false, true, var_name);
130✔
355

356
        // return last value on the stack
357
        page(function_body_page).emplace_back(RET);
130✔
358

359
        // if the computed function is unused, pop it
360
        if (is_result_unused)
130✔
361
        {
362
            compilerWarning("Unused declared function", x);
×
UNCOV
363
            page(p).emplace_back(POP);
×
UNCOV
364
        }
×
365
    }
132✔
366

367
    void Compiler::compileLetMutSet(const Keyword n, const Node& x, const Page p)
401✔
368
    {
401✔
369
        if (const auto sym = x.constList()[1]; sym.nodeType() != NodeType::Symbol)
405✔
UNCOV
370
            throwCompilerError(fmt::format("Expected a symbol, got a {}", typeToString(sym)), sym);
×
371
        if (x.constList().size() != 3)
401✔
372
            throwCompilerError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
1✔
373

374
        const std::string name = x.constList()[1].string();
400✔
375
        uint16_t i = addSymbol(x.constList()[1]);
400✔
376

377
        // put value before symbol id
378
        // starting at index = 2 because x is a (let|mut|set variable ...) node
379
        for (std::size_t idx = 2, end = x.constList().size(); idx < end; ++idx)
800✔
380
            compileExpression(x.constList()[idx], p, false, false, name);
400✔
381

382
        if (n == Keyword::Let || n == Keyword::Mut)
397✔
383
            page(p).emplace_back(STORE, i);
333✔
384
        else
385
            page(p).emplace_back(SET_VAL, i);
64✔
386
    }
401✔
387

388
    void Compiler::compileWhile(const Node& x, const Page p)
29✔
389
    {
29✔
390
        if (x.constList().size() != 3)
29✔
391
            throwCompilerError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
1✔
392

393
        // save current position to jump there at the end of the loop
394
        const auto label_loop = IR::Entity::Label(m_current_label++);
28✔
395
        page(p).emplace_back(label_loop);
28✔
396
        // push condition
397
        compileExpression(x.constList()[1], p, false, false);
28✔
398
        // absolute jump to end of block if condition is false
399
        const auto label_end = IR::Entity::Label(m_current_label++);
28✔
400
        page(p).emplace_back(IR::Entity::GotoIf(label_end, false));
28✔
401
        // push code to page
402
        compileExpression(x.constList()[2], p, true, false);
28✔
403

404
        // loop, jump to the condition
405
        page(p).emplace_back(IR::Entity::Goto(label_loop));
28✔
406

407
        // absolute address to jump to if condition is false
408
        page(p).emplace_back(label_end);
28✔
409
    }
29✔
410

411
    void Compiler::compilePluginImport(const Node& x, const Page p)
×
412
    {
×
413
        std::string path;
×
UNCOV
414
        const Node package_node = x.constList()[1];
×
UNCOV
415
        for (std::size_t i = 0, end = package_node.constList().size(); i < end; ++i)
×
416
        {
UNCOV
417
            path += package_node.constList()[i].string();
×
418
            if (i + 1 != end)
×
419
                path += "/";
×
UNCOV
420
        }
×
UNCOV
421
        path += ".arkm";
×
422

423
        // register plugin path in the constants table
UNCOV
424
        uint16_t id = addValue(Node(NodeType::String, path));
×
425
        // add plugin instruction + id of the constant referring to the plugin path
UNCOV
426
        page(p).emplace_back(PLUGIN, id);
×
UNCOV
427
    }
×
428

429
    void Compiler::handleCalls(const Node& x, const Page p, bool is_result_unused, const bool is_terminal, const std::string& var_name)
1,733✔
430
    {
1,733✔
431
        constexpr std::size_t start_index = 1;
1,733✔
432

433
        const auto node = x.constList()[0];
1,733✔
434
        const std::optional<Instruction> maybe_operator = node.nodeType() == NodeType::Symbol ? getOperator(node.string()) : std::nullopt;
1,733✔
435

436
        enum class ShortcircuitOp
437
        {
438
            And,
439
            Or
440
        };
441
        const std::optional<ShortcircuitOp> maybe_shortcircuit =
1,733✔
442
            node.nodeType() == NodeType::Symbol
3,337✔
443
            ? (node.string() == Language::And
3,194✔
444
                   ? std::make_optional(ShortcircuitOp::And)
14✔
445
                   : (node.string() == Language::Or
1,590✔
446
                          ? std::make_optional(ShortcircuitOp::Or)
6✔
447
                          : std::nullopt))
1,584✔
448
            : std::nullopt;
129✔
449

450
        if (maybe_shortcircuit.has_value())
1,733✔
451
        {
452
            // short circuit implementation
453

454
            compileExpression(x.constList()[1], p, false, false);
20✔
455
            page(p).emplace_back(DUP);
20✔
456

457
            const auto label_shortcircuit = IR::Entity::Label(m_current_label++);
20✔
458
            for (std::size_t i = 2, end = x.constList().size(); i < end; ++i)
48✔
459
            {
460
                switch (maybe_shortcircuit.value())
28✔
461
                {
18✔
462
                    case ShortcircuitOp::And:
463
                        page(p).emplace_back(IR::Entity::GotoIf(label_shortcircuit, false));
18✔
464
                        break;
28✔
465
                    case ShortcircuitOp::Or:
466
                        page(p).emplace_back(IR::Entity::GotoIf(label_shortcircuit, true));
10✔
467
                        break;
10✔
468
                }
28✔
469
                page(p).emplace_back(POP);
28✔
470

471
                compileExpression(x.constList()[i], p, false, false);
28✔
472
                if (i + 1 != end)
28✔
473
                    page(p).emplace_back(DUP);
8✔
474
            }
28✔
475

476
            page(p).emplace_back(label_shortcircuit);
20✔
477
        }
20✔
478
        else if (!maybe_operator.has_value())
1,713✔
479
        {
480
            if (is_terminal && x.constList()[0].nodeType() == NodeType::Symbol && var_name == x.constList()[0].string())
968✔
481
            {
482
                // push the arguments in reverse order
483
                for (std::size_t i = x.constList().size() - 1; i >= start_index; --i)
25✔
484
                {
485
                    if (nodeProducesOutput(x.constList()[i]))
17✔
486
                        compileExpression(x.constList()[i], p, false, false);
16✔
487
                    else
488
                        throwCompilerError(fmt::format("Invalid node inside tail call to `{}'", node.repr()), x);
1✔
489
                }
16✔
490

491
                // jump to the top of the function
492
                page(p).emplace_back(JUMP, 0_u16);
7✔
493
                return;  // skip the potential Instruction::POP at the end
7✔
494
            }
495
            else
496
            {
497
                m_temp_pages.emplace_back();
960✔
498
                const auto proc_page = Page { .index = m_temp_pages.size() - 1u, .is_temp = true };
960✔
499
                // closure chains have been handled (eg: closure.field.field.function)
500
                compileExpression(node, proc_page, false, false);  // storing proc
960✔
501
                if (m_temp_pages.back().empty())
957✔
502
                    throwCompilerError(fmt::format("Can not call {}", x.constList()[0].repr()), x);
1✔
503

504
                // push arguments on current page
505
                for (auto exp = x.constList().begin() + start_index, exp_end = x.constList().end(); exp != exp_end; ++exp)
2,878✔
506
                {
507
                    if (nodeProducesOutput(*exp))
1,922✔
508
                        compileExpression(*exp, p, false, false);
1,921✔
509
                    else
510
                        throwCompilerError(fmt::format("Invalid node inside call to `{}'", node.repr()), x);
1✔
511
                }
1,918✔
512
                // push proc from temp page
513
                for (const auto& inst : m_temp_pages.back())
2,036✔
514
                    page(p).push_back(inst);
1,084✔
515
                m_temp_pages.pop_back();
952✔
516

517
                // number of arguments
518
                std::size_t args_count = 0;
952✔
519
                for (auto it = x.constList().begin() + 1, it_end = x.constList().end(); it != it_end; ++it)
2,868✔
520
                {
521
                    if (it->nodeType() != NodeType::Capture)
1,916✔
522
                        args_count++;
1,916✔
523
                }
1,916✔
524
                // call the procedure
525
                page(p).emplace_back(CALL, args_count);
952✔
526
            }
960✔
527
        }
952✔
528
        else  // operator
529
        {
530
            // retrieve operator
531
            auto op = maybe_operator.value();
745✔
532

533
            if (op == ASSERT)
745✔
534
                is_result_unused = false;
3✔
535

536
            // push arguments on current page
537
            std::size_t exp_count = 0;
745✔
538
            for (std::size_t index = start_index, size = x.constList().size(); index < size; ++index)
2,098✔
539
            {
540
                if (nodeProducesOutput(x.constList()[index]))
1,353✔
541
                    compileExpression(x.constList()[index], p, false, false);
1,352✔
542
                else
543
                    throwCompilerError(fmt::format("Invalid node inside call to operator `{}'", node.repr()), x);
1✔
544

545
                if ((index + 1 < size && x.constList()[index + 1].nodeType() != NodeType::Capture) || index + 1 == size)
1,352✔
546
                    exp_count++;
1,352✔
547

548
                // in order to be able to handle things like (op A B C D...)
549
                // which should be transformed into A B op C op D op...
550
                if (exp_count >= 2)
1,352✔
551
                    page(p).emplace_back(op);
610✔
552
            }
1,352✔
553

554
            if (isUnaryInst(op))
744✔
555
            {
556
                if (exp_count != 1)
155✔
557
                    throwCompilerError(fmt::format("Operator needs one argument, but was called with {}", exp_count), x.constList()[0]);
1✔
558
                page(p).emplace_back(op);
154✔
559
            }
154✔
560
            else if (exp_count <= 1)
589✔
561
            {
562
                throwCompilerError(fmt::format("Operator needs two arguments, but was called with {}", exp_count), x.constList()[0]);
2✔
563
            }
564

565
            // need to check we didn't push the (op A B C D...) things for operators not supporting it
566
            if (exp_count > 2)
741✔
567
            {
568
                switch (op)
18✔
569
                {
18✔
570
                    // authorized instructions
571
                    case ADD: [[fallthrough]];
572
                    case SUB: [[fallthrough]];
573
                    case MUL: [[fallthrough]];
574
                    case DIV: [[fallthrough]];
575
                    case MOD:
576
                        break;
18✔
577

578
                    default:
UNCOV
579
                        throwCompilerError(
×
UNCOV
580
                            fmt::format(
×
UNCOV
581
                                "can not create a chained expression (of length {}) for operator `{}'. You most likely forgot a `)'.",
×
582
                                exp_count,
UNCOV
583
                                Language::operators[static_cast<std::size_t>(op - FIRST_OPERATOR)]),
×
UNCOV
584
                            x);
×
585
                }
18✔
586
            }
18✔
587
        }
745✔
588

589
        if (is_result_unused)
1,713✔
590
            page(p).emplace_back(POP);
664✔
591
    }
1,746✔
592

593
    uint16_t Compiler::addSymbol(const Node& sym)
2,358✔
594
    {
2,358✔
595
        // otherwise, add the symbol, and return its id in the table
596
        auto it = std::ranges::find(m_symbols, sym.string());
2,358✔
597
        if (it == m_symbols.end())
2,358✔
598
        {
599
            m_symbols.push_back(sym.string());
358✔
600
            it = m_symbols.begin() + static_cast<std::vector<std::string>::difference_type>(m_symbols.size() - 1);
358✔
601
        }
358✔
602

603
        const auto distance = std::distance(m_symbols.begin(), it);
2,358✔
604
        if (distance < std::numeric_limits<uint16_t>::max())
2,358✔
605
            return static_cast<uint16_t>(distance);
4,716✔
UNCOV
606
        throwCompilerError("Too many symbols (exceeds 65'536), aborting compilation.", sym);
×
607
    }
2,358✔
608

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

619
        const auto distance = std::distance(m_values.begin(), it);
2,111✔
620
        if (distance < std::numeric_limits<uint16_t>::max())
2,111✔
621
            return static_cast<uint16_t>(distance);
2,111✔
UNCOV
622
        throwCompilerError("Too many values (exceeds 65'536), aborting compilation.", x);
×
623
    }
2,111✔
624

625
    uint16_t Compiler::addValue(const std::size_t page_id, const Node& current)
130✔
626
    {
130✔
627
        const ValTableElem v(page_id);
130✔
628
        auto it = std::ranges::find(m_values, v);
130✔
629
        if (it == m_values.end())
130✔
630
        {
631
            m_values.push_back(v);
130✔
632
            it = m_values.begin() + static_cast<std::vector<ValTableElem>::difference_type>(m_values.size() - 1);
130✔
633
        }
130✔
634

635
        const auto distance = std::distance(m_values.begin(), it);
130✔
636
        if (distance < std::numeric_limits<uint16_t>::max())
130✔
637
            return static_cast<uint16_t>(distance);
130✔
UNCOV
638
        throwCompilerError("Too many values (exceeds 65'536), aborting compilation.", current);
×
639
    }
130✔
640
}
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