33#include < fmt/core.h>
44#include < fmt/color.h>
55#include < fmt/ostream.h>
6+ #include < chrono>
7+ #include < thread>
8+ #include < charconv>
69
710#include < Ark/State.hpp>
811#include < Ark/VM/VM.hpp>
@@ -59,6 +62,8 @@ namespace Ark::internal
5962
6063 void Debugger::run (VM& vm, ExecutionContext& context, const bool from_breakpoint)
6164 {
65+ using namespace std ::chrono_literals;
66+
6267 if (from_breakpoint)
6368 showContext (vm, context);
6469
@@ -72,7 +77,7 @@ namespace Ark::internal
7277
7378 while (true )
7479 {
75- std::optional<std::string> maybe_input = prompt (ip_at_breakpoint, pp_at_breakpoint);
80+ std::optional<std::string> maybe_input = prompt (ip_at_breakpoint, pp_at_breakpoint, vm, context );
7681
7782 if (maybe_input)
7883 {
@@ -102,6 +107,8 @@ namespace Ark::internal
102107 m_colorize ? fmt::fg (fmt::color::chocolate) : fmt::text_style ()));
103108 }
104109 }
110+ else
111+ 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
105112 }
106113 else
107114 break ;
@@ -143,7 +150,101 @@ namespace Ark::internal
143150 }
144151 }
145152
146- std::optional<std::string> Debugger::prompt (const std::size_t ip, const std::size_t pp)
153+ void Debugger::showStack (VM& vm, const ExecutionContext& context, const std::size_t count) const
154+ {
155+ std::size_t i = 1 ;
156+ do
157+ {
158+ if (context.sp < i)
159+ break ;
160+
161+ const auto color = m_colorize ? fmt::fg (i % 2 == 0 ? fmt::color::forest_green : fmt::color::cornflower_blue) : fmt::text_style ();
162+ fmt::println (
163+ m_os,
164+ " {} -> {}" ,
165+ fmt::styled (context.sp - i, color),
166+ fmt::styled (context.stack [context.sp - i].toString (vm, /* show_as_code= */ true ), color));
167+ ++i;
168+ } while (i < count);
169+
170+ if (context.sp == 0 )
171+ fmt::println (m_os, " Stack is empty" );
172+
173+ fmt::println (m_os, " " );
174+ }
175+
176+ void Debugger::showLocals (VM& vm, ExecutionContext& context, const std::size_t count) const
177+ {
178+ const std::size_t limit = context.locals [context.locals .size () - 2 ].size (); // -2 because we created a scope for the debugger
179+ if (limit > 0 && count > 0 )
180+ {
181+ fmt::println (m_os, " scope size: {}" , limit);
182+ fmt::println (m_os, " index | id | type | value" );
183+ std::size_t i = 0 ;
184+
185+ do
186+ {
187+ if (limit <= i)
188+ break ;
189+
190+ auto & [id, value] = context.locals [context.locals .size () - 2 ].atPosReverse (i);
191+ const auto color = m_colorize ? fmt::fg (i % 2 == 0 ? fmt::color::forest_green : fmt::color::cornflower_blue) : fmt::text_style ();
192+
193+ fmt::println (
194+ m_os,
195+ " {:>5} | {:3} | {:>9} | {}" ,
196+ fmt::styled (limit - i - 1 , color),
197+ fmt::styled (id, color),
198+ fmt::styled (std::to_string (value.valueType ()), color),
199+ fmt::styled (value.toString (vm, /* show_as_code= */ true ), color));
200+ ++i;
201+ } while (i < count);
202+ }
203+ else
204+ fmt::println (m_os, " Current scope is empty" );
205+
206+ fmt::println (m_os, " " );
207+ }
208+
209+ std::optional<std::string> Debugger::getCommandArg (const std::string& command, const std::string& line)
210+ {
211+ std::string arg = line.substr (command.size ());
212+ Utils::trimWhitespace (arg);
213+
214+ if (arg.empty ())
215+ return std::nullopt ;
216+ return arg;
217+ }
218+
219+ std::optional<std::size_t > Debugger::parseStringAsInt (const std::string& str)
220+ {
221+ std::size_t result = 0 ;
222+ auto [ptr, ec] = std::from_chars (str.data (), str.data () + str.size (), result);
223+
224+ if (ec == std::errc ())
225+ return result;
226+ return std::nullopt ;
227+ }
228+
229+ std::optional<std::size_t > Debugger::getArgAndParseOrError (const std::string& command, const std::string& line, const std::size_t default_value) const
230+ {
231+ const auto maybe_arg = getCommandArg (command, line);
232+ std::size_t count = default_value;
233+ if (maybe_arg)
234+ {
235+ if (const auto maybe_int = parseStringAsInt (maybe_arg.value ()))
236+ count = maybe_int.value ();
237+ else
238+ {
239+ fmt::println (m_os, " Couldn't parse argument as an integer" );
240+ return std::nullopt ;
241+ }
242+ }
243+
244+ return count;
245+ }
246+
247+ std::optional<std::string> Debugger::prompt (const std::size_t ip, const std::size_t pp, VM& vm, ExecutionContext& context)
147248 {
148249 std::string code;
149250 long open_parens = 0 ;
@@ -182,12 +283,28 @@ namespace Ark::internal
182283 m_quit_vm = true ;
183284 return std::nullopt ;
184285 }
286+ else if (line.starts_with (" stack" ))
287+ {
288+ if (auto arg = getArgAndParseOrError (" stack" , line, /* default_value= */ 5 ))
289+ showStack (vm, context, arg.value ());
290+ else
291+ return std::nullopt ;
292+ }
293+ else if (line.starts_with (" locals" ))
294+ {
295+ if (auto arg = getArgAndParseOrError (" locals" , line, /* default_value= */ 5 ))
296+ showLocals (vm, context, arg.value ());
297+ else
298+ return std::nullopt ;
299+ }
185300 else if (line == " help" )
186301 {
187302 fmt::println (m_os, " Available commands:" );
188303 fmt::println (m_os, " help -- display this message" );
189304 fmt::println (m_os, " c, continue -- resume execution" );
190305 fmt::println (m_os, " q, quit -- quit the debugger, stopping the script execution" );
306+ fmt::println (m_os, " stack <n=5> -- show the last n values on the stack" );
307+ fmt::println (m_os, " locals <n=5> -- show the last n values on the locals' stack" );
191308 }
192309 else
193310 {
0 commit comments