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

ArkScript-lang / Ark / 11087505602

28 Sep 2024 10:31PM UTC coverage: 75.218% (+1.6%) from 73.576%
11087505602

push

github

SuperFola
fix(json compiler): disable import solver in json compiler

1 of 1 new or added line in 1 file covered. (100.0%)

364 existing lines in 24 files now uncovered.

4841 of 6436 relevant lines covered (75.22%)

9644.12 hits per line

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

86.46
/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 <picosha2.h>
8
#include <algorithm>
9
#include <fmt/core.h>
10
#include <fmt/color.h>
11

12
#include <Ark/Constants.hpp>
13
#include <Ark/Literals.hpp>
14
#include <Ark/Utils.hpp>
15
#include <Ark/Builtins/Builtins.hpp>
16
#include <Ark/Compiler/Macros/Processor.hpp>
17

18
namespace Ark
19
{
20
    using namespace internal;
21
    using namespace literals;
22

23
    Compiler::Compiler(const unsigned debug) :
87✔
24
        m_debug(debug)
87✔
25
    {}
174✔
26

27
    void Compiler::process(const Node& ast)
39✔
28
    {
39✔
29
        pushFileHeader();
39✔
30

31
        m_code_pages.emplace_back();  // create empty page
39✔
32

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

40
        pushSymAndValTables();
26✔
41

42
        // push the different code segments
43
        for (std::size_t i = 0, end = m_code_pages.size(); i < end; ++i)
155✔
44
        {
45
            std::vector<Word>& page = m_code_pages[i];
129✔
46
            // just in case we got too far, always add a HALT to be sure the
47
            // VM won't do anything crazy
48
            page.emplace_back(Instruction::HALT);
129✔
49

50
            // push number of elements
51
            const std::size_t page_size = page.size();
129✔
52
            if (page_size > std::numeric_limits<uint16_t>::max())
129✔
53
                throw std::overflow_error(fmt::format("Size of page {} exceeds the maximum size of 2^16 - 1", i));
×
54

55
            m_bytecode.push_back(Instruction::CODE_SEGMENT_START);
129✔
56
            m_bytecode.push_back(static_cast<uint8_t>((page_size & 0xff00) >> 8));
129✔
57
            m_bytecode.push_back(static_cast<uint8_t>(page_size & 0x00ff));
129✔
58

59
            for (auto inst : page)
8,507✔
60
            {
61
                m_bytecode.push_back(inst.padding);
8,378✔
62
                m_bytecode.push_back(inst.opcode);
8,378✔
63

64
                auto [first, second] = inst.bytes();
8,378✔
65
                m_bytecode.push_back(first);
8,378✔
66
                m_bytecode.push_back(second);
8,378✔
67
            }
8,378✔
68
        }
129✔
69

70
        if (m_code_pages.empty())
26✔
71
        {
72
            // code segment with a single instruction
73
            m_bytecode.push_back(Instruction::CODE_SEGMENT_START);
×
UNCOV
74
            m_bytecode.push_back(0_u8);
×
75
            m_bytecode.push_back(1_u8);
×
76

77
            m_bytecode.push_back(0_u8);
×
78
            m_bytecode.push_back(Instruction::HALT);
×
79
            m_bytecode.push_back(0_u8);
×
UNCOV
80
            m_bytecode.push_back(0_u8);
×
UNCOV
81
        }
×
82

83
        constexpr std::size_t header_size = 18;
26✔
84

85
        // generate a hash of the tables + bytecode
86
        std::vector<unsigned char> hash_out(picosha2::k_digest_size);
26✔
87
        picosha2::hash256(m_bytecode.begin() + header_size, m_bytecode.end(), hash_out);
26✔
88
        m_bytecode.insert(m_bytecode.begin() + header_size, hash_out.begin(), hash_out.end());
26✔
89
    }
39✔
90

91
    const bytecode_t& Compiler::bytecode() const noexcept
26✔
92
    {
26✔
93
        return m_bytecode;
26✔
94
    }
95

96
    void Compiler::pushFileHeader() noexcept
39✔
97
    {
39✔
98
        /*
99
            Generating headers:
100
                - lang name (to be sure we are executing an ArkScript file)
101
                    on 4 bytes (ark + padding)
102
                - version (major: 2 bytes, minor: 2 bytes, patch: 2 bytes)
103
                - timestamp (8 bytes, unix format)
104
        */
105

106
        m_bytecode.push_back('a');
39✔
107
        m_bytecode.push_back('r');
39✔
108
        m_bytecode.push_back('k');
39✔
109
        m_bytecode.push_back(0_u8);
39✔
110

111
        // push version
112
        for (const int n : std::array { ARK_VERSION_MAJOR, ARK_VERSION_MINOR, ARK_VERSION_PATCH })
156✔
113
        {
114
            m_bytecode.push_back(static_cast<uint8_t>((n & 0xff00) >> 8));
117✔
115
            m_bytecode.push_back(static_cast<uint8_t>(n & 0x00ff));
117✔
116
        }
117✔
117

118
        // push timestamp
119
        const long long timestamp = std::chrono::duration_cast<std::chrono::seconds>(
39✔
120
                                        std::chrono::system_clock::now().time_since_epoch())
39✔
121
                                        .count();
39✔
122
        for (long i = 0; i < 8; ++i)
351✔
123
        {
124
            const long shift = 8 * (7 - i);
312✔
125
            const auto ts_byte = static_cast<uint8_t>((timestamp & (0xffLL << shift)) >> shift);
312✔
126
            m_bytecode.push_back(ts_byte);
312✔
127
        }
312✔
128
    }
39✔
129

130
    void Compiler::pushSymAndValTables()
26✔
131
    {
26✔
132
        const std::size_t symbol_size = m_symbols.size();
26✔
133
        if (symbol_size > std::numeric_limits<uint16_t>::max())
26✔
UNCOV
134
            throw std::overflow_error(fmt::format("Too many symbols: {}, exceeds the maximum size of 2^16 - 1", symbol_size));
×
135

136
        m_bytecode.push_back(SYM_TABLE_START);
26✔
137
        m_bytecode.push_back(static_cast<uint8_t>((symbol_size & 0xff00) >> 8));
26✔
138
        m_bytecode.push_back(static_cast<uint8_t>(symbol_size & 0x00ff));
26✔
139

140
        for (const auto& sym : m_symbols)
285✔
141
        {
142
            // push the string, null terminated
143
            std::string s = sym.string();
259✔
144
            std::ranges::transform(s, std::back_inserter(m_bytecode), [](const char i) {
2,107✔
145
                return static_cast<uint8_t>(i);
1,848✔
146
            });
147
            m_bytecode.push_back(0_u8);
259✔
148
        }
259✔
149

150
        const std::size_t value_size = m_values.size();
26✔
151
        if (value_size > std::numeric_limits<uint16_t>::max())
26✔
UNCOV
152
            throw std::overflow_error(fmt::format("Too many values: {}, exceeds the maximum size of 2^16 - 1", value_size));
×
153

154
        m_bytecode.push_back(VAL_TABLE_START);
26✔
155
        m_bytecode.push_back(static_cast<uint8_t>((value_size & 0xff00) >> 8));
26✔
156
        m_bytecode.push_back(static_cast<uint8_t>(value_size & 0x00ff));
26✔
157

158
        for (const ValTableElem& val : m_values)
667✔
159
        {
160
            if (val.type == ValTableElemType::Number)
641✔
161
            {
162
                m_bytecode.push_back(NUMBER_TYPE);
108✔
163
                const auto n = std::get<double>(val.value);
108✔
164
                std::string t = std::to_string(n);
108✔
165
                std::ranges::transform(t, std::back_inserter(m_bytecode), [](const char i) {
1,006✔
166
                    return static_cast<uint8_t>(i);
898✔
167
                });
168
            }
108✔
169
            else if (val.type == ValTableElemType::String)
533✔
170
            {
171
                m_bytecode.push_back(STRING_TYPE);
430✔
172
                auto t = std::get<std::string>(val.value);
430✔
173
                std::ranges::transform(t, std::back_inserter(m_bytecode), [](const char i) {
6,407✔
174
                    return static_cast<uint8_t>(i);
5,977✔
175
                });
176
            }
430✔
177
            else if (val.type == ValTableElemType::PageAddr)
103✔
178
            {
179
                m_bytecode.push_back(FUNC_TYPE);
103✔
180
                const std::size_t addr = std::get<std::size_t>(val.value);
103✔
181
                m_bytecode.push_back(static_cast<uint8_t>((addr & 0xff00) >> 8));
103✔
182
                m_bytecode.push_back(static_cast<uint8_t>(addr & 0x00ff));
103✔
183
            }
103✔
184
            else
UNCOV
185
                throw Error("The compiler is trying to put a value in the value table, but the type isn't handled.\nCertainly a logic problem in the compiler source code");
×
186

187
            m_bytecode.push_back(0_u8);
641✔
188
        }
641✔
189
    }
26✔
190

191
    std::optional<uint8_t> Compiler::getOperator(const std::string& name) noexcept
3,007✔
192
    {
3,007✔
193
        const auto it = std::ranges::find(internal::Language::operators, name);
3,007✔
194
        if (it != internal::Language::operators.end())
3,007✔
195
            return static_cast<uint8_t>(std::distance(internal::Language::operators.begin(), it) + FIRST_OPERATOR);
751✔
196
        return std::nullopt;
2,256✔
197
    }
3,007✔
198

199
    std::optional<uint16_t> Compiler::getBuiltin(const std::string& name) noexcept
1,896✔
200
    {
1,896✔
201
        const auto it = std::ranges::find_if(Builtins::builtins,
1,896✔
202
                                             [&name](const std::pair<std::string, Value>& element) -> bool {
86,373✔
203
                                                 return name == element.first;
84,477✔
204
                                             });
205
        if (it != Builtins::builtins.end())
1,896✔
206
            return static_cast<uint16_t>(std::distance(Builtins::builtins.begin(), it));
436✔
207
        return std::nullopt;
1,460✔
208
    }
1,896✔
209

210
    std::optional<Instruction> Compiler::getListInstruction(const std::string& name) noexcept
2,511✔
211
    {
2,511✔
212
        const auto it = std::ranges::find(internal::Language::listInstructions, name);
2,511✔
213
        if (it != internal::Language::listInstructions.end())
2,511✔
214
            return static_cast<Instruction>(std::distance(internal::Language::listInstructions.begin(), it) + LIST);
964✔
215
        return std::nullopt;
1,547✔
216
    }
2,511✔
217

218
    bool Compiler::nodeProducesOutput(const Node& node)
3,654✔
219
    {
3,654✔
220
        if (node.nodeType() == NodeType::List && !node.constList().empty() && node.constList()[0].nodeType() == NodeType::Keyword)
3,654✔
221
            return (node.constList()[0].keyword() == Keyword::Begin && node.constList().size() > 1) ||
34✔
222
                node.constList()[0].keyword() == Keyword::Fun ||
25✔
223
                node.constList()[0].keyword() == Keyword::If;
8✔
224
        return true;  // any other node, function call, symbol, number...
3,637✔
225
    }
3,654✔
226

227
    bool Compiler::isUnaryInst(const Instruction inst) noexcept
749✔
228
    {
749✔
229
        switch (inst)
749✔
230
        {
162✔
231
            case NOT: [[fallthrough]];
232
            case LEN: [[fallthrough]];
233
            case EMPTY: [[fallthrough]];
234
            case TAIL: [[fallthrough]];
235
            case HEAD: [[fallthrough]];
236
            case ISNIL: [[fallthrough]];
237
            case TO_NUM: [[fallthrough]];
238
            case TO_STR: [[fallthrough]];
239
            case TYPE:
240
                return true;
749✔
241

242
            default:
243
                return false;
587✔
244
        }
245
    }
749✔
246

247
    bool Compiler::mayBeFromPlugin(const std::string& name) noexcept
×
248
    {
×
249
        std::string splitted = Utils::splitString(name, ':')[0];
×
250
        const auto it = std::ranges::find_if(m_plugins,
×
251
                                             [&splitted](const std::string& plugin) -> bool {
×
252
                                                 return std::filesystem::path(plugin).stem().string() == splitted;
×
253
                                             });
×
UNCOV
254
        return it != m_plugins.end();
×
255
    }
×
256

257
    void Compiler::compilerWarning(const std::string& message, const Node& node)
×
258
    {
×
UNCOV
259
        fmt::println("{} {}", fmt::styled("Warning", fmt::fg(fmt::color::dark_orange)), Diagnostics::makeContextWithNode(message, node));
×
UNCOV
260
    }
×
261

262
    void Compiler::throwCompilerError(const std::string& message, const Node& node)
13✔
263
    {
13✔
264
        throw CodeError(message, node.filename(), node.line(), node.col(), node.repr());
13✔
265
    }
13✔
266

267
    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✔
268
    {
6,965✔
269
        // register symbols
270
        if (x.nodeType() == NodeType::Symbol)
6,965✔
271
            compileSymbol(x, p, is_result_unused);
1,686✔
272
        else if (x.nodeType() == NodeType::Field)
5,279✔
273
        {
274
            // the parser guarantees us that there is at least 2 elements (eg: a.b)
275
            compileSymbol(x.constList()[0], p, is_result_unused);
210✔
276
            for (auto it = x.constList().begin() + 1, end = x.constList().end(); it != end; ++it)
425✔
277
            {
278
                uint16_t i = addSymbol(*it);
215✔
279
                page(p).emplace_back(GET_FIELD, i);
215✔
280
            }
215✔
281
        }
210✔
282
        // register values
283
        else if (x.nodeType() == NodeType::String || x.nodeType() == NodeType::Number)
5,069✔
284
        {
285
            uint16_t i = addValue(x);
1,912✔
286

287
            if (!is_result_unused)
1,912✔
288
                page(p).emplace_back(LOAD_CONST, i);
1,912✔
289
        }
1,912✔
290
        // empty code block should be nil
291
        else if (x.constList().empty())
3,157✔
292
        {
UNCOV
293
            if (!is_result_unused)
×
294
            {
295
                static const std::optional<uint16_t> nil = getBuiltin("nil");
35✔
296
                page(p).emplace_back(BUILTIN, nil.value());
×
UNCOV
297
            }
×
UNCOV
298
        }
×
299
        // list instructions
300
        else if (const auto c0 = x.constList()[0]; c0.nodeType() == NodeType::Symbol && getListInstruction(c0.string()).has_value())
6,314✔
301
            compileListInstruction(c0, x, p, is_result_unused);
482✔
302
        // registering structures
303
        else if (x.constList()[0].nodeType() == NodeType::Keyword)
2,675✔
304
        {
305
            switch (const Keyword keyword = x.constList()[0].keyword())
1,008✔
306
            {
289✔
307
                case Keyword::If:
308
                    compileIf(x, p, is_result_unused, is_terminal, var_name);
289✔
309
                    break;
664✔
310

311
                case Keyword::Set:
312
                    [[fallthrough]];
313
                case Keyword::Let:
314
                    [[fallthrough]];
315
                case Keyword::Mut:
316
                    compileLetMutSet(keyword, x, p);
375✔
317
                    break;
480✔
318

319
                case Keyword::Fun:
320
                    compileFunction(x, p, is_result_unused, var_name);
109✔
321
                    break;
306✔
322

323
                case Keyword::Begin:
324
                {
325
                    for (std::size_t i = 1, size = x.constList().size(); i < size; ++i)
1,135✔
326
                        compileExpression(
1,870✔
327
                            x.constList()[i],
935✔
328
                            p,
935✔
329
                            // All the nodes in a begin (except for the last one) are producing a result that we want to drop.
330
                            (i != size - 1) || is_result_unused,
935✔
331
                            // If the begin is a terminal node, only its last node is terminal.
332
                            is_terminal && (i == size - 1),
935✔
333
                            var_name);
935✔
334
                    break;
187✔
335
                }
32✔
336

337
                case Keyword::While:
338
                    compileWhile(x, p);
32✔
339
                    break;
31✔
340

341
                case Keyword::Import:
UNCOV
342
                    compilePluginImport(x, p);
×
343
                    break;
3✔
344

345
                case Keyword::Del:
346
                    page(p).emplace_back(DEL, addSymbol(x.constList()[1]));
3✔
347
                    break;
3✔
348
            }
1,008✔
349
        }
987✔
350
        else if (x.nodeType() == NodeType::List)
1,667✔
351
        {
352
            // if we are here, we should have a function name
353
            // push arguments first, then function name, then call it
354
            handleCalls(x, p, is_result_unused, is_terminal, var_name);
1,667✔
355
        }
1,654✔
356
        else
UNCOV
357
            throwCompilerError("boop", x);  // FIXME
×
358
    }
6,965✔
359

360
    void Compiler::compileSymbol(const Node& x, const Page p, const bool is_result_unused)
1,896✔
361
    {
1,896✔
362
        const std::string& name = x.string();
1,896✔
363

364
        if (const auto it_builtin = getBuiltin(name))
3,792✔
365
            page(p).emplace_back(Instruction::BUILTIN, it_builtin.value());
436✔
366
        else if (getOperator(name).has_value())
1,460✔
367
            throwCompilerError(fmt::format("Found a free standing operator: `{}`", name), x);
1✔
368
        else
369
            page(p).emplace_back(LOAD_SYMBOL, addSymbol(x));  // using the variable
1,459✔
370

371
        if (is_result_unused)
1,895✔
372
        {
373
            compilerWarning("Statement has no effect", x);
×
UNCOV
374
            page(p).emplace_back(POP);
×
UNCOV
375
        }
×
376
    }
1,896✔
377

378
    void Compiler::compileListInstruction(const Node& c0, const Node& x, const Page p, const bool is_result_unused)
482✔
379
    {
482✔
380
        std::string name = c0.string();
482✔
381
        Instruction inst = getListInstruction(name).value();
482✔
382

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

391
        // compile arguments in reverse order
392
        for (std::size_t i = x.constList().size() - 1u; i > 0; --i)
960✔
393
        {
394
            const auto node = x.constList()[i];
478✔
395
            if (nodeProducesOutput(node))
478✔
396
                compileExpression(node, p, false, false);
477✔
397
            else
398
                throwCompilerError(fmt::format("Invalid node inside call to {}", name), node);
1✔
399
        }
478✔
400

401
        // put inst and number of arguments
402
        std::size_t inst_argc;
481✔
403
        switch (inst)
481✔
404
        {
435✔
405
            case LIST:
406
                inst_argc = argc;
435✔
407
                break;
471✔
408

409
            case APPEND:
410
            case APPEND_IN_PLACE:
411
            case CONCAT:
412
            case CONCAT_IN_PLACE:
413
                inst_argc = argc - 1;
36✔
414
                break;
46✔
415

416
            default:
417
                inst_argc = 0;
10✔
418
                break;
10✔
419
        }
481✔
420
        page(p).emplace_back(inst, static_cast<uint16_t>(inst_argc));
481✔
421

422
        if (is_result_unused && name.back() != '!')  // in-place functions never push a value
481✔
423
        {
424
            compilerWarning("Ignoring return value of function", x);
×
UNCOV
425
            page(p).emplace_back(POP);
×
UNCOV
426
        }
×
427
    }
483✔
428

429
    void Compiler::compileIf(const Node& x, const Page p, const bool is_result_unused, const bool is_terminal, const std::string& var_name)
289✔
430
    {
289✔
431
        // compile condition
432
        compileExpression(x.constList()[1], p, false, false);
289✔
433

434
        // jump only if needed to the if
435
        const std::size_t jump_to_if_pos = page(p).size();
289✔
436
        page(p).emplace_back(Instruction::POP_JUMP_IF_TRUE);
289✔
437

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

442
        // when else is finished, jump to end
443
        const std::size_t jump_to_end_pos = page(p).size();
289✔
444
        page(p).emplace_back(Instruction::JUMP);
289✔
445

446
        // absolute address to jump to if condition is true
447
        page(p)[jump_to_if_pos].data = static_cast<uint16_t>(page(p).size());
289✔
448
        // if code
449
        compileExpression(x.constList()[2], p, is_result_unused, is_terminal, var_name);
289✔
450
        // set jump to end pos
451
        page(p)[jump_to_end_pos].data = static_cast<uint16_t>(page(p).size());
289✔
452
    }
289✔
453

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

461
        // capture, if needed
462
        bool is_closure = false;
107✔
463
        for (const auto& node : x.constList()[1].constList())
290✔
464
        {
465
            if (node.nodeType() == NodeType::Capture)
183✔
466
            {
467
                page(p).emplace_back(CAPTURE, addSymbol(node));
36✔
468
                is_closure = true;
36✔
469
            }
36✔
470
        }
183✔
471

472
        // create new page for function body
473
        m_code_pages.emplace_back();
107✔
474
        const auto function_body_page = Page { .index = m_code_pages.size() - 1, .is_temp = false };
107✔
475
        // save page_id into the constants table as PageAddr and load the const
476
        page(p).emplace_back(is_closure ? MAKE_CLOSURE : LOAD_CONST, addValue(function_body_page.index, x));
107✔
477

478
        // pushing arguments from the stack into variables in the new scope
479
        for (const auto& node : x.constList()[1].constList())
290✔
480
        {
481
            if (node.nodeType() == NodeType::Symbol)
183✔
482
                page(function_body_page).emplace_back(STORE, addSymbol(node));
147✔
483
        }
183✔
484

485
        // push body of the function
486
        compileExpression(x.constList()[2], function_body_page, false, true, var_name);
107✔
487

488
        // return last value on the stack
489
        page(function_body_page).emplace_back(RET);
107✔
490

491
        // if the computed function is unused, pop it
492
        if (is_result_unused)
107✔
493
        {
UNCOV
494
            compilerWarning("Unused declared function", x);
×
UNCOV
495
            page(p).emplace_back(POP);
×
496
        }
×
497
    }
109✔
498

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

506
        const std::string name = x.constList()[1].string();
374✔
507
        uint16_t i = addSymbol(x.constList()[1]);
374✔
508

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

514
        if (n == Keyword::Let || n == Keyword::Mut)
371✔
515
            page(p).emplace_back(STORE, i);
290✔
516
        else
517
            page(p).emplace_back(SET_VAL, i);
81✔
518
    }
375✔
519

520
    void Compiler::compileWhile(const Node& x, const Page p)
32✔
521
    {
32✔
522
        if (x.constList().size() != 3)
32✔
523
            throwCompilerError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
1✔
524

525
        // save current position to jump there at the end of the loop
526
        std::size_t current = page(p).size();
31✔
527
        // push condition
528
        compileExpression(x.constList()[1], p, false, false);
31✔
529
        // absolute jump to end of block if condition is false
530
        const std::size_t jump_to_end_pos = page(p).size();
31✔
531
        page(p).emplace_back(POP_JUMP_IF_FALSE);
31✔
532
        // push code to page
533
        compileExpression(x.constList()[2], p, true, false);
31✔
534

535
        // loop, jump to the condition
536
        page(p).emplace_back(JUMP, current);
31✔
537

538
        // absolute address to jump to if condition is false
539
        page(p)[jump_to_end_pos].data = static_cast<uint16_t>(page(p).size());
31✔
540
    }
32✔
541

542
    void Compiler::compilePluginImport(const Node& x, const Page p)
×
UNCOV
543
    {
×
544
        std::string path;
×
545
        const Node package_node = x.constList()[1];
×
546
        for (std::size_t i = 0, end = package_node.constList().size(); i < end; ++i)
×
547
        {
548
            path += package_node.constList()[i].string();
×
UNCOV
549
            if (i + 1 != end)
×
UNCOV
550
                path += "/";
×
551
        }
×
UNCOV
552
        path += ".arkm";
×
553

554
        // register plugin path in the constants table
555
        uint16_t id = addValue(Node(NodeType::String, path));
×
556
        // save plugin name to use it later
UNCOV
557
        m_plugins.push_back(path);
×
558
        // add plugin instruction + id of the constant referring to the plugin path
UNCOV
559
        page(p).emplace_back(PLUGIN, id);
×
UNCOV
560
    }
×
561

562
    void Compiler::handleCalls(const Node& x, const Page p, bool is_result_unused, const bool is_terminal, const std::string& var_name)
1,667✔
563
    {
1,667✔
564
        constexpr std::size_t start_index = 1;
1,667✔
565

566
        const auto node = x.constList()[0];
1,667✔
567
        const auto maybe_operator = node.nodeType() == NodeType::Symbol ? getOperator(node.string()) : std::nullopt;
1,667✔
568

569
        enum class ShortcircuitOp
570
        {
571
            And,
572
            Or
573
        };
574
        const std::optional<ShortcircuitOp> maybe_shortcircuit =
1,667✔
575
            node.nodeType() == NodeType::Symbol
3,214✔
576
            ? (node.string() == Language::And
3,078✔
577
                   ? std::make_optional(ShortcircuitOp::And)
16✔
578
                   : (node.string() == Language::Or
1,531✔
579
                          ? std::make_optional(ShortcircuitOp::Or)
6✔
580
                          : std::nullopt))
1,525✔
581
            : std::nullopt;
120✔
582

583
        if (maybe_shortcircuit.has_value())
1,667✔
584
        {
585
            // short circuit implementation
586

587
            compileExpression(x.constList()[1], p, false, false);
22✔
588
            page(p).emplace_back(DUP);
22✔
589

590
            std::vector<std::size_t> to_update;
22✔
591
            for (std::size_t i = 2, end = x.constList().size(); i < end; ++i)
52✔
592
            {
593
                to_update.push_back(page(p).size());
30✔
594

595
                switch (maybe_shortcircuit.value())
30✔
596
                {
20✔
597
                    case ShortcircuitOp::And:
598
                        page(p).emplace_back(POP_JUMP_IF_FALSE);
20✔
599
                        break;
30✔
600
                    case ShortcircuitOp::Or:
601
                        page(p).emplace_back(POP_JUMP_IF_TRUE);
10✔
602
                        break;
10✔
603
                }
30✔
604
                page(p).emplace_back(POP);
30✔
605

606
                compileExpression(x.constList()[i], p, false, false);
30✔
607
                if (i + 1 != end)
30✔
608
                    page(p).emplace_back(DUP);
8✔
609
            }
30✔
610

611
            for (const auto pos : to_update)
52✔
612
                page(p)[pos].data = static_cast<uint16_t>(page(p).size());
30✔
613
        }
22✔
614
        else if (!maybe_operator.has_value())
1,645✔
615
        {
616
            if (is_terminal && x.constList()[0].nodeType() == NodeType::Symbol && var_name == x.constList()[0].string())
895✔
617
            {
618
                // push the arguments in reverse order
619
                for (std::size_t i = x.constList().size() - 1; i >= start_index; --i)
9✔
620
                {
621
                    if (nodeProducesOutput(x.constList()[i]))
6✔
622
                        compileExpression(x.constList()[i], p, false, false);
5✔
623
                    else
624
                        throwCompilerError(fmt::format("Invalid node inside tail call to `{}'", node.repr()), x);
1✔
625
                }
5✔
626

627
                // jump to the top of the function
628
                page(p).emplace_back(JUMP, 0_u16);
2✔
629
                return;  // skip the potential Instruction::POP at the end
2✔
630
            }
631
            else
632
            {
633
                m_temp_pages.emplace_back();
892✔
634
                const auto proc_page = Page { .index = m_temp_pages.size() - 1u, .is_temp = true };
892✔
635
                // closure chains have been handled (eg: closure.field.field.function)
636
                compileExpression(node, proc_page, false, false);  // storing proc
892✔
637
                if (m_temp_pages.back().empty())
889✔
638
                    throwCompilerError(fmt::format("Can not call {}", x.constList()[0].repr()), x);
1✔
639

640
                // push arguments on current page
641
                for (auto exp = x.constList().begin() + start_index, exp_end = x.constList().end(); exp != exp_end; ++exp)
2,699✔
642
                {
643
                    if (nodeProducesOutput(*exp))
1,811✔
644
                        compileExpression(*exp, p, false, false);
1,810✔
645
                    else
646
                        throwCompilerError(fmt::format("Invalid node inside call to `{}'", node.repr()), x);
1✔
647
                }
1,807✔
648
                // push proc from temp page
649
                for (const Word& word : m_temp_pages.back())
1,889✔
650
                    page(p).push_back(word);
1,005✔
651
                m_temp_pages.pop_back();
884✔
652

653
                // number of arguments
654
                std::size_t args_count = 0;
884✔
655
                for (auto it = x.constList().begin() + 1, it_end = x.constList().end(); it != it_end; ++it)
2,689✔
656
                {
657
                    if (it->nodeType() != NodeType::Capture)
1,805✔
658
                        args_count++;
1,805✔
659
                }
1,805✔
660
                // call the procedure
661
                page(p).emplace_back(CALL, args_count);
884✔
662
            }
892✔
663
        }
884✔
664
        else  // operator
665
        {
666
            // retrieve operator
667
            auto op = Word(maybe_operator.value());
750✔
668

669
            if (op.opcode == ASSERT)
750✔
670
                is_result_unused = false;
6✔
671

672
            // push arguments on current page
673
            std::size_t exp_count = 0;
750✔
674
            for (std::size_t index = start_index, size = x.constList().size(); index < size; ++index)
2,109✔
675
            {
676
                if (nodeProducesOutput(x.constList()[index]))
1,359✔
677
                    compileExpression(x.constList()[index], p, false, false);
1,358✔
678
                else
679
                    throwCompilerError(fmt::format("Invalid node inside call to operator `{}'", node.repr()), x);
1✔
680

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

684
                // in order to be able to handle things like (op A B C D...)
685
                // which should be transformed into A B op C op D op...
686
                if (exp_count >= 2)
1,358✔
687
                    page(p).emplace_back(op.opcode, 2);  // TODO generalize to n arguments (n >= 2)
611✔
688
            }
1,358✔
689

690
            if (isUnaryInst(static_cast<Instruction>(op.opcode)))
749✔
691
            {
692
                if (exp_count != 1)
162✔
693
                    throwCompilerError(fmt::format("Operator needs one argument, but was called with {}", exp_count), x.constList()[0]);
1✔
694
                page(p).emplace_back(op.opcode);
161✔
695
            }
161✔
696
            else if (exp_count <= 1)
587✔
697
            {
698
                throwCompilerError(fmt::format("Operator needs two arguments, but was called with {}", exp_count), x.constList()[0]);
2✔
699
            }
700

701
            // need to check we didn't push the (op A B C D...) things for operators not supporting it
702
            if (exp_count > 2)
746✔
703
            {
704
                switch (op.opcode)
21✔
705
                {
21✔
706
                    // authorized instructions
707
                    case ADD: [[fallthrough]];
708
                    case SUB: [[fallthrough]];
709
                    case MUL: [[fallthrough]];
710
                    case DIV: [[fallthrough]];
711
                    case MOD:
712
                        break;
21✔
713

714
                    default:
715
                        throwCompilerError(
×
716
                            fmt::format(
×
UNCOV
717
                                "can not create a chained expression (of length {}) for operator `{}'. You most likely forgot a `)'.",
×
718
                                exp_count,
UNCOV
719
                                Language::operators[static_cast<std::size_t>(op.opcode - FIRST_OPERATOR)]),
×
UNCOV
720
                            x);
×
721
                }
21✔
722
            }
21✔
723
        }
750✔
724

725
        if (is_result_unused)
1,652✔
726
            page(p).emplace_back(POP);
625✔
727
    }
1,680✔
728

729
    uint16_t Compiler::addSymbol(const Node& sym)
2,234✔
730
    {
2,234✔
731
        // otherwise, add the symbol, and return its id in the table
732
        auto it = std::ranges::find_if(m_symbols, [&sym](const Node& sym_node) -> bool {
158,418✔
733
            return sym_node.string() == sym.string();
156,184✔
734
        });
735
        if (it == m_symbols.end())
2,234✔
736
        {
737
            m_symbols.push_back(sym);
275✔
738
            it = m_symbols.begin() + static_cast<std::vector<std::string>::difference_type>(m_symbols.size() - 1);
275✔
739
        }
275✔
740

741
        const auto distance = std::distance(m_symbols.begin(), it);
2,234✔
742
        if (distance < std::numeric_limits<uint16_t>::max())
2,234✔
743
            return static_cast<uint16_t>(distance);
4,468✔
UNCOV
744
        throwCompilerError("Too many symbols (exceeds 65'536), aborting compilation.", sym);
×
745
    }
2,234✔
746

747
    uint16_t Compiler::addValue(const Node& x)
1,912✔
748
    {
1,912✔
749
        const ValTableElem v(x);
1,912✔
750
        auto it = std::ranges::find(m_values, v);
1,912✔
751
        if (it == m_values.end())
1,912✔
752
        {
753
            m_values.push_back(v);
543✔
754
            it = m_values.begin() + static_cast<std::vector<ValTableElem>::difference_type>(m_values.size() - 1);
543✔
755
        }
543✔
756

757
        const auto distance = std::distance(m_values.begin(), it);
1,912✔
758
        if (distance < std::numeric_limits<uint16_t>::max())
1,912✔
759
            return static_cast<uint16_t>(distance);
1,912✔
UNCOV
760
        throwCompilerError("Too many values (exceeds 65'536), aborting compilation.", x);
×
761
    }
1,912✔
762

763
    uint16_t Compiler::addValue(const std::size_t page_id, const Node& current)
107✔
764
    {
107✔
765
        const ValTableElem v(page_id);
107✔
766
        auto it = std::ranges::find(m_values, v);
107✔
767
        if (it == m_values.end())
107✔
768
        {
769
            m_values.push_back(v);
107✔
770
            it = m_values.begin() + static_cast<std::vector<ValTableElem>::difference_type>(m_values.size() - 1);
107✔
771
        }
107✔
772

773
        const auto distance = std::distance(m_values.begin(), it);
107✔
774
        if (distance < std::numeric_limits<uint16_t>::max())
107✔
775
            return static_cast<uint16_t>(distance);
107✔
UNCOV
776
        throwCompilerError("Too many values (exceeds 65'536), aborting compilation.", current);
×
777
    }
107✔
778
}
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