Skip to content

Commit 31f78d0

Browse files
committed
feat(debugger): create a minimalistic shell for the debugger
1 parent e1fd022 commit 31f78d0

File tree

10 files changed

+177
-83
lines changed

10 files changed

+177
-83
lines changed

include/Ark/Utils/Utils.hpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,21 @@ namespace Ark::Utils
9999
* @return std::size_t
100100
*/
101101
ARK_API std::size_t levenshteinDistance(const std::string& str1, const std::string& str2);
102+
103+
/**
104+
* @brief Count the open enclosure and its counterpart: (), {}, []
105+
* @param line data to operate on
106+
* @param open the open char: (, { or [
107+
* @param close the closing char: ), } or ]
108+
* @return positive if there are more open enclosures than closed. 0 when both are equal, negative otherwise
109+
*/
110+
ARK_API long countOpenEnclosures(const std::string& line, char open, char close);
111+
112+
/**
113+
* @brief Remove whitespaces at the start and end of a string
114+
* @param line string modified in place
115+
*/
116+
ARK_API void trimWhitespace(std::string& line);
102117
}
103118

104119
#endif

include/Ark/VM/Debugger.hpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,19 +72,28 @@ namespace Ark::internal
7272
*/
7373
void run(VM& vm, ExecutionContext& context);
7474

75-
inline bool isRunning() const noexcept
75+
[[nodiscard]] inline bool isRunning() const noexcept
7676
{
7777
return m_running;
7878
}
7979

80+
[[nodiscard]] inline bool shouldQuitVM() const noexcept
81+
{
82+
return m_quit_vm;
83+
}
84+
8085
private:
8186
std::vector<std::unique_ptr<SavedState>> m_states;
8287
std::vector<std::filesystem::path> m_libenv;
8388
std::vector<std::string> m_symbols;
8489
std::vector<Value> m_constants;
8590
bool m_running;
91+
bool m_quit_vm;
8692

8793
std::string m_code; ///< Code added while inside the debugger
94+
std::size_t m_line_count = 0;
95+
96+
std::optional<std::string> prompt();
8897

8998
/**
9099
* @brief Take care of compiling new code using the existing data tables

include/CLI/REPL/Utils.hpp

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,6 @@
1818

1919
namespace Ark::internal
2020
{
21-
/**
22-
* @brief Count the open enclosure and its counterpart: (), {}, []
23-
* @param line data to operate on
24-
* @param open the open char: (, { or [
25-
* @param close the closing char: ), } or ]
26-
* @return positive if there are more open enclosures than closed. 0 when both are equal, negative otherwise
27-
*/
28-
long countOpenEnclosures(const std::string& line, char open, char close);
29-
30-
/**
31-
* @brief Remove whitespaces at the start and end of a string
32-
* @param line string modified in place
33-
*/
34-
void trimWhitespace(std::string& line);
35-
3621
/**
3722
* @brief Compute a list of all the language keywords and builtins
3823
*

src/arkreactor/Utils/Utils.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#include <Ark/Utils/Utils.hpp>
22

3+
#include <ranges>
4+
35
namespace Ark::Utils
46
{
57
std::size_t levenshteinDistance(const std::string& str1, const std::string& str2)
@@ -29,4 +31,19 @@ namespace Ark::Utils
2931

3032
return edit_distances[str1_len][str2_len];
3133
}
34+
35+
long countOpenEnclosures(const std::string& line, const char open, const char close)
36+
{
37+
return std::ranges::count(line, open) - std::ranges::count(line, close);
38+
}
39+
40+
void trimWhitespace(std::string& line)
41+
{
42+
const std::size_t string_begin = line.find_first_not_of(" \t");
43+
if (std::string::npos != string_begin)
44+
{
45+
const std::size_t string_end = line.find_last_not_of(" \t");
46+
line = line.substr(string_begin, string_end - string_begin + 1);
47+
}
48+
}
3249
}

src/arkreactor/VM/Debugger.cpp

Lines changed: 99 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55

66
#include <Ark/State.hpp>
77
#include <Ark/VM/VM.hpp>
8+
#include <Ark/Utils/Files.hpp>
89
#include <Ark/Compiler/Welder.hpp>
910
#include <Ark/Compiler/BytecodeReader.hpp>
11+
#include <Ark/Error/Diagnostics.hpp>
1012

1113
namespace Ark::internal
1214
{
1315
Debugger::Debugger(const ExecutionContext& context, const std::vector<std::filesystem::path>& libenv, const std::vector<std::string>& symbols, const std::vector<Value>& constants) :
14-
m_libenv(libenv), m_symbols(symbols), m_constants(constants), m_running(false)
16+
m_libenv(libenv), m_symbols(symbols), m_constants(constants), m_running(false), m_quit_vm(false)
1517
{
1618
saveState(context);
1719
}
@@ -46,31 +48,59 @@ namespace Ark::internal
4648
m_running = true;
4749
const bool is_vm_running = vm.m_running;
4850

49-
// TODO: create a shell
50-
fmt::print("> ");
51-
std::string line;
52-
std::getline(std::cin, line);
53-
54-
if (const auto pages = compile(m_code + line, vm.m_state.m_pages.size()); pages.has_value())
51+
// show the line where the breakpoint hit
52+
const auto maybe_source_loc = vm.findSourceLocation(context.ip, context.pp);
53+
if (maybe_source_loc)
5554
{
56-
context.ip = 0;
57-
context.pp = vm.m_state.m_pages.size();
58-
// create dedicated scope, so that we won't be overwriting existing variables
59-
context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd());
55+
const auto filename = vm.m_state.m_filenames[maybe_source_loc->filename_id];
56+
57+
if (Utils::fileExists(filename))
58+
{
59+
fmt::println("");
60+
Diagnostics::makeContext(
61+
Diagnostics::ErrorLocation {
62+
.filename = filename,
63+
.start = FilePos { .line = maybe_source_loc->line, .column = 0 },
64+
.end = std::nullopt },
65+
std::cout,
66+
/* maybe_context= */ std::nullopt,
67+
/* colorize= */ true);
68+
fmt::println("");
69+
}
70+
}
6071

61-
vm.m_state.extendBytecode(pages.value(), m_symbols, m_constants);
72+
while (true)
73+
{
74+
std::optional<std::string> maybe_input = prompt();
6275

63-
if (vm.safeRun(context) == 0)
76+
if (maybe_input)
6477
{
65-
// executing code worked
66-
m_code += line;
78+
const std::string& line = maybe_input.value();
6779

68-
const Value* maybe_value = vm.peekAndResolveAsPtr(context);
69-
if (maybe_value != nullptr && maybe_value->valueType() != ValueType::Undefined && maybe_value->valueType() != ValueType::InstPtr)
70-
fmt::println("{}", fmt::styled(maybe_value->toString(vm), fmt::fg(fmt::color::chocolate)));
71-
}
80+
if (const auto pages = compile(m_code + line, vm.m_state.m_pages.size()); pages.has_value())
81+
{
82+
context.ip = 0;
83+
context.pp = vm.m_state.m_pages.size();
84+
// create dedicated scope, so that we won't be overwriting existing variables
85+
context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd());
86+
87+
vm.m_state.extendBytecode(pages.value(), m_symbols, m_constants);
7288

73-
context.locals.pop_back();
89+
if (vm.safeRun(context) == 0)
90+
{
91+
// executing code worked
92+
m_code += line;
93+
94+
const Value* maybe_value = vm.peekAndResolveAsPtr(context);
95+
if (maybe_value != nullptr && maybe_value->valueType() != ValueType::Undefined && maybe_value->valueType() != ValueType::InstPtr)
96+
fmt::println("{}", fmt::styled(maybe_value->toString(vm), fmt::fg(fmt::color::chocolate)));
97+
}
98+
99+
context.locals.pop_back();
100+
}
101+
}
102+
else
103+
break;
74104
}
75105

76106
m_running = false;
@@ -80,6 +110,55 @@ namespace Ark::internal
80110
vm.m_running = is_vm_running;
81111
}
82112

113+
std::optional<std::string> Debugger::prompt()
114+
{
115+
std::string code;
116+
long open_parens = 0;
117+
long open_braces = 0;
118+
119+
while (true)
120+
{
121+
const bool unfinished_block = open_parens != 0 || open_braces != 0;
122+
fmt::print("dbg:{:0>3}{} ", m_line_count, unfinished_block ? ":" : ">");
123+
std::string line;
124+
std::getline(std::cin, line);
125+
126+
Utils::trimWhitespace(line);
127+
128+
if (line == "c" || line == "continue" || line.empty())
129+
{
130+
fmt::println("dbg: continue");
131+
return std::nullopt;
132+
}
133+
else if (line == "q" || line == "quit")
134+
{
135+
fmt::println("dbg: stop");
136+
m_quit_vm = true;
137+
return std::nullopt;
138+
}
139+
else if (line == "help")
140+
{
141+
fmt::println("Available commands:");
142+
fmt::println(" help -- display this message");
143+
fmt::println(" c, continue -- resume execution");
144+
fmt::println(" q, quit -- quit the debugger, stopping the script execution");
145+
}
146+
else
147+
{
148+
code += line;
149+
150+
open_parens += Utils::countOpenEnclosures(line, '(', ')');
151+
open_braces += Utils::countOpenEnclosures(line, '{', '}');
152+
153+
++m_line_count;
154+
if (open_braces == 0 && open_parens == 0)
155+
break;
156+
}
157+
}
158+
159+
return code;
160+
}
161+
83162
std::optional<std::vector<bytecode_t>> Debugger::compile(const std::string& code, const std::size_t start_page_at_offset)
84163
{
85164
Welder welder(0, m_libenv, DefaultFeatures);

src/arkreactor/VM/VM.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,6 +1197,9 @@ namespace Ark
11971197
initDebugger(context);
11981198
m_debugger->run(*this, context);
11991199
m_debugger->resetContextToSavedState(context);
1200+
1201+
if (m_debugger->shouldQuitVM())
1202+
GOTO_HALT();
12001203
}
12011204
}
12021205
DISPATCH();

src/arkscript/REPL/Repl.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <Ark/VM/DefaultValues.hpp>
99
#include <Ark/TypeChecker.hpp>
1010
#include <Ark/Utils/Files.hpp>
11+
#include <Ark/Utils/Utils.hpp>
1112

1213
#include <CLI/REPL/Repl.hpp>
1314
#include <CLI/REPL/Utils.hpp>
@@ -166,7 +167,7 @@ namespace Ark
166167

167168
// line history
168169
m_repl.history_add(line);
169-
trimWhitespace(line);
170+
Utils::trimWhitespace(line);
170171

171172
// specific commands handling
172173
if (line == "quit" || buf == nullptr)
@@ -233,8 +234,8 @@ namespace Ark
233234
if (maybe_line.has_value() && !maybe_line.value().empty())
234235
{
235236
code_block += maybe_line.value() + "\n";
236-
open_parentheses += countOpenEnclosures(maybe_line.value(), '(', ')');
237-
open_braces += countOpenEnclosures(maybe_line.value(), '{', '}');
237+
open_parentheses += Utils::countOpenEnclosures(maybe_line.value(), '(', ')');
238+
open_braces += Utils::countOpenEnclosures(maybe_line.value(), '{', '}');
238239

239240
// lines number incrementation
240241
++m_line_count;

src/arkscript/REPL/Utils.cpp

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,6 @@
1010

1111
namespace Ark::internal
1212
{
13-
long countOpenEnclosures(const std::string& line, const char open, const char close)
14-
{
15-
return std::ranges::count(line, open) - std::ranges::count(line, close);
16-
}
17-
18-
void trimWhitespace(std::string& line)
19-
{
20-
const std::size_t string_begin = line.find_first_not_of(" \t");
21-
if (std::string::npos != string_begin)
22-
{
23-
const std::size_t string_end = line.find_last_not_of(" \t");
24-
line = line.substr(string_begin, string_end - string_begin + 1);
25-
}
26-
}
27-
2813
std::vector<std::string> getAllKeywords()
2914
{
3015
std::vector<std::string> output;

tests/unittests/Suites/ReplSuite.cpp

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,6 @@ using namespace boost;
99
ut::suite<"Repl"> repl_suite = [] {
1010
using namespace ut;
1111

12-
"countOpenEnclosures"_test = [] {
13-
expect(that % Ark::internal::countOpenEnclosures("", '(', ')') == 0);
14-
expect(that % Ark::internal::countOpenEnclosures("(", '(', ')') == 1);
15-
expect(that % Ark::internal::countOpenEnclosures(")", '(', ')') == -1);
16-
expect(that % Ark::internal::countOpenEnclosures("{}", '(', ')') == 0);
17-
expect(that % Ark::internal::countOpenEnclosures("{)(()}", '(', ')') == 0);
18-
expect(that % Ark::internal::countOpenEnclosures("{)(()}", '{', '}') == 0);
19-
};
20-
21-
"trimWhitespace"_test = [] {
22-
const std::string expected = "hello world";
23-
std::string line = expected;
24-
25-
Ark::internal::trimWhitespace(line);
26-
expect(that % line == expected);
27-
28-
line = " \thello world";
29-
Ark::internal::trimWhitespace(line);
30-
expect(that % line == expected);
31-
32-
line = "hello world \t";
33-
Ark::internal::trimWhitespace(line);
34-
expect(that % line == expected);
35-
36-
line = " \thello world \t";
37-
Ark::internal::trimWhitespace(line);
38-
expect(that % line == expected);
39-
};
40-
4112
const auto kws = Ark::internal::getAllKeywords();
4213
const auto colors = Ark::internal::getColorPerKeyword();
4314
const auto append_color = std::ranges::find_if(colors, [](const auto& pair) {

tests/unittests/Suites/ToolsSuite.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,35 @@ ut::suite<"Tools"> tools_suite = [] {
3939
expect(that % Ark::Utils::levenshteinDistance("arkscript", "OrC") == 8_z);
4040
};
4141

42+
"countOpenEnclosures"_test = [] {
43+
expect(that % Ark::Utils::countOpenEnclosures("", '(', ')') == 0);
44+
expect(that % Ark::Utils::countOpenEnclosures("(", '(', ')') == 1);
45+
expect(that % Ark::Utils::countOpenEnclosures(")", '(', ')') == -1);
46+
expect(that % Ark::Utils::countOpenEnclosures("{}", '(', ')') == 0);
47+
expect(that % Ark::Utils::countOpenEnclosures("{)(()}", '(', ')') == 0);
48+
expect(that % Ark::Utils::countOpenEnclosures("{)(()}", '{', '}') == 0);
49+
};
50+
51+
"trimWhitespace"_test = [] {
52+
const std::string expected = "hello world";
53+
std::string line = expected;
54+
55+
Ark::Utils::trimWhitespace(line);
56+
expect(that % line == expected);
57+
58+
line = " \thello world";
59+
Ark::Utils::trimWhitespace(line);
60+
expect(that % line == expected);
61+
62+
line = "hello world \t";
63+
Ark::Utils::trimWhitespace(line);
64+
expect(that % line == expected);
65+
66+
line = " \thello world \t";
67+
Ark::Utils::trimWhitespace(line);
68+
expect(that % line == expected);
69+
};
70+
4271
"Utils::fileExists"_test = [] {
4372
expect(Ark::Utils::fileExists(".gitignore"));
4473
expect(!Ark::Utils::fileExists(""));

0 commit comments

Comments
 (0)