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

ArkScript-lang / Ark / 14012558627

22 Mar 2025 09:33PM UTC coverage: 79.378% (+0.5%) from 78.852%
14012558627

push

github

SuperFola
chore: cleaning up dead code and old todos

6 of 14 new or added lines in 5 files covered. (42.86%)

248 existing lines in 6 files now uncovered.

6047 of 7618 relevant lines covered (79.38%)

77956.04 hits per line

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

64.08
/src/arkreactor/VM/VM.cpp
1
#include <Ark/VM/VM.hpp>
2

3
#include <utility>
4
#include <numeric>
5
#include <limits>
6
#include <ranges>
7
#include <fmt/core.h>
8
#include <fmt/color.h>
9

10
#include <Ark/Files.hpp>
11
#include <Ark/Utils.hpp>
12
#include <Ark/TypeChecker.hpp>
13
#include <Ark/Compiler/Instructions.hpp>
14

15
struct mapping
16
{
17
    char* name;
18
    Ark::Value (*value)(std::vector<Ark::Value>&, Ark::VM*);
19
};
20

21
namespace Ark
22
{
23
    using namespace internal;
24

25
    namespace helper
26
    {
27
        inline Value tail(Value* a)
336✔
28
        {
336✔
29
            if (a->valueType() == ValueType::List)
336✔
30
            {
31
                if (a->constList().size() < 2)
69✔
32
                    return Value(ValueType::List);
18✔
33

34
                std::vector<Value> tmp(a->constList().size() - 1);
51✔
35
                for (std::size_t i = 1, end = a->constList().size(); i < end; ++i)
327✔
36
                    tmp[i - 1] = a->constList()[i];
276✔
37
                return Value(std::move(tmp));
51✔
38
            }
51✔
39
            if (a->valueType() == ValueType::String)
267✔
40
            {
41
                if (a->string().size() < 2)
267✔
42
                    return Value(ValueType::String);
50✔
43

44
                Value b { *a };
217✔
45
                b.stringRef().erase(b.stringRef().begin());
217✔
46
                return b;
217✔
47
            }
217✔
48

49
            types::generateError(
×
50
                "tail",
×
51
                { { types::Contract { { types::Typedef("value", ValueType::List) } },
×
52
                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
×
53
                { *a });
×
54
        }
336✔
55

56
        inline Value head(Value* a)
1,128✔
57
        {
1,128✔
58
            if (a->valueType() == ValueType::List)
1,128✔
59
            {
60
                if (a->constList().empty())
861✔
61
                    return Builtins::nil;
×
62
                return a->constList()[0];
861✔
63
            }
64
            if (a->valueType() == ValueType::String)
267✔
65
            {
66
                if (a->string().empty())
267✔
67
                    return Value(ValueType::String);
1✔
68
                return Value(std::string(1, a->stringRef()[0]));
266✔
69
            }
70

71
            types::generateError(
×
72
                "head",
×
73
                { { types::Contract { { types::Typedef("value", ValueType::List) } },
×
74
                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
×
75
                { *a });
×
76
        }
1,128✔
77
    }
78

79
    VM::VM(State& state) noexcept :
246✔
80
        m_state(state), m_exit_code(0), m_running(false)
82✔
81
    {
82✔
82
        m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>());
82✔
83
    }
82✔
84

85
    void VM::init() noexcept
83✔
86
    {
83✔
87
        ExecutionContext& context = *m_execution_contexts.back();
83✔
88
        for (const auto& c : m_execution_contexts)
166✔
89
        {
90
            c->ip = 0;
83✔
91
            c->pp = 0;
83✔
92
            c->sp = 0;
83✔
93
        }
83✔
94

95
        context.sp = 0;
83✔
96
        context.fc = 1;
83✔
97

98
        m_shared_lib_objects.clear();
83✔
99
        context.stacked_closure_scopes.clear();
83✔
100
        context.stacked_closure_scopes.emplace_back(nullptr);
83✔
101

102
        context.saved_scope.reset();
83✔
103
        m_exit_code = 0;
83✔
104

105
        context.locals.clear();
83✔
106
        context.locals.emplace_back(context.scopes_storage.data(), 0);
83✔
107

108
        // loading bound stuff
109
        // put them in the global frame if we can, aka the first one
110
        for (const auto& [sym_id, value] : m_state.m_binded)
110✔
111
        {
112
            auto it = std::ranges::find(m_state.m_symbols, sym_id);
22✔
113
            if (it != m_state.m_symbols.end())
22✔
114
                context.locals[0].push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), value);
5✔
115
        }
22✔
116
    }
83✔
117

118
    Value& VM::operator[](const std::string& name) noexcept
28✔
119
    {
28✔
120
        // find id of object
121
        const auto it = std::ranges::find(m_state.m_symbols, name);
28✔
122
        if (it == m_state.m_symbols.end())
28✔
123
        {
124
            m_no_value = Builtins::nil;
1✔
125
            return m_no_value;
1✔
126
        }
127

128
        const auto dist = std::distance(m_state.m_symbols.begin(), it);
27✔
129
        if (std::cmp_less(dist, std::numeric_limits<uint16_t>::max()))
27✔
130
        {
131
            ExecutionContext& context = *m_execution_contexts.front();
27✔
132

133
            const auto id = static_cast<uint16_t>(dist);
27✔
134
            Value* var = findNearestVariable(id, context);
27✔
135
            if (var != nullptr)
27✔
136
                return *var;
27✔
137
        }
27✔
138

139
        m_no_value = Builtins::nil;
×
140
        return m_no_value;
×
141
    }
28✔
142

143
    void VM::loadPlugin(const uint16_t id, ExecutionContext& context)
×
144
    {
×
145
        namespace fs = std::filesystem;
146

147
        const std::string file = m_state.m_constants[id].stringRef();
×
148

149
        std::string path = file;
×
150
        // bytecode loaded from file
151
        if (m_state.m_filename != ARK_NO_NAME_FILE)
×
152
            path = (fs::path(m_state.m_filename).parent_path() / fs::path(file)).relative_path().string();
×
153

154
        std::shared_ptr<SharedLibrary> lib;
×
155
        // if it exists alongside the .arkc file
156
        if (Utils::fileExists(path))
×
157
            lib = std::make_shared<SharedLibrary>(path);
×
158
        else
159
        {
160
            for (auto const& v : m_state.m_libenv)
×
161
            {
162
                std::string lib_path = (fs::path(v) / fs::path(file)).string();
×
163

164
                // if it's already loaded don't do anything
165
                if (std::ranges::find_if(m_shared_lib_objects, [&](const auto& val) {
×
166
                        return (val->path() == path || val->path() == lib_path);
×
167
                    }) != m_shared_lib_objects.end())
82✔
168
                    return;
×
169

170
                // check in lib_path
171
                if (Utils::fileExists(lib_path))
×
172
                {
173
                    lib = std::make_shared<SharedLibrary>(lib_path);
×
174
                    break;
×
175
                }
176
            }
×
177
        }
178

179
        if (!lib)
×
180
        {
181
            auto lib_path = std::accumulate(
×
182
                std::next(m_state.m_libenv.begin()),
×
183
                m_state.m_libenv.end(),
×
184
                m_state.m_libenv[0].string(),
×
185
                [](const std::string& a, const fs::path& b) -> std::string {
×
186
                    return a + "\n\t- " + b.string();
×
187
                });
×
188
            throwVMError(
×
189
                ErrorKind::Module,
190
                fmt::format("Could not find module '{}'. Searched under\n\t- {}\n\t- {}", file, path, lib_path));
×
191
        }
×
192

193
        m_shared_lib_objects.emplace_back(lib);
×
194

195
        // load the mapping from the dynamic library
196
        try
197
        {
198
            const mapping* map = m_shared_lib_objects.back()->get<mapping* (*)()>("getFunctionsMapping")();
×
199
            // load the mapping data
200
            std::size_t i = 0;
×
201
            while (map[i].name != nullptr)
×
202
            {
203
                // put it in the global frame, aka the first one
204
                auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
×
205
                if (it != m_state.m_symbols.end())
×
206
                    context.locals[0].push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), Value(map[i].value));
×
207

208
                ++i;
×
209
            }
×
210
        }
×
211
        catch (const std::system_error& e)
212
        {
213
            throwVMError(
×
214
                ErrorKind::Module,
215
                fmt::format(
×
216
                    "An error occurred while loading module '{}': {}\nIt is most likely because the versions of the module and the language don't match.",
×
217
                    file, e.what()));
×
218
        }
×
219
    }
×
220

221
    void VM::exit(const int code) noexcept
×
222
    {
×
223
        m_exit_code = code;
×
224
        m_running = false;
×
225
    }
×
226

227
    ExecutionContext* VM::createAndGetContext()
6✔
228
    {
6✔
229
        const std::lock_guard lock(m_mutex);
6✔
230

231
        m_execution_contexts.push_back(std::make_unique<ExecutionContext>());
6✔
232
        ExecutionContext* ctx = m_execution_contexts.back().get();
6✔
233
        ctx->stacked_closure_scopes.emplace_back(nullptr);
6✔
234

235
        ctx->locals.reserve(m_execution_contexts.front()->locals.size());
6✔
236
        ctx->scopes_storage = m_execution_contexts.front()->scopes_storage;
6✔
237
        for (const auto& local : m_execution_contexts.front()->locals)
20✔
238
        {
239
            auto& scope = ctx->locals.emplace_back(ctx->scopes_storage.data(), local.m_start);
14✔
240
            scope.m_size = local.m_size;
14✔
241
            scope.m_min_id = local.m_min_id;
14✔
242
            scope.m_max_id = local.m_max_id;
14✔
243
        }
14✔
244

245
        return ctx;
6✔
246
    }
6✔
247

248
    void VM::deleteContext(ExecutionContext* ec)
5✔
249
    {
5✔
250
        const std::lock_guard lock(m_mutex);
5✔
251

252
        const auto it =
5✔
253
            std::ranges::remove_if(
10✔
254
                m_execution_contexts,
5✔
255
                [ec](const std::unique_ptr<ExecutionContext>& ctx) {
21✔
256
                    return ctx.get() == ec;
16✔
257
                })
258
                .begin();
5✔
259
        m_execution_contexts.erase(it);
5✔
260
    }
5✔
261

262
    Future* VM::createFuture(std::vector<Value>& args)
6✔
263
    {
6✔
264
        ExecutionContext* ctx = createAndGetContext();
6✔
265

266
        // doing this after having created the context
267
        // because the context uses the mutex and we don't want a deadlock
268
        const std::lock_guard lock(m_mutex);
6✔
269
        m_futures.push_back(std::make_unique<Future>(ctx, this, args));
6✔
270

271
        return m_futures.back().get();
6✔
272
    }
6✔
273

274
    void VM::deleteFuture(Future* f)
×
275
    {
×
276
        const std::lock_guard lock(m_mutex);
×
277

278
        const auto it =
×
279
            std::ranges::remove_if(
×
280
                m_futures,
×
281
                [f](const std::unique_ptr<Future>& future) {
×
282
                    return future.get() == f;
×
283
                })
284
                .begin();
×
285
        m_futures.erase(it);
×
286
    }
×
287

288
    bool VM::forceReloadPlugins() const
×
289
    {
×
290
        // load the mapping from the dynamic library
291
        try
292
        {
293
            for (const auto& shared_lib : m_shared_lib_objects)
×
294
            {
295
                const mapping* map = shared_lib->template get<mapping* (*)()>("getFunctionsMapping")();
×
296
                // load the mapping data
297
                std::size_t i = 0;
×
298
                while (map[i].name != nullptr)
×
299
                {
300
                    // put it in the global frame, aka the first one
301
                    auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
×
302
                    if (it != m_state.m_symbols.end())
×
303
                        m_execution_contexts[0]->locals[0].push_back(
×
304
                            static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)),
×
305
                            Value(map[i].value));
×
306

307
                    ++i;
×
308
                }
×
309
            }
×
310

311
            return true;
×
312
        }
×
313
        catch (const std::system_error&)
314
        {
315
            return false;
×
316
        }
×
317
    }
×
318

319
    int VM::run(const bool fail_with_exception)
83✔
320
    {
83✔
321
        init();
83✔
322
        safeRun(*m_execution_contexts[0], 0, fail_with_exception);
83✔
323
        return m_exit_code;
83✔
324
    }
325

326
    int VM::safeRun(ExecutionContext& context, std::size_t untilFrameCount, bool fail_with_exception)
91✔
327
    {
91✔
328
#if ARK_USE_COMPUTED_GOTOS
329
#    define TARGET(op) TARGET_##op:
330
#    define DISPATCH_GOTO()            \
331
        _Pragma("GCC diagnostic push") \
332
            _Pragma("GCC diagnostic ignored \"-Wpedantic\"") goto* opcode_targets[inst];
333
        _Pragma("GCC diagnostic pop")
1✔
334
#    define GOTO_HALT() goto dispatch_end
335
#else
1✔
336
#    define TARGET(op) case op:
337
#    define DISPATCH_GOTO() goto dispatch_opcode
1✔
338
#    define GOTO_HALT() break
339
#endif
340

341
#define NEXTOPARG()                                                                      \
342
    do                                                                                   \
343
    {                                                                                    \
344
        inst = m_state.m_pages[context.pp][context.ip];                                  \
345
        padding = m_state.m_pages[context.pp][context.ip + 1];                           \
346
        arg = static_cast<uint16_t>((m_state.m_pages[context.pp][context.ip + 2] << 8) + \
347
                                    m_state.m_pages[context.pp][context.ip + 3]);        \
348
        context.ip += 4;                                                                 \
349
    } while (false)
350
#define DISPATCH() \
351
    NEXTOPARG();   \
352
    DISPATCH_GOTO();
353
#define UNPACK_ARGS()                                                                 \
354
    do                                                                                \
355
    {                                                                                 \
356
        secondary_arg = static_cast<uint16_t>((padding << 4) | (arg & 0xf000) >> 12); \
357
        primary_arg = arg & 0x0fff;                                                   \
358
    } while (false)
359

360
#if ARK_USE_COMPUTED_GOTOS
361
#    pragma GCC diagnostic push
362
#    pragma GCC diagnostic ignored "-Wpedantic"
363
            constexpr std::array opcode_targets = {
91✔
364
                &&TARGET_NOP,
365
                &&TARGET_LOAD_SYMBOL,
366
                &&TARGET_LOAD_SYMBOL_BY_INDEX,
367
                &&TARGET_LOAD_CONST,
368
                &&TARGET_POP_JUMP_IF_TRUE,
369
                &&TARGET_STORE,
370
                &&TARGET_SET_VAL,
371
                &&TARGET_POP_JUMP_IF_FALSE,
372
                &&TARGET_JUMP,
373
                &&TARGET_RET,
374
                &&TARGET_HALT,
375
                &&TARGET_CALL,
376
                &&TARGET_CAPTURE,
377
                &&TARGET_BUILTIN,
378
                &&TARGET_DEL,
379
                &&TARGET_MAKE_CLOSURE,
380
                &&TARGET_GET_FIELD,
381
                &&TARGET_PLUGIN,
382
                &&TARGET_LIST,
383
                &&TARGET_APPEND,
384
                &&TARGET_CONCAT,
385
                &&TARGET_APPEND_IN_PLACE,
386
                &&TARGET_CONCAT_IN_PLACE,
387
                &&TARGET_POP_LIST,
388
                &&TARGET_POP_LIST_IN_PLACE,
389
                &&TARGET_SET_AT_INDEX,
390
                &&TARGET_SET_AT_2_INDEX,
391
                &&TARGET_POP,
392
                &&TARGET_DUP,
393
                &&TARGET_CREATE_SCOPE,
394
                &&TARGET_POP_SCOPE,
395
                &&TARGET_ADD,
396
                &&TARGET_SUB,
397
                &&TARGET_MUL,
398
                &&TARGET_DIV,
399
                &&TARGET_GT,
400
                &&TARGET_LT,
401
                &&TARGET_LE,
402
                &&TARGET_GE,
403
                &&TARGET_NEQ,
404
                &&TARGET_EQ,
405
                &&TARGET_LEN,
406
                &&TARGET_EMPTY,
407
                &&TARGET_TAIL,
408
                &&TARGET_HEAD,
409
                &&TARGET_ISNIL,
410
                &&TARGET_ASSERT,
411
                &&TARGET_TO_NUM,
412
                &&TARGET_TO_STR,
413
                &&TARGET_AT,
414
                &&TARGET_AT_AT,
415
                &&TARGET_MOD,
416
                &&TARGET_TYPE,
417
                &&TARGET_HASFIELD,
418
                &&TARGET_NOT,
419
                &&TARGET_LOAD_CONST_LOAD_CONST,
420
                &&TARGET_LOAD_CONST_STORE,
421
                &&TARGET_LOAD_CONST_SET_VAL,
422
                &&TARGET_STORE_FROM,
423
                &&TARGET_STORE_FROM_INDEX,
424
                &&TARGET_SET_VAL_FROM,
425
                &&TARGET_SET_VAL_FROM_INDEX,
426
                &&TARGET_INCREMENT,
427
                &&TARGET_INCREMENT_BY_INDEX,
428
                &&TARGET_DECREMENT,
429
                &&TARGET_DECREMENT_BY_INDEX,
430
                &&TARGET_STORE_TAIL,
431
                &&TARGET_STORE_TAIL_BY_INDEX,
432
                &&TARGET_STORE_HEAD,
433
                &&TARGET_STORE_HEAD_BY_INDEX,
434
                &&TARGET_SET_VAL_TAIL,
435
                &&TARGET_SET_VAL_TAIL_BY_INDEX,
436
                &&TARGET_SET_VAL_HEAD,
437
                &&TARGET_SET_VAL_HEAD_BY_INDEX,
438
                &&TARGET_CALL_BUILTIN
439
            };
440

441
        static_assert(opcode_targets.size() == static_cast<std::size_t>(Instruction::InstructionsCount) && "Some instructions are not implemented in the VM");
442
#    pragma GCC diagnostic pop
443
#endif
444

445
        try
446
        {
447
            uint8_t inst = 0;
91✔
448
            uint8_t padding = 0;
91✔
449
            uint16_t arg = 0;
91✔
450
            uint16_t primary_arg = 0;
91✔
451
            uint16_t secondary_arg = 0;
91✔
452

453
            m_running = true;
91✔
454

455
            DISPATCH();
91✔
456
            {
457
#if !ARK_USE_COMPUTED_GOTOS
458
            dispatch_opcode:
459
                switch (inst)
460
#endif
UNCOV
461
                {
×
462
#pragma region "Instructions"
463
                    TARGET(NOP)
464
                    {
UNCOV
465
                        DISPATCH();
×
466
                    }
221,499✔
467

468
                    TARGET(LOAD_SYMBOL)
469
                    {
470
                        push(loadSymbol(arg, context), context);
221,499✔
471
                        DISPATCH();
221,497✔
472
                    }
413,863✔
473

474
                    TARGET(LOAD_SYMBOL_BY_INDEX)
475
                    {
476
                        push(loadSymbolFromIndex(arg, context), context);
413,863✔
477
                        DISPATCH();
413,863✔
478
                    }
293,020✔
479

480
                    TARGET(LOAD_CONST)
481
                    {
482
                        push(loadConstAsPtr(arg), context);
293,020✔
483
                        DISPATCH();
293,020✔
484
                    }
293,245✔
485

486
                    TARGET(POP_JUMP_IF_TRUE)
487
                    {
488
                        if (Value boolean = *popAndResolveAsPtr(context); !!boolean)
396,882✔
489
                            context.ip = arg * 4;  // instructions are 4 bytes
103,637✔
490
                        DISPATCH();
293,245✔
491
                    }
396,750✔
492

493
                    TARGET(STORE)
494
                    {
495
                        store(arg, popAndResolveAsPtr(context), context);
396,750✔
496
                        DISPATCH();
396,750✔
497
                    }
35,263✔
498

499
                    TARGET(SET_VAL)
500
                    {
501
                        setVal(arg, popAndResolveAsPtr(context), context);
35,263✔
502
                        DISPATCH();
35,263✔
503
                    }
24,684✔
504

505
                    TARGET(POP_JUMP_IF_FALSE)
506
                    {
507
                        if (Value boolean = *popAndResolveAsPtr(context); !boolean)
26,903✔
508
                            context.ip = arg * 4;  // instructions are 4 bytes
2,219✔
509
                        DISPATCH();
24,684✔
510
                    }
205,421✔
511

512
                    TARGET(JUMP)
513
                    {
514
                        context.ip = arg * 4;  // instructions are 4 bytes
205,421✔
515
                        DISPATCH();
205,421✔
516
                    }
120,665✔
517

518
                    TARGET(RET)
519
                    {
520
                        {
521
                            Value ip_or_val = *popAndResolveAsPtr(context);
120,665✔
522
                            // no return value on the stack
523
                            if (ip_or_val.valueType() == ValueType::InstPtr) [[unlikely]]
120,665✔
524
                            {
525
                                context.ip = ip_or_val.pageAddr();
1,339✔
526
                                // we always push PP then IP, thus the next value
527
                                // MUST be the page pointer
528
                                context.pp = pop(context)->pageAddr();
1,339✔
529

530
                                returnFromFuncCall(context);
1,339✔
531
                                push(Builtins::nil, context);
1,339✔
532
                            }
1,339✔
533
                            // value on the stack
534
                            else [[likely]]
535
                            {
536
                                const Value* ip = popAndResolveAsPtr(context);
119,326✔
537
                                assert(ip->valueType() == ValueType::InstPtr && "Expected instruction pointer on the stack (is the stack trashed?)");
119,326✔
538
                                context.ip = ip->pageAddr();
119,326✔
539
                                context.pp = pop(context)->pageAddr();
119,326✔
540

541
                                returnFromFuncCall(context);
119,326✔
542
                                push(std::move(ip_or_val), context);
119,326✔
543
                            }
544

545
                            if (context.fc <= untilFrameCount)
120,665✔
546
                                GOTO_HALT();
6✔
547
                        }
120,665✔
548

549
                        DISPATCH();
120,659✔
550
                    }
53✔
551

552
                    TARGET(HALT)
553
                    {
554
                        m_running = false;
53✔
555
                        GOTO_HALT();
53✔
556
                    }
124,762✔
557

558
                    TARGET(CALL)
559
                    {
560
                        // stack pointer + 2 because we push IP and PP
561
                        if (context.sp + 2u >= VMStackSize) [[unlikely]]
124,762✔
562
                            throwVMError(
1✔
563
                                ErrorKind::VM,
564
                                fmt::format(
2✔
565
                                    "Maximum recursion depth exceeded. You could consider rewriting your function `{}' to make use of tail-call optimization.",
1✔
566
                                    m_state.m_symbols[context.last_symbol]));
1✔
567
                        call(context, arg);
124,761✔
568
                        if (!m_running)
124,757✔
UNCOV
569
                            GOTO_HALT();
×
570
                        DISPATCH();
124,757✔
571
                    }
457✔
572

573
                    TARGET(CAPTURE)
574
                    {
575
                        if (!context.saved_scope)
457✔
576
                            context.saved_scope = ClosureScope();
102✔
577

578
                        const Value* ptr = findNearestVariable(arg, context);
457✔
579
                        if (!ptr)
457✔
UNCOV
580
                            throwVMError(ErrorKind::Scope, fmt::format("Couldn't capture `{}' as it is currently unbound", m_state.m_symbols[arg]));
×
581
                        else
582
                        {
583
                            ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr;
457✔
584
                            context.saved_scope.value().push_back(arg, *ptr);
457✔
585
                        }
586

587
                        DISPATCH();
457✔
588
                    }
409✔
589

590
                    TARGET(BUILTIN)
591
                    {
592
                        push(Builtins::builtins[arg].second, context);
409✔
593
                        DISPATCH();
409✔
594
                    }
1✔
595

596
                    TARGET(DEL)
597
                    {
598
                        if (Value* var = findNearestVariable(arg, context); var != nullptr)
1✔
599
                        {
UNCOV
600
                            if (var->valueType() == ValueType::User)
×
UNCOV
601
                                var->usertypeRef().del();
×
UNCOV
602
                            *var = Value();
×
UNCOV
603
                            DISPATCH();
×
604
                        }
605

606
                        throwVMError(ErrorKind::Scope, fmt::format("Can not delete unbound variable `{}'", m_state.m_symbols[arg]));
1✔
607
                    }
102✔
608

609
                    TARGET(MAKE_CLOSURE)
102✔
610
                    {
611
                        push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context);
102✔
612
                        context.saved_scope.reset();
102✔
613
                        DISPATCH();
102✔
614
                    }
1,546✔
615

616
                    TARGET(GET_FIELD)
617
                    {
618
                        Value* var = popAndResolveAsPtr(context);
1,546✔
619
                        if (var->valueType() != ValueType::Closure)
1,546✔
620
                        {
621
                            if (context.last_symbol < m_state.m_symbols.size()) [[likely]]
1✔
622
                                throwVMError(
1✔
623
                                    ErrorKind::Type,
624
                                    fmt::format(
4✔
625
                                        "`{}' is a {}, not a Closure, can not get the field `{}' from it",
1✔
626
                                        m_state.m_symbols[context.last_symbol],
1✔
627
                                        types_to_str[static_cast<std::size_t>(var->valueType())],
1✔
628
                                        m_state.m_symbols[arg]));
1✔
629
                            else
UNCOV
630
                                throwVMError(ErrorKind::Type,
×
UNCOV
631
                                             fmt::format(
×
UNCOV
632
                                                 "{} is not a Closure, can not get the field `{}' from it",
×
UNCOV
633
                                                 types_to_str[static_cast<std::size_t>(var->valueType())],
×
UNCOV
634
                                                 m_state.m_symbols[arg]));
×
UNCOV
635
                        }
×
636

637
                        if (Value* field = var->refClosure().refScope()[arg]; field != nullptr)
1,545✔
638
                        {
639
                            // check for CALL instruction (the instruction because context.ip is already on the next instruction word)
640
                            if (m_state.m_pages[context.pp][context.ip] == CALL)
1,543✔
641
                                push(Value(Closure(var->refClosure().scopePtr(), field->pageAddr())), context);
697✔
642
                            else
643
                                push(field, context);
846✔
644
                        }
1,543✔
645
                        else
646
                        {
647
                            if (!var->refClosure().hasFieldEndingWith(m_state.m_symbols[arg], *this))
2✔
648
                                throwVMError(
1✔
649
                                    ErrorKind::Scope,
650
                                    fmt::format(
2✔
651
                                        "`{0}' isn't in the closure environment: {1}",
1✔
652
                                        m_state.m_symbols[arg],
1✔
653
                                        var->refClosure().toString(*this)));
1✔
654
                            throwVMError(
1✔
655
                                ErrorKind::Scope,
656
                                fmt::format(
2✔
657
                                    "`{0}' isn't in the closure environment: {1}. A variable in the package might have the same name as '{0}', "
1✔
658
                                    "and name resolution tried to fully qualify it. Rename either the variable or the capture to solve this",
659
                                    m_state.m_symbols[arg],
1✔
660
                                    var->refClosure().toString(*this)));
1✔
661
                        }
662
                        DISPATCH();
1,543✔
UNCOV
663
                    }
×
664

665
                    TARGET(PLUGIN)
666
                    {
UNCOV
667
                        loadPlugin(arg, context);
×
UNCOV
668
                        DISPATCH();
×
669
                    }
883✔
670

671
                    TARGET(LIST)
672
                    {
673
                        {
674
                            Value l(ValueType::List);
883✔
675
                            if (arg != 0)
883✔
676
                                l.list().reserve(arg);
478✔
677

678
                            for (uint16_t i = 0; i < arg; ++i)
2,142✔
679
                                l.push_back(*popAndResolveAsPtr(context));
1,259✔
680
                            push(std::move(l), context);
883✔
681
                        }
883✔
682
                        DISPATCH();
883✔
683
                    }
1,033✔
684

685
                    TARGET(APPEND)
686
                    {
687
                        {
688
                            Value* list = popAndResolveAsPtr(context);
1,033✔
689
                            if (list->valueType() != ValueType::List)
1,033✔
UNCOV
690
                                types::generateError(
×
UNCOV
691
                                    "append",
×
UNCOV
692
                                    { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
×
UNCOV
693
                                    { *list });
×
694

695
                            const auto size = static_cast<uint16_t>(list->constList().size());
1,033✔
696

697
                            Value obj { *list };
1,033✔
698
                            obj.list().reserve(size + arg);
1,033✔
699

700
                            for (uint16_t i = 0; i < arg; ++i)
2,066✔
701
                                obj.push_back(*popAndResolveAsPtr(context));
1,033✔
702
                            push(std::move(obj), context);
1,033✔
703
                        }
1,033✔
704
                        DISPATCH();
1,033✔
705
                    }
2✔
706

707
                    TARGET(CONCAT)
708
                    {
709
                        {
710
                            Value* list = popAndResolveAsPtr(context);
2✔
711
                            if (list->valueType() != ValueType::List)
2✔
712
                                types::generateError(
×
713
                                    "concat",
×
714
                                    { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
×
UNCOV
715
                                    { *list });
×
716

717
                            Value obj { *list };
2✔
718

719
                            for (uint16_t i = 0; i < arg; ++i)
4✔
720
                            {
721
                                Value* next = popAndResolveAsPtr(context);
2✔
722

723
                                if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
2✔
UNCOV
724
                                    types::generateError(
×
UNCOV
725
                                        "concat",
×
UNCOV
726
                                        { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
×
UNCOV
727
                                        { *list, *next });
×
728

729
                                std::ranges::copy(next->list(), std::back_inserter(obj.list()));
2✔
730
                            }
2✔
731
                            push(std::move(obj), context);
2✔
732
                        }
2✔
733
                        DISPATCH();
2✔
734
                    }
1,264✔
735

736
                    TARGET(APPEND_IN_PLACE)
737
                    {
738
                        Value* list = popAndResolveAsPtr(context);
1,264✔
739

740
                        if (list->valueType() != ValueType::List)
1,264✔
UNCOV
741
                            types::generateError(
×
UNCOV
742
                                "append!",
×
743
                                { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
×
744
                                { *list });
×
745

746
                        for (uint16_t i = 0; i < arg; ++i)
2,528✔
747
                            list->push_back(*popAndResolveAsPtr(context));
1,264✔
748
                        DISPATCH();
1,264✔
749
                    }
50✔
750

751
                    TARGET(CONCAT_IN_PLACE)
752
                    {
753
                        Value* list = popAndResolveAsPtr(context);
50✔
754

755
                        if (list->valueType() != ValueType::List)
50✔
756
                            types::generateError(
×
UNCOV
757
                                "concat",
×
UNCOV
758
                                { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
×
UNCOV
759
                                { *list });
×
760

761
                        for (uint16_t i = 0; i < arg; ++i)
130✔
762
                        {
763
                            Value* next = popAndResolveAsPtr(context);
80✔
764

765
                            if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
80✔
UNCOV
766
                                types::generateError(
×
UNCOV
767
                                    "concat!",
×
UNCOV
768
                                    { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
×
UNCOV
769
                                    { *list, *next });
×
770

771
                            std::ranges::copy(next->list(), std::back_inserter(list->list()));
80✔
772
                        }
80✔
773
                        DISPATCH();
50✔
774
                    }
4✔
775

776
                    TARGET(POP_LIST)
777
                    {
778
                        {
779
                            Value list = *popAndResolveAsPtr(context);
4✔
780
                            Value number = *popAndResolveAsPtr(context);
4✔
781

782
                            if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number)
4✔
UNCOV
783
                                types::generateError(
×
UNCOV
784
                                    "pop",
×
UNCOV
785
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
×
UNCOV
786
                                    { list, number });
×
787

788
                            long idx = static_cast<long>(number.number());
4✔
789
                            idx = idx < 0 ? static_cast<long>(list.list().size()) + idx : idx;
4✔
790
                            if (std::cmp_greater_equal(idx, list.list().size()))
4✔
791
                                throwVMError(
1✔
792
                                    ErrorKind::Index,
793
                                    fmt::format("pop index ({}) out of range (list size: {})", idx, list.list().size()));
1✔
794

795
                            list.list().erase(list.list().begin() + idx);
3✔
796
                            push(list, context);
3✔
797
                        }
4✔
798
                        DISPATCH();
3✔
799
                    }
52✔
800

801
                    TARGET(POP_LIST_IN_PLACE)
802
                    {
803
                        {
804
                            Value* list = popAndResolveAsPtr(context);
52✔
805
                            Value number = *popAndResolveAsPtr(context);
52✔
806

807
                            if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
52✔
UNCOV
808
                                types::generateError(
×
UNCOV
809
                                    "pop!",
×
UNCOV
810
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
×
UNCOV
811
                                    { *list, number });
×
812

813
                            long idx = static_cast<long>(number.number());
52✔
814
                            idx = idx < 0 ? static_cast<long>(list->list().size()) + idx : idx;
52✔
815
                            if (std::cmp_greater_equal(idx, list->list().size()))
52✔
816
                                throwVMError(
1✔
817
                                    ErrorKind::Index,
818
                                    fmt::format("pop! index ({}) out of range (list size: {})", idx, list->list().size()));
1✔
819

820
                            list->list().erase(list->list().begin() + idx);
51✔
821
                        }
52✔
822
                        DISPATCH();
51✔
823
                    }
488✔
824

825
                    TARGET(SET_AT_INDEX)
826
                    {
827
                        {
828
                            Value* list = popAndResolveAsPtr(context);
488✔
829
                            Value number = *popAndResolveAsPtr(context);
488✔
830
                            Value new_value = *popAndResolveAsPtr(context);
488✔
831

832
                            if (!list->isIndexable() || number.valueType() != ValueType::Number || (list->valueType() == ValueType::String && new_value.valueType() != ValueType::String))
488✔
UNCOV
833
                                types::generateError(
×
UNCOV
834
                                    "@=",
×
UNCOV
835
                                    { { types::Contract {
×
UNCOV
836
                                          { types::Typedef("list", ValueType::List),
×
UNCOV
837
                                            types::Typedef("index", ValueType::Number),
×
UNCOV
838
                                            types::Typedef("new_value", ValueType::Any) } } },
×
UNCOV
839
                                      { types::Contract {
×
UNCOV
840
                                          { types::Typedef("string", ValueType::String),
×
UNCOV
841
                                            types::Typedef("index", ValueType::Number),
×
UNCOV
842
                                            types::Typedef("char", ValueType::String) } } } },
×
UNCOV
843
                                    { *list, number });
×
844

845
                            const std::size_t size = list->valueType() == ValueType::List ? list->list().size() : list->stringRef().size();
488✔
846
                            long idx = static_cast<long>(number.number());
488✔
847
                            idx = idx < 0 ? static_cast<long>(size) + idx : idx;
488✔
848
                            if (std::cmp_greater_equal(idx, size))
488✔
849
                                throwVMError(
1✔
850
                                    ErrorKind::Index,
851
                                    fmt::format("@= index ({}) out of range (indexable size: {})", idx, size));
1✔
852

853
                            if (list->valueType() == ValueType::List)
487✔
854
                                list->list()[static_cast<std::size_t>(idx)] = new_value;
485✔
855
                            else
856
                                list->stringRef()[static_cast<std::size_t>(idx)] = new_value.string()[0];
2✔
857
                        }
488✔
858
                        DISPATCH();
487✔
859
                    }
8✔
860

861
                    TARGET(SET_AT_2_INDEX)
862
                    {
863
                        {
864
                            Value* list = popAndResolveAsPtr(context);
8✔
865
                            Value x = *popAndResolveAsPtr(context);
8✔
866
                            Value y = *popAndResolveAsPtr(context);
8✔
867
                            Value new_value = *popAndResolveAsPtr(context);
8✔
868

869
                            if (list->valueType() != ValueType::List || x.valueType() != ValueType::Number || y.valueType() != ValueType::Number)
8✔
UNCOV
870
                                types::generateError(
×
UNCOV
871
                                    "@@=",
×
UNCOV
872
                                    { { types::Contract {
×
UNCOV
873
                                        { types::Typedef("list", ValueType::List),
×
UNCOV
874
                                          types::Typedef("x", ValueType::Number),
×
875
                                          types::Typedef("y", ValueType::Number),
×
876
                                          types::Typedef("new_value", ValueType::Any) } } } },
×
877
                                    { *list, x, y });
×
878

879
                            long idx_y = static_cast<long>(x.number());
8✔
880
                            idx_y = idx_y < 0 ? static_cast<long>(list->list().size()) + idx_y : idx_y;
8✔
881
                            if (std::cmp_greater_equal(idx_y, list->list().size()))
8✔
882
                                throwVMError(
1✔
883
                                    ErrorKind::Index,
884
                                    fmt::format("@@= index (y: {}) out of range (list size: {})", idx_y, list->list().size()));
1✔
885

886
                            if (!list->list()[static_cast<std::size_t>(idx_y)].isIndexable() ||
11✔
887
                                (list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::String && new_value.valueType() != ValueType::String))
7✔
UNCOV
888
                                types::generateError(
×
UNCOV
889
                                    "@@=",
×
UNCOV
890
                                    { { types::Contract {
×
UNCOV
891
                                          { types::Typedef("list", ValueType::List),
×
UNCOV
892
                                            types::Typedef("x", ValueType::Number),
×
UNCOV
893
                                            types::Typedef("y", ValueType::Number),
×
UNCOV
894
                                            types::Typedef("new_value", ValueType::Any) } } },
×
UNCOV
895
                                      { types::Contract {
×
UNCOV
896
                                          { types::Typedef("string", ValueType::String),
×
UNCOV
897
                                            types::Typedef("x", ValueType::Number),
×
UNCOV
898
                                            types::Typedef("y", ValueType::Number),
×
UNCOV
899
                                            types::Typedef("char", ValueType::String) } } } },
×
UNCOV
900
                                    { *list, x, y });
×
901

902
                            const bool is_list = list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
7✔
903
                            const std::size_t size =
7✔
904
                                is_list
14✔
905
                                ? list->list()[static_cast<std::size_t>(idx_y)].list().size()
5✔
906
                                : list->list()[static_cast<std::size_t>(idx_y)].stringRef().size();
2✔
907

908
                            long idx_x = static_cast<long>(y.number());
7✔
909
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
7✔
910
                            if (std::cmp_greater_equal(idx_x, size))
7✔
911
                                throwVMError(
1✔
912
                                    ErrorKind::Index,
913
                                    fmt::format("@@= index (x: {}) out of range (inner indexable size: {})", idx_x, size));
1✔
914

915
                            if (is_list)
6✔
916
                                list->list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)] = new_value;
4✔
917
                            else
918
                                list->list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)] = new_value.string()[0];
2✔
919
                        }
8✔
920
                        DISPATCH();
6✔
921
                    }
9,162✔
922

923
                    TARGET(POP)
924
                    {
925
                        pop(context);
9,162✔
926
                        DISPATCH();
9,162✔
927
                    }
7,976✔
928

929
                    TARGET(DUP)
930
                    {
931
                        context.stack[context.sp] = context.stack[context.sp - 1];
7,976✔
932
                        ++context.sp;
7,976✔
933
                        DISPATCH();
7,976✔
934
                    }
1,767✔
935

936
                    TARGET(CREATE_SCOPE)
937
                    {
938
                        context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd());
1,767✔
939
                        DISPATCH();
1,767✔
940
                    }
1,767✔
941

942
                    TARGET(POP_SCOPE)
943
                    {
944
                        context.locals.pop_back();
1,767✔
945
                        DISPATCH();
1,767✔
946
                    }
25,165✔
947

948
#pragma endregion
949

950
#pragma region "Operators"
951

952
                    TARGET(ADD)
953
                    {
954
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
25,165✔
955

956
                        if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
25,165✔
957
                            push(Value(a->number() + b->number()), context);
18,059✔
958
                        else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
7,106✔
959
                            push(Value(a->string() + b->string()), context);
7,106✔
960
                        else
961
                            types::generateError(
×
962
                                "+",
×
963
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } },
×
964
                                    types::Contract { { types::Typedef("a", ValueType::String), types::Typedef("b", ValueType::String) } } } },
×
UNCOV
965
                                { *a, *b });
×
966
                        DISPATCH();
25,165✔
967
                    }
117✔
968

969
                    TARGET(SUB)
970
                    {
971
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
117✔
972

973
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
117✔
974
                            types::generateError(
×
975
                                "-",
×
976
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
977
                                { *a, *b });
×
978
                        push(Value(a->number() - b->number()), context);
117✔
979
                        DISPATCH();
117✔
980
                    }
1,317✔
981

982
                    TARGET(MUL)
983
                    {
984
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,317✔
985

986
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1,317✔
987
                            types::generateError(
×
988
                                "*",
×
989
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
990
                                { *a, *b });
×
991
                        push(Value(a->number() * b->number()), context);
1,317✔
992
                        DISPATCH();
1,317✔
993
                    }
1,054✔
994

995
                    TARGET(DIV)
996
                    {
997
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,054✔
998

999
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1,054✔
UNCOV
1000
                            types::generateError(
×
UNCOV
1001
                                "/",
×
UNCOV
1002
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
UNCOV
1003
                                { *a, *b });
×
1004
                        auto d = b->number();
1,054✔
1005
                        if (d == 0)
1,054✔
1006
                            throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a->toString(*this), b->toString(*this)));
1✔
1007

1008
                        push(Value(a->number() / d), context);
1,053✔
1009
                        DISPATCH();
1,053✔
1010
                    }
173,036✔
1011

1012
                    TARGET(GT)
1013
                    {
1014
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
173,036✔
1015
                        push((*a != *b && !(*a < *b)) ? Builtins::trueSym : Builtins::falseSym, context);
173,036✔
1016
                        DISPATCH();
173,036✔
1017
                    }
39,686✔
1018

1019
                    TARGET(LT)
1020
                    {
1021
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
39,686✔
1022
                        push((*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
39,686✔
1023
                        DISPATCH();
39,686✔
1024
                    }
7,178✔
1025

1026
                    TARGET(LE)
1027
                    {
1028
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
7,178✔
1029
                        push((((*a < *b) || (*a == *b)) ? Builtins::trueSym : Builtins::falseSym), context);
7,178✔
1030
                        DISPATCH();
7,178✔
1031
                    }
5,268✔
1032

1033
                    TARGET(GE)
1034
                    {
1035
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
5,268✔
1036
                        push(!(*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
5,268✔
1037
                        DISPATCH();
5,268✔
1038
                    }
617✔
1039

1040
                    TARGET(NEQ)
1041
                    {
1042
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
617✔
1043
                        push((*a != *b) ? Builtins::trueSym : Builtins::falseSym, context);
617✔
1044
                        DISPATCH();
617✔
1045
                    }
89,361✔
1046

1047
                    TARGET(EQ)
1048
                    {
1049
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
89,361✔
1050
                        push((*a == *b) ? Builtins::trueSym : Builtins::falseSym, context);
89,361✔
1051
                        DISPATCH();
89,361✔
1052
                    }
10,675✔
1053

1054
                    TARGET(LEN)
1055
                    {
1056
                        const Value* a = popAndResolveAsPtr(context);
10,675✔
1057

1058
                        if (a->valueType() == ValueType::List)
10,675✔
1059
                            push(Value(static_cast<int>(a->constList().size())), context);
3,186✔
1060
                        else if (a->valueType() == ValueType::String)
7,489✔
1061
                            push(Value(static_cast<int>(a->string().size())), context);
7,489✔
1062
                        else
UNCOV
1063
                            types::generateError(
×
UNCOV
1064
                                "len",
×
UNCOV
1065
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
×
UNCOV
1066
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
×
1067
                                { *a });
×
1068
                        DISPATCH();
10,675✔
1069
                    }
537✔
1070

1071
                    TARGET(EMPTY)
1072
                    {
1073
                        const Value* a = popAndResolveAsPtr(context);
537✔
1074

1075
                        if (a->valueType() == ValueType::List)
537✔
1076
                            push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context);
86✔
1077
                        else if (a->valueType() == ValueType::String)
451✔
1078
                            push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context);
451✔
1079
                        else
UNCOV
1080
                            types::generateError(
×
UNCOV
1081
                                "empty?",
×
UNCOV
1082
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
×
UNCOV
1083
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
×
UNCOV
1084
                                { *a });
×
1085
                        DISPATCH();
537✔
1086
                    }
334✔
1087

1088
                    TARGET(TAIL)
1089
                    {
1090
                        Value* const a = popAndResolveAsPtr(context);
334✔
1091
                        push(helper::tail(a), context);
334✔
1092
                        DISPATCH();
334✔
1093
                    }
1,096✔
1094

1095
                    TARGET(HEAD)
1096
                    {
1097
                        Value* const a = popAndResolveAsPtr(context);
1,096✔
1098
                        push(helper::head(a), context);
1,096✔
1099
                        DISPATCH();
1,096✔
1100
                    }
1,268✔
1101

1102
                    TARGET(ISNIL)
1103
                    {
1104
                        const Value* a = popAndResolveAsPtr(context);
1,268✔
1105
                        push((*a == Builtins::nil) ? Builtins::trueSym : Builtins::falseSym, context);
1,268✔
1106
                        DISPATCH();
1,268✔
1107
                    }
556✔
1108

1109
                    TARGET(ASSERT)
1110
                    {
1111
                        Value* const b = popAndResolveAsPtr(context);
556✔
1112
                        Value* const a = popAndResolveAsPtr(context);
556✔
1113

1114
                        if (b->valueType() != ValueType::String)
556✔
UNCOV
1115
                            types::generateError(
×
UNCOV
1116
                                "assert",
×
1117
                                { { types::Contract { { types::Typedef("expr", ValueType::Any), types::Typedef("message", ValueType::String) } } } },
×
1118
                                { *a, *b });
×
1119

1120
                        if (*a == Builtins::falseSym)
556✔
UNCOV
1121
                            throw AssertionFailed(b->stringRef());
×
1122
                        DISPATCH();
556✔
1123
                    }
13✔
1124

1125
                    TARGET(TO_NUM)
1126
                    {
1127
                        const Value* a = popAndResolveAsPtr(context);
13✔
1128

1129
                        if (a->valueType() != ValueType::String)
13✔
UNCOV
1130
                            types::generateError(
×
UNCOV
1131
                                "toNumber",
×
UNCOV
1132
                                { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
×
UNCOV
1133
                                { *a });
×
1134

1135
                        double val;
1136
                        if (Utils::isDouble(a->string(), &val))
13✔
1137
                            push(Value(val), context);
10✔
1138
                        else
1139
                            push(Builtins::nil, context);
3✔
1140
                        DISPATCH();
13✔
1141
                    }
16✔
1142

1143
                    TARGET(TO_STR)
1144
                    {
1145
                        const Value* a = popAndResolveAsPtr(context);
16✔
1146
                        push(Value(a->toString(*this)), context);
16✔
1147
                        DISPATCH();
16✔
1148
                    }
14,415✔
1149

1150
                    TARGET(AT)
1151
                    {
1152
                        {
1153
                            const Value* b = popAndResolveAsPtr(context);
14,415✔
1154
                            Value& a = *popAndResolveAsPtr(context);
14,415✔
1155

1156
                            if (b->valueType() != ValueType::Number)
14,415✔
UNCOV
1157
                                types::generateError(
×
UNCOV
1158
                                    "@",
×
UNCOV
1159
                                    { { types::Contract { { types::Typedef("src", ValueType::List), types::Typedef("idx", ValueType::Number) } },
×
UNCOV
1160
                                        types::Contract { { types::Typedef("src", ValueType::String), types::Typedef("idx", ValueType::Number) } } } },
×
UNCOV
1161
                                    { a, *b });
×
1162

1163
                            long idx = static_cast<long>(b->number());
14,415✔
1164

1165
                            if (a.valueType() == ValueType::List)
14,415✔
1166
                            {
1167
                                if (std::cmp_less(std::abs(idx), a.list().size()))
6,771✔
1168
                                    push(a.list()[static_cast<std::size_t>(idx < 0 ? static_cast<long>(a.list().size()) + idx : idx)], context);
6,770✔
1169
                                else
1170
                                    throwVMError(
1✔
1171
                                        ErrorKind::Index,
1172
                                        fmt::format("{} out of range {} (length {})", idx, a.toString(*this), a.list().size()));
1✔
1173
                            }
6,770✔
1174
                            else if (a.valueType() == ValueType::String)
7,644✔
1175
                            {
1176
                                if (std::cmp_less(std::abs(idx), a.string().size()))
7,644✔
1177
                                    push(Value(std::string(1, a.string()[static_cast<std::size_t>(idx < 0 ? static_cast<long>(a.string().size()) + idx : idx)])), context);
7,643✔
1178
                                else
1179
                                    throwVMError(
1✔
1180
                                        ErrorKind::Index,
1181
                                        fmt::format("{} out of range \"{}\" (length {})", idx, a.string(), a.string().size()));
1✔
1182
                            }
7,643✔
1183
                            else
UNCOV
1184
                                types::generateError(
×
UNCOV
1185
                                    "@",
×
UNCOV
1186
                                    { { types::Contract { { types::Typedef("src", ValueType::List), types::Typedef("idx", ValueType::Number) } },
×
UNCOV
1187
                                        types::Contract { { types::Typedef("src", ValueType::String), types::Typedef("idx", ValueType::Number) } } } },
×
UNCOV
1188
                                    { a, *b });
×
1189
                        }
1190
                        DISPATCH();
14,413✔
1191
                    }
15✔
1192

1193
                    TARGET(AT_AT)
1194
                    {
1195
                        {
1196
                            const Value* x = popAndResolveAsPtr(context);
15✔
1197
                            const Value* y = popAndResolveAsPtr(context);
15✔
1198
                            Value& list = *popAndResolveAsPtr(context);
15✔
1199

1200
                            if (y->valueType() != ValueType::Number || x->valueType() != ValueType::Number ||
15✔
1201
                                list.valueType() != ValueType::List)
15✔
UNCOV
1202
                                types::generateError(
×
UNCOV
1203
                                    "@@",
×
UNCOV
1204
                                    { { types::Contract {
×
UNCOV
1205
                                        { types::Typedef("src", ValueType::List),
×
UNCOV
1206
                                          types::Typedef("y", ValueType::Number),
×
UNCOV
1207
                                          types::Typedef("x", ValueType::Number) } } } },
×
UNCOV
1208
                                    { list, *y, *x });
×
1209

1210
                            long idx_y = static_cast<long>(y->number());
15✔
1211
                            idx_y = idx_y < 0 ? static_cast<long>(list.list().size()) + idx_y : idx_y;
15✔
1212
                            if (std::cmp_greater_equal(idx_y, list.list().size()))
15✔
1213
                                throwVMError(
1✔
1214
                                    ErrorKind::Index,
1215
                                    fmt::format("@@ index ({}) out of range (list size: {})", idx_y, list.list().size()));
1✔
1216

1217
                            const bool is_list = list.list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
14✔
1218
                            const std::size_t size =
14✔
1219
                                is_list
28✔
1220
                                ? list.list()[static_cast<std::size_t>(idx_y)].list().size()
7✔
1221
                                : list.list()[static_cast<std::size_t>(idx_y)].stringRef().size();
7✔
1222

1223
                            long idx_x = static_cast<long>(x->number());
14✔
1224
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
14✔
1225
                            if (std::cmp_greater_equal(idx_x, size))
14✔
1226
                                throwVMError(
1✔
1227
                                    ErrorKind::Index,
1228
                                    fmt::format("@@ index (x: {}) out of range (inner indexable size: {})", idx_x, size));
1✔
1229

1230
                            if (is_list)
13✔
1231
                                push(list.list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)], context);
6✔
1232
                            else
1233
                                push(Value(std::string(1, list.list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)])), context);
7✔
1234
                        }
1235
                        DISPATCH();
13✔
1236
                    }
783✔
1237

1238
                    TARGET(MOD)
1239
                    {
1240
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
783✔
1241
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
783✔
1242
                            types::generateError(
×
1243
                                "mod",
×
1244
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
UNCOV
1245
                                { *a, *b });
×
1246
                        push(Value(std::fmod(a->number(), b->number())), context);
783✔
1247
                        DISPATCH();
783✔
1248
                    }
87✔
1249

1250
                    TARGET(TYPE)
1251
                    {
1252
                        const Value* a = popAndResolveAsPtr(context);
87✔
1253
                        if (a == &m_undefined_value) [[unlikely]]
87✔
UNCOV
1254
                            types::generateError(
×
UNCOV
1255
                                "type",
×
1256
                                { { types::Contract { { types::Typedef("value", ValueType::Any) } } } },
×
1257
                                {});
×
1258

1259
                        push(Value(types_to_str[static_cast<unsigned>(a->valueType())]), context);
87✔
1260
                        DISPATCH();
87✔
1261
                    }
2✔
1262

1263
                    TARGET(HASFIELD)
1264
                    {
1265
                        {
1266
                            Value* const field = popAndResolveAsPtr(context);
2✔
1267
                            Value* const closure = popAndResolveAsPtr(context);
2✔
1268
                            if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String)
2✔
UNCOV
1269
                                types::generateError(
×
UNCOV
1270
                                    "hasField",
×
UNCOV
1271
                                    { { types::Contract { { types::Typedef("closure", ValueType::Closure), types::Typedef("field", ValueType::String) } } } },
×
UNCOV
1272
                                    { *closure, *field });
×
1273

1274
                            auto it = std::ranges::find(m_state.m_symbols, field->stringRef());
2✔
1275
                            if (it == m_state.m_symbols.end())
2✔
1276
                            {
1277
                                push(Builtins::falseSym, context);
1✔
1278
                                DISPATCH();
1✔
1279
                            }
1280

1281
                            auto id = static_cast<std::uint16_t>(std::distance(m_state.m_symbols.begin(), it));
1✔
1282
                            push(closure->refClosure().refScope()[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context);
1✔
1283
                        }
1284
                        DISPATCH();
1✔
1285
                    }
2,346✔
1286

1287
                    TARGET(NOT)
1288
                    {
1289
                        const Value* a = popAndResolveAsPtr(context);
2,346✔
1290
                        push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
2,346✔
1291
                        DISPATCH();
2,346✔
1292
                    }
5,376✔
1293

1294
#pragma endregion
1295

1296
#pragma region "Super Instructions"
1297
                    TARGET(LOAD_CONST_LOAD_CONST)
1298
                    {
1299
                        UNPACK_ARGS();
5,376✔
1300
                        push(loadConstAsPtr(primary_arg), context);
5,376✔
1301
                        push(loadConstAsPtr(secondary_arg), context);
5,376✔
1302
                        DISPATCH();
5,376✔
1303
                    }
5,390✔
1304

1305
                    TARGET(LOAD_CONST_STORE)
1306
                    {
1307
                        UNPACK_ARGS();
5,390✔
1308
                        store(secondary_arg, loadConstAsPtr(primary_arg), context);
5,390✔
1309
                        DISPATCH();
5,390✔
1310
                    }
208✔
1311

1312
                    TARGET(LOAD_CONST_SET_VAL)
1313
                    {
1314
                        UNPACK_ARGS();
208✔
1315
                        setVal(secondary_arg, loadConstAsPtr(primary_arg), context);
208✔
1316
                        DISPATCH();
207✔
1317
                    }
15✔
1318

1319
                    TARGET(STORE_FROM)
1320
                    {
1321
                        UNPACK_ARGS();
15✔
1322
                        store(secondary_arg, loadSymbol(primary_arg, context), context);
15✔
1323
                        DISPATCH();
14✔
1324
                    }
572✔
1325

1326
                    TARGET(STORE_FROM_INDEX)
1327
                    {
1328
                        UNPACK_ARGS();
572✔
1329
                        store(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
572✔
1330
                        DISPATCH();
572✔
1331
                    }
35✔
1332

1333
                    TARGET(SET_VAL_FROM)
1334
                    {
1335
                        UNPACK_ARGS();
35✔
1336
                        setVal(secondary_arg, loadSymbol(primary_arg, context), context);
35✔
1337
                        DISPATCH();
35✔
1338
                    }
123✔
1339

1340
                    TARGET(SET_VAL_FROM_INDEX)
1341
                    {
1342
                        UNPACK_ARGS();
123✔
1343
                        setVal(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
123✔
1344
                        DISPATCH();
123✔
1345
                    }
14,883✔
1346

1347
                    TARGET(INCREMENT)
1348
                    {
1349
                        UNPACK_ARGS();
14,883✔
1350
                        {
1351
                            Value* var = loadSymbol(primary_arg, context);
14,883✔
1352

1353
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1354
                            if (var->valueType() == ValueType::Reference)
14,883✔
1355
                                var = var->reference();
×
1356

1357
                            if (var->valueType() == ValueType::Number)
14,883✔
1358
                                push(Value(var->number() + secondary_arg), context);
14,883✔
1359
                            else
UNCOV
1360
                                types::generateError(
×
UNCOV
1361
                                    "+",
×
UNCOV
1362
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
UNCOV
1363
                                    { *var, Value(secondary_arg) });
×
1364
                        }
1365
                        DISPATCH();
14,883✔
1366
                    }
89,998✔
1367

1368
                    TARGET(INCREMENT_BY_INDEX)
1369
                    {
1370
                        UNPACK_ARGS();
89,998✔
1371
                        {
1372
                            Value* var = loadSymbolFromIndex(primary_arg, context);
89,998✔
1373

1374
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1375
                            if (var->valueType() == ValueType::Reference)
89,998✔
UNCOV
1376
                                var = var->reference();
×
1377

1378
                            if (var->valueType() == ValueType::Number)
89,998✔
1379
                                push(Value(var->number() + secondary_arg), context);
89,998✔
1380
                            else
UNCOV
1381
                                types::generateError(
×
UNCOV
1382
                                    "+",
×
UNCOV
1383
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
UNCOV
1384
                                    { *var, Value(secondary_arg) });
×
1385
                        }
1386
                        DISPATCH();
89,998✔
1387
                    }
1,274✔
1388

1389
                    TARGET(DECREMENT)
1390
                    {
1391
                        UNPACK_ARGS();
1,274✔
1392
                        {
1393
                            Value* var = loadSymbol(primary_arg, context);
1,274✔
1394

1395
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1396
                            if (var->valueType() == ValueType::Reference)
1,274✔
UNCOV
1397
                                var = var->reference();
×
1398

1399
                            if (var->valueType() == ValueType::Number)
1,274✔
1400
                                push(Value(var->number() - secondary_arg), context);
1,274✔
1401
                            else
UNCOV
1402
                                types::generateError(
×
UNCOV
1403
                                    "-",
×
UNCOV
1404
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
UNCOV
1405
                                    { *var, Value(secondary_arg) });
×
1406
                        }
1407
                        DISPATCH();
1,274✔
1408
                    }
194,358✔
1409

1410
                    TARGET(DECREMENT_BY_INDEX)
1411
                    {
1412
                        UNPACK_ARGS();
194,358✔
1413
                        {
1414
                            Value* var = loadSymbolFromIndex(primary_arg, context);
194,358✔
1415

1416
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1417
                            if (var->valueType() == ValueType::Reference)
194,358✔
UNCOV
1418
                                var = var->reference();
×
1419

1420
                            if (var->valueType() == ValueType::Number)
194,358✔
1421
                                push(Value(var->number() - secondary_arg), context);
194,358✔
1422
                            else
UNCOV
1423
                                types::generateError(
×
UNCOV
1424
                                    "-",
×
UNCOV
1425
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
UNCOV
1426
                                    { *var, Value(secondary_arg) });
×
1427
                        }
1428
                        DISPATCH();
194,358✔
UNCOV
1429
                    }
×
1430

1431
                    TARGET(STORE_TAIL)
1432
                    {
UNCOV
1433
                        UNPACK_ARGS();
×
1434
                        {
UNCOV
1435
                            Value* list = loadSymbol(primary_arg, context);
×
1436
                            Value tail = helper::tail(list);
×
UNCOV
1437
                            store(secondary_arg, &tail, context);
×
UNCOV
1438
                        }
×
UNCOV
1439
                        DISPATCH();
×
1440
                    }
1✔
1441

1442
                    TARGET(STORE_TAIL_BY_INDEX)
1443
                    {
1444
                        UNPACK_ARGS();
1✔
1445
                        {
1446
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1447
                            Value tail = helper::tail(list);
1✔
1448
                            store(secondary_arg, &tail, context);
1✔
1449
                        }
1✔
1450
                        DISPATCH();
1✔
UNCOV
1451
                    }
×
1452

1453
                    TARGET(STORE_HEAD)
1454
                    {
1455
                        UNPACK_ARGS();
×
1456
                        {
1457
                            Value* list = loadSymbol(primary_arg, context);
×
UNCOV
1458
                            Value head = helper::head(list);
×
1459
                            store(secondary_arg, &head, context);
×
1460
                        }
×
1461
                        DISPATCH();
×
1462
                    }
31✔
1463

1464
                    TARGET(STORE_HEAD_BY_INDEX)
1465
                    {
1466
                        UNPACK_ARGS();
31✔
1467
                        {
1468
                            Value* list = loadSymbolFromIndex(primary_arg, context);
31✔
1469
                            Value head = helper::head(list);
31✔
1470
                            store(secondary_arg, &head, context);
31✔
1471
                        }
31✔
1472
                        DISPATCH();
31✔
1473
                    }
×
1474

1475
                    TARGET(SET_VAL_TAIL)
1476
                    {
UNCOV
1477
                        UNPACK_ARGS();
×
1478
                        {
1479
                            Value* list = loadSymbol(primary_arg, context);
×
UNCOV
1480
                            Value tail = helper::tail(list);
×
1481
                            setVal(secondary_arg, &tail, context);
×
UNCOV
1482
                        }
×
1483
                        DISPATCH();
×
1484
                    }
1✔
1485

1486
                    TARGET(SET_VAL_TAIL_BY_INDEX)
1487
                    {
1488
                        UNPACK_ARGS();
1✔
1489
                        {
1490
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1491
                            Value tail = helper::tail(list);
1✔
1492
                            setVal(secondary_arg, &tail, context);
1✔
1493
                        }
1✔
1494
                        DISPATCH();
1✔
1495
                    }
×
1496

1497
                    TARGET(SET_VAL_HEAD)
1498
                    {
1499
                        UNPACK_ARGS();
×
1500
                        {
1501
                            Value* list = loadSymbol(primary_arg, context);
×
1502
                            Value head = helper::head(list);
×
1503
                            setVal(secondary_arg, &head, context);
×
1504
                        }
×
UNCOV
1505
                        DISPATCH();
×
1506
                    }
1✔
1507

1508
                    TARGET(SET_VAL_HEAD_BY_INDEX)
1509
                    {
1510
                        UNPACK_ARGS();
1✔
1511
                        {
1512
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1513
                            Value head = helper::head(list);
1✔
1514
                            setVal(secondary_arg, &head, context);
1✔
1515
                        }
1✔
1516
                        DISPATCH();
1✔
1517
                    }
10,633✔
1518

1519
                    TARGET(CALL_BUILTIN)
1520
                    {
1521
                        UNPACK_ARGS();
10,633✔
1522
                        // no stack size check because we do not push IP/PP since we are just calling a builtin
1523
                        callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg);
10,633✔
1524
                        if (!m_running)
10,624✔
1525
                            GOTO_HALT();
×
1526
                        DISPATCH();
10,624✔
1527
                    }
1528
#pragma endregion
1529
                }
59✔
1530
#if ARK_USE_COMPUTED_GOTOS
1531
            dispatch_end:
1532
                do
59✔
1533
                {
1534
                } while (false);
59✔
1535
#endif
1536
            }
1537
        }
89✔
1538
        catch (const std::exception& e)
1539
        {
1540
            if (fail_with_exception)
30✔
1541
                throw;
30✔
1542

1543
            fmt::println("{}", e.what());
×
UNCOV
1544
            backtrace(context);
×
1545
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1546
            // don't report a "failed" exit code so that the fuzzers can more accurately triage crashes
1547
            m_exit_code = 0;
1548
#else
UNCOV
1549
            m_exit_code = 1;
×
1550
#endif
1551
        }
89✔
1552
        catch (...)
1553
        {
UNCOV
1554
            if (fail_with_exception)
×
UNCOV
1555
                throw;
×
1556

1557
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1558
            throw;
1559
#endif
UNCOV
1560
            fmt::println("Unknown error");
×
UNCOV
1561
            backtrace(context);
×
UNCOV
1562
            m_exit_code = 1;
×
1563
        }
60✔
1564

1565
        return m_exit_code;
59✔
1566
    }
62✔
1567

UNCOV
1568
    uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
×
UNCOV
1569
    {
×
UNCOV
1570
        for (auto& local : std::ranges::reverse_view(context.locals))
×
1571
        {
UNCOV
1572
            if (const auto id = local.idFromValue(value); id < m_state.m_symbols.size())
×
UNCOV
1573
                return id;
×
UNCOV
1574
        }
×
UNCOV
1575
        return std::numeric_limits<uint16_t>::max();
×
UNCOV
1576
    }
×
1577

1578
    void VM::throwVMError(ErrorKind kind, const std::string& message)
22✔
1579
    {
22✔
1580
        throw std::runtime_error(std::string(errorKinds[static_cast<std::size_t>(kind)]) + ": " + message + "\n");
22✔
1581
    }
22✔
1582

UNCOV
1583
    void VM::backtrace(ExecutionContext& context) noexcept
×
UNCOV
1584
    {
×
UNCOV
1585
        const std::size_t saved_ip = context.ip;
×
UNCOV
1586
        const std::size_t saved_pp = context.pp;
×
UNCOV
1587
        const uint16_t saved_sp = context.sp;
×
1588

UNCOV
1589
        if (const uint16_t original_frame_count = context.fc; original_frame_count > 1)
×
1590
        {
1591
            // display call stack trace
UNCOV
1592
            const ScopeView old_scope = context.locals.back();
×
1593

UNCOV
1594
            while (context.fc != 0)
×
1595
            {
UNCOV
1596
                fmt::print("[{}] ", fmt::styled(context.fc, fmt::fg(fmt::color::cyan)));
×
UNCOV
1597
                if (context.pp != 0)
×
1598
                {
UNCOV
1599
                    const uint16_t id = findNearestVariableIdWithValue(
×
UNCOV
1600
                        Value(static_cast<PageAddr_t>(context.pp)),
×
UNCOV
1601
                        context);
×
1602

UNCOV
1603
                    if (id < m_state.m_symbols.size())
×
UNCOV
1604
                        fmt::println("In function `{}'", fmt::styled(m_state.m_symbols[id], fmt::fg(fmt::color::green)));
×
1605
                    else  // should never happen
UNCOV
1606
                        fmt::println("In function `{}'", fmt::styled("???", fmt::fg(fmt::color::gold)));
×
1607

UNCOV
1608
                    Value* ip;
×
UNCOV
1609
                    do
×
1610
                    {
UNCOV
1611
                        ip = popAndResolveAsPtr(context);
×
UNCOV
1612
                    } while (ip->valueType() != ValueType::InstPtr);
×
1613

UNCOV
1614
                    context.ip = ip->pageAddr();
×
UNCOV
1615
                    context.pp = pop(context)->pageAddr();
×
UNCOV
1616
                    returnFromFuncCall(context);
×
UNCOV
1617
                }
×
1618
                else
1619
                {
UNCOV
1620
                    fmt::println("In global scope");
×
UNCOV
1621
                    break;
×
1622
                }
1623

UNCOV
1624
                if (original_frame_count - context.fc > 7)
×
1625
                {
UNCOV
1626
                    fmt::println("...");
×
UNCOV
1627
                    break;
×
1628
                }
1629
            }
1630

1631
            // display variables values in the current scope
UNCOV
1632
            fmt::println("\nCurrent scope variables values:");
×
UNCOV
1633
            for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
×
1634
            {
UNCOV
1635
                fmt::println(
×
UNCOV
1636
                    "{} = {}",
×
UNCOV
1637
                    fmt::styled(m_state.m_symbols[old_scope.atPos(i).first], fmt::fg(fmt::color::cyan)),
×
UNCOV
1638
                    old_scope.atPos(i).second.toString(*this));
×
UNCOV
1639
            }
×
UNCOV
1640
        }
×
1641

NEW
1642
        fmt::println(
×
NEW
1643
            "At IP: {}, PP: {}, SP: {}",
×
1644
            // dividing by 4 because the instructions are actually on 4 bytes
NEW
1645
            fmt::styled(saved_ip / 4, fmt::fg(fmt::color::cyan)),
×
NEW
1646
            fmt::styled(saved_pp, fmt::fg(fmt::color::green)),
×
NEW
1647
            fmt::styled(saved_sp, fmt::fg(fmt::color::yellow)));
×
UNCOV
1648
    }
×
1649
}
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