Files
jak-project/common/goos/TextDB.h
T
Hat Kid fe2086acfb goalc: add debug server (#4365)
This adds a debug server to `goalc` that sends JSON over the socket to
communicate with an external debugger using the Debug Adapter Protocol.

This lets us debug GOAL code in a proper debugger with breakpoints, step
over, step in and step out per line, stack frames and supports watches
for global symbols, registers and local variables (local variables only
work within the most recent stack frame). Special registers (`r13`,
`r14`, `r15`, argument registers, etc.) are tracked separately and the
current process register even displays the type of the current `pp` if
possible.

Watches that track addresses holding a reference type generate a list of
field names according to the object's type. All fields will show their
name, type and value and, depending on the type, will try to infer extra
info like symbol names/values, function names for `function` fields,
enum values and more. This also works nested, so any field that is also
a reference type can also be accessed and display its fields, etc.
Dynamic arrays are also supported where possible, e.g. in
`inline-array-class` children and boxed arrays, it will figure out the
value of the `length` field and access the memory up to that point so
all the elements can be accessed and viewed from the `data` field.

Our VS Code extension implements the DAP in
open-goal/opengoal-vscode#375.

Using it is as simple as connecting a REPL to a running game instance
with `(lt)`, compiling with `(mi)` and, with the extension installed,
pressing F5 in VS Code to start the debugger. By default, it will try to
connect to the game that the active `.gc` file is from, the socket port
is different per game (8128 for Jak 1, 8129 for Jak 2, 8130 for Jak 3).
The `launch.json` was updated with two entries for this, the second
entry lets you pick the game/port manually if desired.

~~Not tested on Windows.~~ Only supports x86 for now.
2026-07-27 22:28:28 +02:00

127 lines
3.7 KiB
C++

#pragma once
/*!
* @file TextDB.h
* The Text Database for storing source code text.
* This allows us to ask for things like "where did this form come from?"
* and be able to print the file name and offset into the file, as well as the line.
*
* The purpose is to be able to have an error message like:
*
* Error on (+ a b): a has invalid type (string)
* From my-file.gc, line 25:
* (+ 1 (+ a b)) ; compute the sum
*/
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>
#include "common/goos/Object.h"
namespace goos {
/*!
* Parent class for source-code text organized in lines
*/
class SourceText {
public:
explicit SourceText(std::string r);
SourceText() = default;
const char* get_text() { return m_text.c_str(); }
int get_size() { return m_text.size(); }
virtual std::string get_description() = 0;
std::string get_line_containing_offset(int offset);
int get_line_idx(int offset);
int get_offset_of_line(int line_idx);
// should the compiler keep looking up the stack when printing errors on this, or not?
// this should return true if the text source is specific enough so that they can find what they
// want
virtual bool terminate_compiler_error() { return true; }
virtual ~SourceText() {};
protected:
void build_offsets();
std::string m_text;
std::vector<int> m_offset_by_line;
std::pair<int, int> get_containing_line(int offset);
};
/*!
* Text from the REPL prompt
*/
class ReplText : public SourceText {
public:
explicit ReplText(const std::string& text_) : SourceText(text_) {}
std::string get_description() override { return "REPL"; }
~ReplText() = default;
};
/*!
* Text generated by the program itself (like for example, with (read "hello"))
*/
class ProgramString : public SourceText {
public:
explicit ProgramString(const std::string& text_,
const std::string& string_name = "Program string")
: SourceText(text_), m_string_name(string_name) {}
std::string get_description() override { return m_string_name; }
~ProgramString() = default;
private:
std::string m_string_name;
};
/*!
* Text from a file.
*/
class FileText : public SourceText {
public:
FileText(const std::string& file_path, const std::string& description_name);
std::string get_description() { return m_desc_name; }
~FileText() = default;
private:
std::string m_filepath;
std::string m_desc_name;
};
struct TextRef {
int offset;
std::shared_ptr<SourceText> frag;
};
class TextDb {
public:
struct ShortInfo {
std::string filename;
int line_idx_to_display = -1;
int pos_in_line = -1;
std::string line_text;
};
void insert(const std::shared_ptr<SourceText>& frag);
void link(const Object& o, std::shared_ptr<SourceText> frag, int offset);
std::string get_info_for(const Object& o, bool* terminate_compiler_error = nullptr) const;
std::optional<ShortInfo> get_short_info_for(const Object& o) const;
std::string get_info_for(const std::shared_ptr<SourceText>& frag, int offset) const;
std::optional<ShortInfo> get_short_info_for(const std::shared_ptr<SourceText>& frag,
int offset) const;
std::optional<ShortInfo> try_get_short_info(const Object& o) const;
std::optional<ShortInfo> try_get_short_info(const std::shared_ptr<goos::HeapObject>& o,
bool shorten_filename = true) const;
bool has_info(const Object& o) const;
void inherit_info(const Object& parent, const Object& child);
void clear_info();
private:
std::vector<std::shared_ptr<SourceText>> m_fragments;
std::unordered_map<std::shared_ptr<goos::HeapObject>, TextRef> m_map;
};
} // namespace goos