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
1113namespace 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);
0 commit comments