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

ArkScript-lang / Ark / 29761103967

20 Jul 2026 04:47PM UTC coverage: 94.115% (-0.3%) from 94.385%
29761103967

Pull #705

github

web-flow
Merge d1df3d7ce into 00cfbe903
Pull Request #705: Feat/inliner

413 of 459 new or added lines in 11 files covered. (89.98%)

11 existing lines in 2 files now uncovered.

10667 of 11334 relevant lines covered (94.12%)

1164822.98 hits per line

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

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

3
#include <fmt/core.h>
4
#include <fmt/color.h>
5
#include <fmt/ranges.h>
6
#include <fmt/ostream.h>
7
#include <chrono>
8
#include <thread>
9
#include <charconv>
10

11
#include <Ark/State.hpp>
12
#include <Ark/VM/VM.hpp>
13
#include <Ark/Utils/Files.hpp>
14
#include <Ark/Compiler/Welder.hpp>
15
#include <Ark/Compiler/BytecodeReader.hpp>
16
#include <Ark/Error/Diagnostics.hpp>
17

18
namespace Ark::internal
19
{
20
    Debugger::Debugger(const ExecutionContext& context, const std::vector<std::filesystem::path>& libenv, const std::vector<std::string>& symbols, const std::vector<Value>& constants) :
×
21
        m_libenv(libenv),
×
22
        m_symbols(symbols),
×
23
        m_constants(constants),
×
24
        m_os(std::cout),
×
25
        m_colorize(true)
×
26
    {
×
27
        initCommands();
×
28
        saveState(context);
×
29
    }
×
30

31
    Debugger::Debugger(const std::vector<std::filesystem::path>& libenv, const std::string& path_to_prompt_file, std::ostream& os, const std::vector<std::string>& symbols, const std::vector<Value>& constants) :
21✔
32
        m_libenv(libenv),
7✔
33
        m_symbols(symbols),
7✔
34
        m_constants(constants),
7✔
35
        m_os(os),
7✔
36
        m_colorize(false),
7✔
37
        m_prompt_stream(std::make_unique<std::ifstream>(path_to_prompt_file))
7✔
38
    {
7✔
39
        initCommands();
7✔
40
    }
7✔
41

42
    void Debugger::saveState(const ExecutionContext& context)
14✔
43
    {
14✔
44
        m_states.emplace_back(
28✔
45
            std::make_unique<SavedState>(
28✔
46
                context.ip,
14✔
47
                context.pp,
14✔
48
                context.sp,
14✔
49
                context.fc,
14✔
50
                context.locals,
14✔
51
                context.stacked_closure_scopes));
14✔
52
    }
14✔
53

54
    void Debugger::resetContextToSavedState(ExecutionContext& context)
14✔
55
    {
14✔
56
        const auto& [ip, pp, sp, fc, locals, closure_scopes] = *m_states.back();
42✔
57
        context.locals = locals;
14✔
58
        context.stacked_closure_scopes = closure_scopes;
14✔
59
        context.ip = ip;
14✔
60
        context.pp = pp;
14✔
61
        context.sp = sp;
14✔
62
        context.fc = fc;
14✔
63

64
        m_states.pop_back();
14✔
65
    }
14✔
66

67
    void Debugger::run(VM& vm, ExecutionContext& context, const bool from_breakpoint)
14✔
68
    {
14✔
69
        using namespace std::chrono_literals;
70

71
        if (from_breakpoint)
14✔
72
            showContext(vm, context);
13✔
73

74
        m_running = true;
14✔
75
        const bool is_vm_running = vm.m_running;
14✔
76
        const std::size_t ip_at_breakpoint = context.ip,
14✔
77
                          pp_at_breakpoint = context.pp;
14✔
78
        // create dedicated scope, so that we won't be overwriting existing variables
79
        context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd());
14✔
80
        std::size_t last_ip = 0;
14✔
81

82
        while (true)
27✔
83
        {
84
            std::optional<std::string> maybe_input = prompt(ip_at_breakpoint, pp_at_breakpoint, vm, context);
27✔
85

86
            if (maybe_input)
27✔
87
            {
88
                const std::string& line = maybe_input.value();
13✔
89

90
                if (const auto compiled = compile(m_code + line, vm.m_state.m_pages.size()); compiled.has_value())
26✔
91
                {
92
                    context.ip = last_ip;
13✔
93
                    context.pp = vm.m_state.m_pages.size();
13✔
94

95
                    vm.m_state.extendBytecode(compiled->pages, compiled->symbols, compiled->constants);
13✔
96

97
                    if (vm.safeRun(context) == 0)
13✔
98
                    {
99
                        // executing code worked
100
                        m_code += line + "\n";
13✔
101
                        // place ip to end of bytecode instruction (HALT)
102
                        last_ip = context.ip - 4;
13✔
103

104
                        const Value* maybe_value = vm.peekAndResolveAsPtr(context);
13✔
105
                        if (maybe_value != nullptr &&
13✔
106
                            maybe_value->valueType() != ValueType::Undefined &&
13✔
107
                            maybe_value->valueType() != ValueType::InstPtr &&
3✔
108
                            maybe_value->valueType() != ValueType::Garbage)
×
109
                            fmt::println(
×
110
                                m_os,
×
111
                                "{}",
×
112
                                fmt::styled(
×
113
                                    maybe_value->toString(vm),
×
114
                                    m_colorize ? fmt::fg(fmt::color::chocolate) : fmt::text_style()));
×
115
                    }
13✔
116
                }
13✔
117
                else
118
                    std::this_thread::sleep_for(50ms);  // hack to wait for the diagnostics to be output to stderr, since we write to stdout and it's faster than stderr
×
119
            }
13✔
120
            else
121
                break;
14✔
122
        }
27✔
123

124
        context.locals.pop_back();
14✔
125

126
        // we do not want to retain code from the past executions
127
        m_code.clear();
14✔
128
        m_line_count = 0;
14✔
129

130
        // we hit a HALT instruction that set 'running' to false, ignore that if we were still running!
131
        vm.m_running = is_vm_running;
14✔
132
        m_running = false;
14✔
133
    }
14✔
134

135
    void Debugger::registerInstruction(const uint32_t word) noexcept
431✔
136
    {
431✔
137
        // We don't want to register instructions from code entered in the debugger!
138
        if (!m_running)
431✔
139
            m_previous_insts.push_back(word);
366✔
140
    }
431✔
141

142
    std::optional<Debugger::Command::Args_t> Debugger::Command::getArgs(const std::string& line, std::ostream& os) const
16✔
143
    {
16✔
144
        std::vector<std::string> split = Utils::splitString(line, ' ');
16✔
145
        Args_t args_values = args;
16✔
146
        std::size_t i = 0;
16✔
147
        for (const auto& arg : std::ranges::views::drop(split, 1))
24✔
148
        {
149
            if (i < args.size())
8✔
150
                args_values[i].second = arg;
7✔
151
            else
152
            {
153
                fmt::println(os, "Too many arguments provided to {}, expected {}, got {}", split.front(), args.size(), split.size() - 1);
1✔
154
                return std::nullopt;
1✔
155
            }
156

157
            ++i;
7✔
158
        }
15✔
159

7✔
160
        return args_values;
15✔
161
    }
16✔
162

163
    std::optional<std::size_t> Debugger::Command::argAsCount(const std::string& line, const std::size_t idx, std::ostream& os) const
16✔
164
    {
16✔
165
        const std::optional<Args_t> maybe_parsed = getArgs(line, os);
16✔
166
        if (maybe_parsed && idx < maybe_parsed->size())
16✔
167
        {
7✔
168
            const std::string str = maybe_parsed.value()[idx].second;
15✔
169
            std::size_t result = 0;
15✔
170
            auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result);
15✔
171

172
            if (ec == std::errc())
15✔
173
                return result;
13✔
174

175
            fmt::println(os, "Couldn't parse argument as an unsigned integer");
2✔
176
            return std::nullopt;
2✔
177
        }
15✔
178
        return std::nullopt;
1✔
179
    }
16✔
180

181
    void Debugger::initCommands()
7✔
182
    {
7✔
183
        m_commands = {
63✔
184
            Command(
7✔
185
                "help",
7✔
186
                "display this message",
7✔
187
                [this](const std::string&, const CommandArgs&) {
8✔
188
                    fmt::println(m_os, "Available commands:");
1✔
189
                    for (const Command& cmd : m_commands)
9✔
190
                    {
191
                        if (cmd.is_exact)
8✔
192
                            fmt::println(m_os, "  {} -- {}", fmt::join(cmd.names, ", "), cmd.description);
4✔
193
                        else
194
                        {
195
                            const auto v = std::views::transform(cmd.args, [](const auto& p) {
8✔
196
                                return fmt::format("{}={}", p.first, p.second);
4✔
197
                            });
198
                            fmt::println(m_os, "  {} <{}> -- {}", fmt::join(cmd.names, ", "), fmt::join(v, ", "), cmd.description);
4✔
199
                        }
4✔
200
                    }
8✔
201
                    return false;
1✔
202
                }),
203
            Command(
7✔
204
                { "c", "continue" },
7✔
205
                "resume execution",
7✔
206
                [this](const std::string&, const CommandArgs&) {
20✔
207
                    fmt::println(m_os, "dbg: continue");
13✔
208
                    return true;
13✔
209
                }),
210
            Command(
7✔
211
                { "q", "quit" },
7✔
212
                "quit the debugger, stopping the script execution",
7✔
213
                [this](const std::string&, const CommandArgs&) {
8✔
214
                    fmt::println(m_os, "dbg: stop");
1✔
215
                    m_quit_vm = true;
1✔
216
                    return true;
1✔
217
                }),
218
            Command(
7✔
219
                StartsWith("stack"),
7✔
220
                { { "n", "5" } },
7✔
221
                "show the last n values on the stack",
7✔
222
                [this](const std::string& line, const CommandArgs& args) {
10✔
223
                    if (const auto arg = args.me.argAsCount(line, 0, m_os))
6✔
224
                        showStack(*args.vm_ptr, *args.ctx_ptr, arg.value());
3✔
225
                    return false;
3✔
226
                }),
227
            Command(
7✔
228
                StartsWith("locals"),
7✔
229
                { { "n", "5" } },
7✔
230
                "show the last n values on the locals' stack",
7✔
231
                [this](const std::string& line, const CommandArgs& args) {
13✔
232
                    if (const auto arg = args.me.argAsCount(line, 0, m_os))
12✔
233
                        showLocals(*args.vm_ptr, *args.ctx_ptr, arg.value());
6✔
234
                    return false;
6✔
235
                }),
236
            Command(
7✔
237
                StartsWith("scopes"),
7✔
238
                { { "n", "5" } },
7✔
239
                "show the last n scopes",
7✔
240
                [this](const std::string& line, const CommandArgs& args) {
7✔
NEW
241
                    if (const auto arg = args.me.argAsCount(line, 0, m_os))
×
NEW
242
                        showScopes(*args.vm_ptr, *args.ctx_ptr, arg.value());
×
NEW
243
                    return false;
×
244
                }),
245
            Command(
7✔
246
                "ptr",
7✔
247
                "show the values of the VM pointers",
7✔
248
                [this](const std::string&, const CommandArgs& args) {
8✔
249
                    fmt::println(
2✔
250
                        m_os,
1✔
251
                        "IP: {} - PP: {} - SP: {}",
1✔
252
                        fmt::styled(args.ip / 4, m_colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
1✔
253
                        fmt::styled(args.pp, m_colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
1✔
254
                        fmt::styled(args.ctx_ptr->sp, m_colorize ? fmt::fg(fmt::color::yellow) : fmt::text_style()));
1✔
255
                    return false;
1✔
256
                }),
257
            Command(
7✔
258
                StartsWith("trace"),
7✔
259
                { { "n", "10" } },
7✔
260
                "show the last n executed instructions",
7✔
261
                [this](const std::string& line, const CommandArgs& args) {
14✔
262
                    if (const auto arg = args.me.argAsCount(line, 0, m_os))
11✔
263
                        showPreviousInstructions(*args.vm_ptr, arg.value());
4✔
264
                    return false;
7✔
265
                }),
266
        };
267
    }
7✔
268

269
    std::optional<Debugger::Command> Debugger::matchCommand(const std::string& line) const
49✔
270
    {
49✔
271
        for (const Command& c : m_commands)
320✔
272
        {
273
            if (c.is_exact)
271✔
274
            {
275
                if (std::ranges::find(c.names, line) != c.names.end())
157✔
276
                    return c;
16✔
277
            }
141✔
278
            else
279
            {
280
                if (std::ranges::find_if(c.names, [&line](const std::string& name) -> bool {
228✔
281
                        return line.starts_with(name);
114✔
282
                    }) != c.names.end())
114✔
283
                    return c;
16✔
284
            }
285
        }
271✔
286

287
        return std::nullopt;
17✔
288
    }
49✔
289

290
    void Debugger::showContext(const VM& vm, const ExecutionContext& context) const
13✔
291
    {
13✔
292
        // show the line where the breakpoint hit
293
        const auto maybe_source_loc = vm.findSourceLocation(context.ip, context.pp);
13✔
294
        if (maybe_source_loc)
13✔
295
        {
296
            const auto filename = vm.m_state.m_filenames[maybe_source_loc->filename_id];
13✔
297

298
            if (Utils::fileExists(filename))
13✔
299
            {
300
                fmt::println(m_os, "");
13✔
301
                Diagnostics::makeContext(
26✔
302
                    Diagnostics::ErrorLocation {
26✔
303
                        .filename = filename,
13✔
304
                        .start = FilePos { .line = maybe_source_loc->line, .column = 0 },
13✔
305
                        .end = std::nullopt,
13✔
306
                        .maybe_content = std::nullopt },
13✔
307
                    m_os,
13✔
308
                    /* maybe_context= */ std::nullopt,
13✔
309
                    /* colorize= */ m_colorize);
13✔
310
                fmt::println(m_os, "");
13✔
311
            }
13✔
312
        }
13✔
313
    }
13✔
314

315
    void Debugger::showStack(VM& vm, const ExecutionContext& context, const std::size_t count) const
3✔
316
    {
3✔
317
        std::size_t i = 1;
3✔
318
        do
9✔
319
        {
320
            if (context.sp < i)
9✔
321
                break;
1✔
322

323
            const auto color = m_colorize ? fmt::fg(i % 2 == 0 ? fmt::color::forest_green : fmt::color::cornflower_blue) : fmt::text_style();
8✔
324
            fmt::println(
16✔
325
                m_os,
8✔
326
                "{} -> {}",
8✔
327
                fmt::styled(context.sp - i, color),
8✔
328
                fmt::styled(context.stack[context.sp - i].toString(vm, /* show_as_code= */ true), color));
8✔
329
            ++i;
8✔
330
        } while (i < count);
8✔
331

332
        if (context.sp == 0)
3✔
333
            fmt::println(m_os, "Stack is empty");
1✔
334

335
        fmt::println(m_os, "");
3✔
336
    }
3✔
337

338
    void Debugger::showLocals(VM& vm, ExecutionContext& context, const std::size_t count) const
6✔
339
    {
6✔
340
        const std::size_t limit = context.locals[context.locals.size() - 2].size();  // -2 because we created a scope for the debugger
6✔
341
        if (limit > 0 && count > 0)
6✔
342
        {
343
            fmt::println(m_os, "scope size: {}", limit);
6✔
344
            showLocals(context.locals[context.locals.size() - 2], vm, count);
6✔
345
        }
6✔
346
        else
NEW
347
            fmt::println(m_os, "Current scope is empty");
×
348

349
        fmt::println(m_os, "");
6✔
350
    }
6✔
351

NEW
352
    void Debugger::showScopes(VM& vm, ExecutionContext& context, const std::size_t count) const
×
NEW
353
    {
×
NEW
354
        if (count == 0)
×
NEW
355
            fmt::println(m_os, "Nothing to show, count must be > 0");
×
356
        else
357
        {
NEW
358
            const std::size_t scopes_count = context.locals.size() - 1;
×
NEW
359
            fmt::println(m_os, "There are {} scope{}\n", scopes_count, scopes_count == 1 ? "" : "s");
×
360

UNCOV
361
            std::size_t i = 0;
×
362

UNCOV
363
            do
×
364
            {
NEW
365
                if (scopes_count <= i)
×
UNCOV
366
                    break;
×
367

368
                // `i` is in [0, scopes_count[, so scopes_count - max(i) == 0, we can safely subtract 1
NEW
369
                const std::size_t idx = scopes_count - i - 1;
×
NEW
370
                const auto& scope = context.locals[idx];
×
371

NEW
372
                fmt::println(m_os, "Scope {}, size: {}", idx, scope.size());
×
NEW
373
                if (scope.size() > 0)
×
NEW
374
                    showLocals(scope, vm);
×
NEW
375
                fmt::println("");
×
376

UNCOV
377
                ++i;
×
UNCOV
378
            } while (i < count);
×
UNCOV
379
        }
×
380

381
        fmt::println(m_os, "");
×
UNCOV
382
    }
×
383

384
    void Debugger::showLocals(const ScopeView& scope, VM& vm, std::optional<std::size_t> limit) const
6✔
385
    {
6✔
386
        fmt::println(m_os, "index |  id |      name      |    type   | value");
6✔
387
        std::size_t i = 0;
6✔
388

389
        do
17✔
390
        {
391
            if (scope.size() <= i)
17✔
392
                break;
3✔
393

394
            auto& [id, value] = scope.atPosReverse(i);
56✔
395
            const auto color = m_colorize ? fmt::fg(i % 2 == 0 ? fmt::color::forest_green : fmt::color::cornflower_blue) : fmt::text_style();
14✔
396

397
            fmt::println(
28✔
398
                m_os,
14✔
399
                "{:>5} | {:3} | {:14} | {:>9} | {}",
14✔
400
                fmt::styled(scope.size() - i - 1, color),
14✔
401
                fmt::styled(id, color),
28✔
402
                fmt::styled(vm.m_state.m_symbols[id], color),
14✔
403
                fmt::styled(std::to_string(value.valueType()), color),
28✔
404
                fmt::styled(value.toString(vm, /* show_as_code= */ true), color));
28✔
405
            ++i;
14✔
406
        } while (!limit.has_value() || i < limit.value());
14✔
407
    }
6✔
408

409
    void Debugger::showPreviousInstructions(const VM& vm, const std::size_t count) const
4✔
410
    {
4✔
411
        BytecodeReader bcr;
4✔
412
        bcr.feed(vm.bytecode());
4✔
413

414
        const auto syms = bcr.symbols();
4✔
415
        const auto vals = bcr.values(syms);
4✔
416

417
        for (std::size_t i = 0; i < count; ++i)
22✔
418
        {
419
            if (i >= m_previous_insts.size())
18✔
420
                break;
2✔
421

422
            const uint8_t inst = (m_previous_insts[m_previous_insts.size() - 1 - i] >> 24) & 0xff;
16✔
423
            const uint8_t padding = (m_previous_insts[m_previous_insts.size() - 1 - i] >> 16) & 0xff;
16✔
424
            const uint16_t arg = m_previous_insts[m_previous_insts.size() - 1 - i] & 0xffff;
16✔
425
            bcr.printInstruction(m_os, inst, padding, arg, syms, vals, m_colorize);
16✔
426
        }
16✔
427
    }
4✔
428

429
    std::optional<std::string> Debugger::prompt(const std::size_t ip, const std::size_t pp, VM& vm, ExecutionContext& context)
27✔
430
    {
27✔
431
        std::string code;
27✔
432
        long open_parens = 0;
27✔
433
        long open_braces = 0;
27✔
434

435
        while (true)
49✔
436
        {
437
            const bool unfinished_block = open_parens != 0 || open_braces != 0;
49✔
438
            fmt::print(
147✔
439
                m_os,
49✔
440
                "dbg[{},{}]:{:0>3}{} ",
49✔
441
                fmt::format("pp:{}", fmt::styled(pp, m_colorize ? fmt::fg(fmt::color::green) : fmt::text_style())),
49✔
442
                fmt::format("ip:{}", fmt::styled(ip / 4, m_colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style())),
49✔
443
                m_line_count,
49✔
444
                unfinished_block ? ":" : ">");
49✔
445

446
            std::string line;
49✔
447
            if (m_prompt_stream)
49✔
448
            {
449
                std::getline(*m_prompt_stream, line);
49✔
450
                fmt::println(m_os, "{}", line);  // because nothing is printed otherwise, and prompts get printed on the same line
49✔
451
            }
49✔
452
            else
453
                std::getline(std::cin, line);
×
454

455
            Utils::trimWhitespace(line);
49✔
456

457
            if (line.empty() && !unfinished_block)
49✔
458
            {
459
                fmt::println(m_os, "dbg: continue");
×
460
                return std::nullopt;
×
461
            }
462

463
            if (const auto& maybe_cmd = matchCommand(line))
98✔
464
            {
465
                const Command cmd = maybe_cmd.value();
32✔
466
                if (cmd.action(line, CommandArgs { .vm_ptr = &vm, .ctx_ptr = &context, .ip = ip, .pp = pp, .me = cmd }))
32✔
467
                    return std::nullopt;
14✔
468
            }
32✔
469
            else
470
            {
471
                code += line + "\n";
17✔
472

473
                open_parens += Utils::countOpenEnclosures(line, '(', ')');
17✔
474
                open_braces += Utils::countOpenEnclosures(line, '{', '}');
17✔
475

476
                ++m_line_count;
17✔
477
                if (open_braces == 0 && open_parens == 0)
17✔
478
                    break;
13✔
479
            }
480
        }
49✔
481

482
        return code;
13✔
483
    }
27✔
484

485
    std::optional<CompiledPrompt> Debugger::compile(const std::string& code, const std::size_t start_page_at_offset) const
13✔
486
    {
13✔
487
        Welder welder(0, m_libenv, DefaultFeatures);
13✔
488
        if (!welder.computeASTFromStringWithKnownSymbols(code, m_symbols))
13✔
489
            return std::nullopt;
×
490
        if (!welder.generateBytecodeUsingTables(m_symbols, m_constants, start_page_at_offset))
13✔
491
            return std::nullopt;
×
492

493
        BytecodeReader bcr;
13✔
494
        bcr.feed(welder.bytecode());
13✔
495
        const auto syms = bcr.symbols();
13✔
496
        const auto vals = bcr.values(syms);
13✔
497
        const auto files = bcr.filenames(vals);
13✔
498
        const auto inst_locs = bcr.instLocations(files);
13✔
499
        const auto [pages, _] = bcr.code(inst_locs);
13✔
500

501
        return std::optional(CompiledPrompt(pages, syms.symbols, vals.values));
26✔
502
    }
13✔
503
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc