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

ArkScript-lang / Ark / 21145063979

19 Jan 2026 04:36PM UTC coverage: 92.799% (+0.06%) from 92.743%
21145063979

Pull #628

github

web-flow
Merge b204126c3 into ada0e0686
Pull Request #628: Feat/debugger

317 of 356 new or added lines in 11 files covered. (89.04%)

1 existing line in 1 file now uncovered.

8750 of 9429 relevant lines covered (92.8%)

273976.06 hits per line

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

94.7
/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/ostream.h>
6

7
#include <Ark/State.hpp>
8
#include <Ark/VM/VM.hpp>
9
#include <Ark/Utils/Files.hpp>
10
#include <Ark/Compiler/Welder.hpp>
11
#include <Ark/Compiler/BytecodeReader.hpp>
12
#include <Ark/Error/Diagnostics.hpp>
13

14
namespace Ark::internal
15
{
NEW
16
    Debugger::Debugger(const ExecutionContext& context, const std::vector<std::filesystem::path>& libenv, const std::vector<std::string>& symbols, const std::vector<Value>& constants) :
×
NEW
17
        m_libenv(libenv), m_symbols(symbols), m_constants(constants), m_os(std::cout), m_colorize(true)
×
NEW
18
    {
×
NEW
19
        saveState(context);
×
NEW
20
    }
×
21

22
    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) :
8✔
23
        m_libenv(libenv), m_symbols(symbols), m_constants(constants), m_os(os), m_colorize(false)
4✔
24
    {
4✔
25
        m_prompt_stream = std::make_unique<std::ifstream>(path_to_prompt_file);
4✔
26
    }
4✔
27

28
    void Debugger::saveState(const ExecutionContext& context)
9✔
29
    {
9✔
30
        m_states.emplace_back(
18✔
31
            std::make_unique<SavedState>(
18✔
32
                context.ip,
9✔
33
                context.pp,
9✔
34
                context.sp,
9✔
35
                context.fc,
9✔
36
                context.locals,
9✔
37
                context.stacked_closure_scopes));
9✔
38
    }
9✔
39

40
    void Debugger::resetContextToSavedState(ExecutionContext& context)
9✔
41
    {
9✔
42
        const auto& [ip, pp, sp, fc, locals, closure_scopes] = *m_states.back();
27✔
43
        context.locals = locals;
9✔
44
        context.stacked_closure_scopes = closure_scopes;
9✔
45
        context.ip = ip;
9✔
46
        context.pp = pp;
9✔
47
        context.sp = sp;
9✔
48
        context.fc = fc;
9✔
49

50
        m_states.pop_back();
9✔
51
    }
9✔
52

53
    void Debugger::run(VM& vm, ExecutionContext& context)
9✔
54
    {
9✔
55
        showContext(vm, context);
9✔
56

57
        m_running = true;
9✔
58
        const bool is_vm_running = vm.m_running;
9✔
59
        const std::size_t ip_at_breakpoint = context.ip,
9✔
60
                          pp_at_breakpoint = context.pp;
9✔
61
        // create dedicated scope, so that we won't be overwriting existing variables
62
        context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd());
9✔
63
        std::size_t last_ip = 0;
9✔
64

65
        while (true)
20✔
66
        {
67
            std::optional<std::string> maybe_input = prompt(ip_at_breakpoint, pp_at_breakpoint);
20✔
68

69
            if (maybe_input)
20✔
70
            {
71
                const std::string& line = maybe_input.value();
11✔
72

73
                if (const auto compiled = compile(m_code + line, vm.m_state.m_pages.size()); compiled.has_value())
22✔
74
                {
75
                    context.ip = last_ip;
11✔
76
                    context.pp = vm.m_state.m_pages.size();
11✔
77

78
                    vm.m_state.extendBytecode(compiled->pages, compiled->symbols, compiled->constants);
11✔
79

80
                    if (vm.safeRun(context) == 0)
11✔
81
                    {
82
                        // executing code worked
83
                        m_code += line + "\n";
11✔
84
                        // place ip to end of bytecode instruction (HALT)
85
                        last_ip = context.ip - 4;
11✔
86

87
                        const Value* maybe_value = vm.peekAndResolveAsPtr(context);
11✔
88
                        if (maybe_value != nullptr && maybe_value->valueType() != ValueType::Undefined && maybe_value->valueType() != ValueType::InstPtr)
11✔
89
                            fmt::println(
16✔
90
                                m_os,
8✔
91
                                "{}",
8✔
92
                                fmt::styled(
16✔
93
                                    maybe_value->toString(vm),
8✔
94
                                    m_colorize ? fmt::fg(fmt::color::chocolate) : fmt::text_style()));
8✔
95
                    }
11✔
96
                }
11✔
97
            }
11✔
98
            else
99
                break;
9✔
100
        }
20✔
101

102
        context.locals.pop_back();
9✔
103

104
        // we do not want to retain code from the past executions
105
        m_code.clear();
9✔
106
        m_line_count = 0;
9✔
107

108
        // we hit a HALT instruction that set 'running' to false, ignore that if we were still running!
4✔
109
        vm.m_running = is_vm_running;
13✔
110
        m_running = false;
9✔
111
    }
9✔
112

113
    void Debugger::showContext(const VM& vm, const ExecutionContext& context) const
9✔
114
    {
9✔
115
        // show the line where the breakpoint hit
4✔
116
        const auto maybe_source_loc = vm.findSourceLocation(context.ip, context.pp);
9✔
117
        if (maybe_source_loc)
9✔
118
        {
119
            const auto filename = vm.m_state.m_filenames[maybe_source_loc->filename_id];
9✔
120

121
            if (Utils::fileExists(filename))
9✔
122
            {
123
                fmt::println(m_os, "");
9✔
124
                Diagnostics::makeContext(
18✔
125
                    Diagnostics::ErrorLocation {
18✔
126
                        .filename = filename,
9✔
127
                        .start = FilePos { .line = maybe_source_loc->line, .column = 0 },
9✔
128
                        .end = std::nullopt },
9✔
129
                    m_os,
9✔
130
                    /* maybe_context= */ std::nullopt,
9✔
131
                    /* colorize= */ m_colorize);
9✔
132
                fmt::println(m_os, "");
9✔
133
            }
9✔
134
        }
9✔
135
    }
9✔
136

137
    std::optional<std::string> Debugger::prompt(const std::size_t ip, const std::size_t pp)
20✔
138
    {
20✔
139
        std::string code;
20✔
140
        long open_parens = 0;
20✔
141
        long open_braces = 0;
20✔
142

143
        while (true)
21✔
144
        {
145
            const bool unfinished_block = open_parens != 0 || open_braces != 0;
21✔
146
            fmt::print(
63✔
147
                m_os,
21✔
148
                "dbg[{},{}]:{:0>3}{} ",
21✔
149
                fmt::format("pp:{}", fmt::styled(pp, m_colorize ? fmt::fg(fmt::color::green) : fmt::text_style())),
21✔
150
                fmt::format("ip:{}", fmt::styled(ip, m_colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style())),
21✔
151
                m_line_count,
21✔
152
                unfinished_block ? ":" : ">");
21✔
153

154
            std::string line;
21✔
155
            if (m_prompt_stream)
21✔
156
            {
157
                std::getline(*m_prompt_stream, line);
21✔
158
                fmt::println(m_os, "{}", line);  // because nothing is printed otherwise, and prompts get printed on the same line
21✔
159
            }
21✔
160
            else
NEW
161
                std::getline(std::cin, line);
×
162

163
            Utils::trimWhitespace(line);
21✔
164

165
            if (line == "c" || line == "continue" || line.empty())
21✔
166
            {
167
                fmt::println(m_os, "dbg: continue");
8✔
168
                return std::nullopt;
8✔
169
            }
170
            else if (line == "q" || line == "quit")
13✔
171
            {
172
                fmt::println(m_os, "dbg: stop");
1✔
173
                m_quit_vm = true;
1✔
174
                return std::nullopt;
1✔
175
            }
176
            else if (line == "help")
12✔
177
            {
178
                fmt::println(m_os, "Available commands:");
1✔
179
                fmt::println(m_os, "  help -- display this message");
1✔
180
                fmt::println(m_os, "  c, continue -- resume execution");
1✔
181
                fmt::println(m_os, "  q, quit -- quit the debugger, stopping the script execution");
1✔
182
            }
1✔
183
            else
184
            {
185
                code += line;
11✔
186

187
                open_parens += Utils::countOpenEnclosures(line, '(', ')');
11✔
188
                open_braces += Utils::countOpenEnclosures(line, '{', '}');
11✔
189

190
                ++m_line_count;
11✔
191
                if (open_braces == 0 && open_parens == 0)
11✔
192
                    break;
11✔
193
            }
194
        }
21✔
195

196
        return code;
11✔
197
    }
20✔
198

199
    std::optional<CompiledPrompt> Debugger::compile(const std::string& code, const std::size_t start_page_at_offset) const
11✔
200
    {
11✔
201
        Welder welder(0, m_libenv, DefaultFeatures);
11✔
202
        if (!welder.computeASTFromStringWithKnownSymbols(code, m_symbols))
11✔
NEW
203
            return std::nullopt;
×
204
        if (!welder.generateBytecodeUsingTables(m_symbols, m_constants, start_page_at_offset))
11✔
NEW
205
            return std::nullopt;
×
206

207
        BytecodeReader bcr;
11✔
208
        bcr.feed(welder.bytecode());
11✔
209
        const auto syms = bcr.symbols();
11✔
210
        const auto vals = bcr.values(syms);
11✔
211
        const auto files = bcr.filenames(vals);
11✔
212
        const auto inst_locs = bcr.instLocations(files);
11✔
213
        const auto [pages, _] = bcr.code(inst_locs);
11✔
214

215
        return std::optional(CompiledPrompt(pages, syms.symbols, vals.values));
22✔
216
    }
11✔
217
}
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