6#include <fmt/ostream.h>
20 Debugger::Debugger(
const ExecutionContext& context,
const std::vector<std::filesystem::path>& libenv,
const std::vector<std::string>& symbols,
const std::vector<Value>& constants) :
23 m_constants(constants),
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) :
34 m_constants(constants),
37 m_prompt_stream(
std::make_unique<
std::ifstream>(path_to_prompt_file))
45 std::make_unique<SavedState>(
56 const auto& [ip, pp, sp, fc, locals, closure_scopes] = *
m_states.back();
69 using namespace std::chrono_literals;
76 const std::size_t ip_at_breakpoint = context.
ip,
77 pp_at_breakpoint = context.
pp;
80 std::size_t last_ip = 0;
84 std::optional<std::string> maybe_input =
prompt(ip_at_breakpoint, pp_at_breakpoint, vm, context);
88 const std::string& line = maybe_input.value();
102 last_ip = context.
ip - 4;
105 if (maybe_value !=
nullptr &&
114 m_colorize ? fmt::fg(fmt::color::chocolate) : fmt::text_style()));
118 std::this_thread::sleep_for(50ms);
124 context.
locals.pop_back();
140 m_previous_insts.emplace_back(
TracedInstruction { .inst = inst, .padding = padding, .arg = arg, .ip = ip, .pp = pp });
141 if (m_previous_insts.size() > 4096)
142 m_previous_insts.pop_front();
151 for (
const auto& arg : std::ranges::views::drop(split, 1))
154 args_values[i].second = arg;
157 fmt::println(os,
"Too many arguments provided to {}, expected {}, got {}", split.front(),
args.size(), split.size() - 1);
169 const std::optional<Args_t> maybe_parsed = getArgs(line, os);
170 if (maybe_parsed && idx < maybe_parsed->size())
172 const std::string str = maybe_parsed.value()[idx].second;
173 std::size_t result = 0;
174 auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result);
176 if (ec == std::errc())
179 fmt::println(os,
"Couldn't parse argument as an unsigned integer");
190 "display this message",
192 fmt::println(
m_os,
"Available commands:");
196 fmt::println(
m_os,
" {} -- {}", fmt::join(cmd.names,
", "), cmd.description);
199 const auto v = std::views::transform(cmd.args, [](
const auto& p) {
200 return fmt::format(
"{}={}", p.first, p.second);
202 fmt::println(
m_os,
" {} <{}> -- {}", fmt::join(cmd.names,
", "), fmt::join(v,
", "), cmd.description);
211 fmt::println(
m_os,
"dbg: continue");
216 "quit the debugger, stopping the script execution",
218 fmt::println(
m_os,
"dbg: stop");
225 "show the last n values on the stack",
226 [
this](
const std::string& line,
const CommandArgs& args) {
227 if (
const auto arg = args.me.argAsCount(line, 0,
m_os))
228 showStack(*args.vm_ptr, *args.ctx_ptr, arg.value());
234 "show the last n values on the locals' stack",
235 [
this](
const std::string& line,
const CommandArgs& args) {
236 if (
const auto arg = args.me.argAsCount(line, 0,
m_os))
237 showLocals(*args.vm_ptr, *args.ctx_ptr, arg.value());
243 "show the last n scopes",
244 [
this](
const std::string& line,
const CommandArgs& args) {
245 if (
const auto arg = args.me.argAsCount(line, 0,
m_os))
246 showScopes(*args.vm_ptr, *args.ctx_ptr, arg.value());
251 "show the values of the VM pointers",
252 [
this](
const std::string&,
const CommandArgs& args) {
255 "IP: {} - PP: {} - SP: {}",
256 fmt::styled(args.
ip / 4,
m_colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
257 fmt::styled(args.
pp,
m_colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
258 fmt::styled(args.
ctx_ptr->
sp,
m_colorize ? fmt::fg(fmt::color::yellow) : fmt::text_style()));
264 "show the last n executed instructions",
265 [
this](
const std::string& line,
const CommandArgs& args) {
279 if (std::ranges::find(c.names, line) != c.names.end())
284 if (std::ranges::find_if(c.names, [&line](
const std::string& name) ->
bool {
285 return line.starts_with(name);
298 if (maybe_source_loc)
304 fmt::println(
m_os,
"");
307 .filename = filename,
308 .start =
FilePos { .line = maybe_source_loc->line, .column = 0 },
310 .maybe_content = std::nullopt },
314 fmt::println(
m_os,
"");
327 const auto color =
m_colorize ? fmt::fg(i % 2 == 0 ? fmt::color::forest_green : fmt::color::cornflower_blue) : fmt::text_style();
331 fmt::styled(context.
sp - i, color),
332 fmt::styled(context.
stack[context.
sp - i].toString(vm,
true), color));
337 fmt::println(
m_os,
"Stack is empty");
339 fmt::println(
m_os,
"");
344 const std::size_t limit = context.
locals[context.
locals.size() - 2].size();
345 if (limit > 0 && count > 0)
347 fmt::println(
m_os,
"scope size: {}", limit);
351 fmt::println(
m_os,
"Current scope is empty");
353 fmt::println(
m_os,
"");
360 fmt::println(
m_os,
"Nothing to show, count must be > 0");
363 const std::size_t scopes_count = context.
locals.size() - 1;
364 fmt::println(
m_os,
"There are {} scope{}\n", scopes_count, scopes_count == 1 ?
"" :
"s");
370 if (scopes_count <= i)
374 const std::size_t idx = scopes_count - i - 1;
375 const auto& scope = context.
locals[idx];
377 fmt::println(
m_os,
"Scope {}, size: {}", idx, scope.size());
378 if (scope.size() > 0)
386 fmt::println(
m_os,
"");
391 fmt::println(
m_os,
"index | id | name | type | value");
396 if (scope.
size() <= i)
400 const auto color =
m_colorize ? fmt::fg(i % 2 == 0 ? fmt::color::forest_green : fmt::color::cornflower_blue) : fmt::text_style();
404 "{:>5} | {:3} | {:14} | {:>9} | {}",
405 fmt::styled(scope.
size() - i - 1, color),
406 fmt::styled(
id, color),
409 fmt::styled(value.toString(vm,
true), color));
411 }
while (!limit.has_value() || i < limit.value());
419 const auto syms = bcr.
symbols();
420 const auto vals = bcr.
values(syms);
423 fmt::println(
m_os,
" PP, IP");
425 for (std::size_t i = 0; i < count; ++i)
431 fmt::print(
m_os,
"{:>3},{:>3} ", pp, ip);
439 long open_parens = 0;
440 long open_braces = 0;
444 const bool unfinished_block = open_parens != 0 || open_braces != 0;
447 "dbg[{},{}]:{:0>3}{} ",
448 fmt::format(
"pp:{}", fmt::styled(pp,
m_colorize ? fmt::fg(fmt::color::green) : fmt::text_style())),
449 fmt::format(
"ip:{}", fmt::styled(ip / 4,
m_colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style())),
451 unfinished_block ?
":" :
">");
457 fmt::println(
m_os,
"{}", line);
460 std::getline(std::cin, line);
464 if (line.empty() && !unfinished_block)
466 fmt::println(
m_os,
"dbg: continue");
472 const Command cmd = maybe_cmd.value();
473 if (cmd.
action(line,
CommandArgs { .vm_ptr = &vm, .ctx_ptr = &context, .ip = ip, .pp = pp, .me = cmd }))
484 if (open_braces == 0 && open_parens == 0)
492 std::optional<CompiledPrompt>
Debugger::compile(
const std::string& code,
const std::size_t start_page_at_offset)
const
502 const auto syms = bcr.
symbols();
503 const auto vals = bcr.
values(syms);
506 const auto [pages, _] = bcr.
code(inst_locs);
508 return std::optional(
CompiledPrompt(pages, syms.symbols, vals.values));
A bytecode disassembler for ArkScript.
Debugger used by the VM when an error or a breakpoint is reached.
Tools to report code errors nicely to the user.
Lots of utilities about the filesystem.
State used by the virtual machine: it loads the bytecode, can compile it if needed,...
The ArkScript virtual machine.
In charge of welding everything needed to compile code.
This class is just a helper to.
Filenames filenames(const Values &values) const
InstLocations instLocations(const Filenames &filenames) const
Code code(const InstLocations &instLocations) const
Values values(const Symbols &symbols) const
void printInstruction(std::ostream &os, uint8_t inst, uint8_t padding, uint16_t imm_arg, const Symbols &syms, const Values &vals, bool colorize=true) const
void feed(const std::string &file)
Construct needed data before displaying information about a given file.
std::vector< std::string > m_filenames
std::vector< std::string > m_symbols
std::vector< bytecode_t > m_pages
void extendBytecode(const std::vector< bytecode_t > &pages, const std::vector< std::string > &symbols, const std::vector< Value > &constants)
Used by the debugger to add code to the VM at runtime.
The ArkScript virtual machine, executing ArkScript bytecode.
ARK_ALWAYS_INLINE Value * peekAndResolveAsPtr(internal::ExecutionContext &context, std::size_t offset=0)
Return a pointer to the top of the stack without consuming it, and resolve it if possible.
int safeRun(internal::ExecutionContext &context, std::size_t untilFrameCount=0, bool fail_with_exception=false)
Run ArkScript bytecode inside a try catch to retrieve all the exceptions and display a stack trace if...
const bytecode_t & bytecode() const
std::optional< internal::InstLoc > findSourceLocation(std::size_t ip, std::size_t pp) const
Find the nearest source location information given instruction and page pointers.
ValueType valueType() const noexcept
std::string toString(VM &vm, bool show_as_code=false) const noexcept
The welder joins all the compiler passes.
bool generateBytecodeUsingTables(const std::vector< std::string > &symbols, const std::vector< Value > &constants, std::size_t start_page_at_offset)
Compile the AST processed by computeASTFromFile / computeASTFromString, with prefilled symbols and co...
const bytecode_t & bytecode() const noexcept
bool computeASTFromStringWithKnownSymbols(const std::string &code, const std::vector< std::string > &symbols)
Compile code from a string, with a set of known symbols (useful for the debugger)
void run(VM &vm, ExecutionContext &context, bool from_breakpoint)
Start the debugger shell.
std::string m_code
Code added while inside the debugger.
std::vector< std::unique_ptr< SavedState > > m_states
void showStack(VM &vm, const ExecutionContext &context, std::size_t count) const
std::optional< Command > matchCommand(const std::string &line) const
void showLocals(VM &vm, ExecutionContext &context, std::size_t count) const
std::deque< TracedInstruction > m_previous_insts
std::optional< std::string > prompt(std::size_t ip, std::size_t pp, VM &vm, ExecutionContext &context)
void resetContextToSavedState(ExecutionContext &context)
Reset a VM context to the last state saved by the debugger.
Debugger(const ExecutionContext &context, const std::vector< std::filesystem::path > &libenv, const std::vector< std::string > &symbols, const std::vector< Value > &constants)
Create a new Debugger object.
void registerInstruction(uint8_t inst, uint8_t padding, uint16_t arg, std::size_t ip, std::size_t pp) noexcept
std::unique_ptr< std::istream > m_prompt_stream
std::vector< std::string > m_symbols
void showScopes(VM &vm, ExecutionContext &context, std::size_t count) const
std::vector< Command > m_commands
void showContext(const VM &vm, const ExecutionContext &context) const
void saveState(const ExecutionContext &context)
Save the current VM state, to get back to it once the debugger is done running.
std::vector< std::filesystem::path > m_libenv
std::vector< Value > m_constants
void showPreviousInstructions(const VM &vm, std::size_t count) const
std::optional< CompiledPrompt > compile(const std::string &code, std::size_t start_page_at_offset) const
Take care of compiling new code using the existing data tables.
A class to handle the VM scope more efficiently.
ARK_ALWAYS_INLINE pair_t & atPosReverse(const std::size_t i) const noexcept
Return the element at index, starting from the end.
ARK_ALWAYS_INLINE std::size_t size() const noexcept
Return the size of the scope.
ARK_API void makeContext(const ErrorLocation &loc, std::ostream &os, const std::optional< CodeErrorContext > &maybe_context, bool colorize)
Helper to create a colorized context to report errors to the user.
bool fileExists(const std::string &name) noexcept
Checks if a file exists.
ARK_API long countOpenEnclosures(const std::string &line, char open, char close)
Count the open enclosure and its counterpart: (), {}, [].
std::vector< std::string > splitString(const std::string &source, const char sep)
Cut a string into pieces, given a character separator.
ARK_API void trimWhitespace(std::string &line)
Remove whitespaces at the start and end of a string.
constexpr uint16_t DefaultFeatures
@ Garbage
Used to signal a value was used and can/should be collected and removed from the stack.
std::string to_string(const Ark::ValueType type) noexcept
ExecutionContext * ctx_ptr
std::vector< std::pair< std::string, std::string > > Args_t
std::optional< std::size_t > argAsCount(const std::string &line, std::size_t idx, std::ostream &os) const
std::optional< Args_t > getArgs(const std::string &line, std::ostream &os) const
std::array< ScopeView::pair_t, ScopeStackSize > scopes_storage
All the ScopeView use this array to store id->value.
std::vector< std::shared_ptr< ClosureScope > > stacked_closure_scopes
Stack the closure scopes to keep the closure alive as long as we are calling them.
std::size_t pp
Page pointer.
std::vector< ScopeView > locals
std::array< Value, VMStackSizeWithOverflowBuffer > stack
uint16_t sp
Stack pointer.
std::size_t ip
Instruction pointer.