tsl-devkit/lsp-server/test/test_symbol/debug_printer.cpp

787 lines
26 KiB
C++

#include <algorithm>
#include <functional>
#include <iomanip>
#include <sstream>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "./debug_printer.hpp"
namespace
{
using namespace lsp::language;
using namespace lsp::language::symbol;
const char* AccessModifierToString(ast::AccessModifier access)
{
switch (access)
{
case ast::AccessModifier::kPublic:
return "public";
case ast::AccessModifier::kProtected:
return "protected";
case ast::AccessModifier::kPrivate:
return "private";
}
return "public";
}
const char* VariableScopeToString(VariableScope scope)
{
switch (scope)
{
case VariableScope::kAutomatic:
return "auto";
case VariableScope::kStatic:
return "static";
case VariableScope::kGlobal:
return "global";
case VariableScope::kParameter:
return "param";
case VariableScope::kField:
return "field";
}
return "auto";
}
} // namespace
namespace lsp::language::symbol::debug
{
namespace Color
{
constexpr const char* Reset = "\033[0m";
constexpr const char* Bold = "\033[1m";
constexpr const char* Dim = "\033[2m";
constexpr const char* Red = "\033[31m";
constexpr const char* Green = "\033[32m";
constexpr const char* Yellow = "\033[33m";
constexpr const char* Blue = "\033[34m";
constexpr const char* Magenta = "\033[35m";
constexpr const char* Cyan = "\033[36m";
constexpr const char* White = "\033[37m";
constexpr const char* BrightBlue = "\033[94m";
constexpr const char* BrightMagenta = "\033[95m";
constexpr const char* BrightGreen = "\033[92m";
constexpr const char* Gray = "\033[90m";
}
// ==================== PrintOptions ====================
PrintOptions PrintOptions::Default()
{
return PrintOptions{};
}
PrintOptions PrintOptions::Compact()
{
PrintOptions opts;
opts.compact_mode = true;
opts.show_details = false;
opts.show_children = false;
opts.indent_size = 1;
return opts;
}
PrintOptions PrintOptions::Verbose()
{
PrintOptions opts;
opts.show_details = true;
opts.show_children = true;
return opts;
}
PrintOptions PrintOptions::NoColor()
{
PrintOptions opts;
opts.use_color = false;
return opts;
}
// ==================== Statistics ====================
void Statistics::Compute(const SymbolTable& table)
{
auto all_defs = table.all_definitions();
total_symbols = all_defs.size();
symbol_counts.clear();
scope_counts.clear();
for (const auto& ref : all_defs)
{
const auto& sym = ref.get();
symbol_counts[sym.kind()]++;
}
const auto& scopes = table.scopes().all_scopes();
total_scopes = scopes.size();
for (const auto& [_, info] : scopes)
{
scope_counts[info.kind]++;
}
}
void Statistics::Print(std::ostream& os, bool use_color) const
{
auto color = [use_color](const char* code) {
return use_color ? code : "";
};
os << "\n";
os << color(Color::Bold) << "╔════════════════════════════════════════════════════════════╗\n";
os << "║ STATISTICS ║\n";
os << "╚════════════════════════════════════════════════════════════╝"
<< color(Color::Reset) << "\n\n";
os << color(Color::Bold) << "Overview:" << color(Color::Reset) << "\n";
os << " Total Symbols: " << color(Color::Cyan) << total_symbols << color(Color::Reset) << "\n";
os << " Total Scopes: " << color(Color::Cyan) << total_scopes << color(Color::Reset) << "\n";
os << "\n";
os << color(Color::Bold) << "Symbol Distribution:" << color(Color::Reset) << "\n";
struct KindInfo
{
SymbolKind kind;
const char* name;
const char* icon;
};
KindInfo kinds[] = {
{ SymbolKind::Namespace, "Namespace", "🗃️" },
{ SymbolKind::Class, "Classes", "🏛️" },
{ SymbolKind::Function, "Functions", "🎄" },
{ SymbolKind::Method, "Methods", "🪀" },
{ SymbolKind::Property, "Properties", "📋" },
{ SymbolKind::Field, "Fields", "📌" },
{ SymbolKind::Variable, "Variables", "📊" },
{ SymbolKind::Constant, "Constants", "🔒" }
};
for (const auto& kind_info : kinds)
{
auto it = symbol_counts.find(kind_info.kind);
if (it != symbol_counts.end() && it->second > 0)
{
os << " " << kind_info.icon << " "
<< std::setw(15) << std::left << kind_info.name
<< ": " << color(Color::BrightBlue) << std::setw(5) << std::right
<< it->second << color(Color::Reset) << "\n";
}
}
if (!scope_counts.empty())
{
os << "\n"
<< color(Color::Bold) << "Scope Distribution:" << color(Color::Reset) << "\n";
for (const auto& [kind, count] : scope_counts)
{
os << " " << std::setw(12) << std::left << static_cast<int>(kind)
<< ": " << color(Color::BrightMagenta) << count << color(Color::Reset) << "\n";
}
}
}
// ==================== DebugPrinter ====================
DebugPrinter::DebugPrinter(const SymbolTable& table, const PrintOptions& options) : table_(table), options_(options)
{
stats_.Compute(table_);
}
void DebugPrinter::PrintSeparator(std::ostream& os, char ch, int width) const
{
for (int i = 0; i < width; ++i)
os << ch;
os << "\n";
}
std::string DebugPrinter::Color(const char* color_code) const
{
return options_.use_color ? color_code : "";
}
std::string DebugPrinter::Bold(const std::string& text) const
{
return options_.use_color ? std::string(Color::Bold) + text + Color::Reset : text;
}
std::string DebugPrinter::Dim(const std::string& text) const
{
return options_.use_color ? std::string(Color::Dim) + text + Color::Reset : text;
}
std::string DebugPrinter::Indent(int depth) const
{
return std::string(static_cast<size_t>(depth * options_.indent_size), ' ');
}
std::string DebugPrinter::FormatLocation(const ast::Location& loc) const
{
std::ostringstream oss;
oss << loc.start_line << ":" << loc.start_column << "-" << loc.end_line << ":" << loc.end_column;
return oss.str();
}
std::string DebugPrinter::FormatSymbolKind(SymbolKind kind) const
{
switch (kind)
{
case SymbolKind::Namespace:
return "Namespace";
case SymbolKind::Class:
return "Class";
case SymbolKind::Method:
return "Method";
case SymbolKind::Property:
return "Property";
case SymbolKind::Field:
return "Field";
case SymbolKind::Function:
return "Function";
case SymbolKind::Variable:
return "Variable";
case SymbolKind::Constant:
return "Constant";
default:
return "Unknown";
}
}
std::string DebugPrinter::FormatScopeKind(ScopeKind kind) const
{
switch (kind)
{
case ScopeKind::kGlobal:
return "Global";
case ScopeKind::kUnit:
return "Unit";
case ScopeKind::kClass:
return "Class";
case ScopeKind::kFunction:
return "Function";
case ScopeKind::kAnonymousFunction:
return "AnonymousFunction";
case ScopeKind::kBlock:
return "Block";
default:
return "Unknown";
}
}
std::string DebugPrinter::SymbolIcon(SymbolKind kind) const
{
switch (kind)
{
case SymbolKind::Namespace:
return "🗃️";
case SymbolKind::Class:
return "🏛️";
case SymbolKind::Method:
return "🔧"; // Default, will be overridden in GetSymbolIcon
case SymbolKind::Property:
return "📋";
case SymbolKind::Field:
return "📌";
case SymbolKind::Function:
return "🌲"; // Default, will be overridden in GetSymbolIcon
case SymbolKind::Variable:
return "📊";
case SymbolKind::Constant:
return "🔒";
default:
return "";
}
}
std::string DebugPrinter::GetSymbolIcon(const Symbol& symbol) const
{
// Check if it's a Method with declaration/implementation distinction
if (const auto* method = symbol.As<Method>())
{
bool has_decl = method->declaration_range.start_line != 0;
bool has_impl = method->implementation_range.has_value();
if (has_decl && has_impl)
{
// Has both declaration and implementation
if (symbol.selection_range().start_line == method->declaration_range.start_line)
{
return "🔷"; // Declaration: blue diamond (interface/contract)
}
else if (has_impl && symbol.selection_range().start_line == method->implementation_range->start_line)
{
return "🔶"; // Implementation: orange diamond (concrete/실행)
}
}
return "🔧"; // Default method icon (wrench/tool)
}
// Check if it's a Function with declaration/implementation distinction
if (const auto* func = symbol.As<Function>())
{
bool has_decl = func->declaration_range.start_line != 0;
bool has_impl = func->implementation_range.has_value();
if (has_decl && has_impl)
{
// Has both declaration and implementation
if (symbol.selection_range().start_line == func->declaration_range.start_line)
{
return "🌳"; // Declaration: deciduous tree (interface/skeleton)
}
else if (has_impl && symbol.selection_range().start_line == func->implementation_range->start_line)
{
return "🎄"; // Implementation: christmas tree (decorated/complete)
}
}
return "🌲"; // Default function icon (evergreen tree)
}
// For other types, use the default icon
return SymbolIcon(symbol.kind());
}
std::string DebugPrinter::ColorizeSymbolKind(SymbolKind kind) const
{
return Color(Color::Blue) + FormatSymbolKind(kind) + Color(Color::Reset);
}
std::string DebugPrinter::ColorizeSymbolName(const std::string& name, SymbolKind /*kind*/) const
{
return Color(Color::Yellow) + name + Color(Color::Reset);
}
void DebugPrinter::PrintHeader(const std::string& title, std::ostream& os) const
{
os << "\n"
<< Bold(title) << "\n";
PrintSeparator(os, '=', 60);
}
void DebugPrinter::PrintSubHeader(const std::string& title, std::ostream& os) const
{
os << "\n"
<< Bold(title) << "\n";
PrintSeparator(os, '-', 40);
}
void DebugPrinter::PrintSymbolData(const Symbol& symbol, std::ostream& os, int depth)
{
std::visit(
[&](const auto& data) {
// Tree connector prefix based on depth
std::string prefix;
if (depth > 0)
{
prefix = Indent(depth - 1) + Color(Color::Gray) + "├─ " + Color(Color::Reset);
}
else
{
prefix = "";
}
os << prefix << GetSymbolIcon(symbol) << " "
<< ColorizeSymbolName(symbol.name(), symbol.kind())
<< " " << Color(Color::Dim) << "(" << FormatSymbolKind(symbol.kind()) << ")" << Color(Color::Reset);
if (options_.show_location)
{
os << " " << Color(Color::Gray) << "@ " << FormatLocation(symbol.selection_range()) << Color(Color::Reset);
}
// Type-specific details
if constexpr (std::is_same_v<std::decay_t<decltype(data)>, Function>)
{
if (options_.show_details)
{
os << " " << Color(Color::BrightGreen) << "" << (data.return_type ? *data.return_type : "void") << Color(Color::Reset);
if (!data.parameters.empty())
{
os << " " << Color(Color::Dim) << "(";
for (size_t i = 0; i < data.parameters.size(); ++i)
{
const auto& p = data.parameters[i];
os << p.name;
if (p.type)
os << ":" << *p.type;
if (i + 1 < data.parameters.size())
os << ", ";
}
os << ")" << Color(Color::Reset);
}
}
}
else if constexpr (std::is_same_v<std::decay_t<decltype(data)>, Method>)
{
if (options_.show_details)
{
os << " " << Color(Color::Cyan) << "[" << AccessModifierToString(data.access) << "]" << Color(Color::Reset);
if (data.is_static)
os << " " << Color(Color::Yellow) << "static" << Color(Color::Reset);
if (data.return_type)
os << " " << Color(Color::BrightGreen) << "" << *data.return_type << Color(Color::Reset);
if (!data.parameters.empty())
{
os << " " << Color(Color::Dim) << "(";
for (size_t i = 0; i < data.parameters.size(); ++i)
{
const auto& p = data.parameters[i];
os << p.name;
if (p.type)
os << ":" << *p.type;
if (i + 1 < data.parameters.size())
os << ", ";
}
os << ")" << Color(Color::Reset);
}
}
}
else if constexpr (std::is_same_v<std::decay_t<decltype(data)>, Property>)
{
if (options_.show_details)
{
os << " " << Color(Color::Cyan) << "[" << AccessModifierToString(data.access) << "]" << Color(Color::Reset);
if (data.type)
os << " " << Color(Color::BrightGreen) << ":" << *data.type << Color(Color::Reset);
}
}
else if constexpr (std::is_same_v<std::decay_t<decltype(data)>, Field>)
{
if (options_.show_details)
{
os << " " << Color(Color::Cyan) << "[" << AccessModifierToString(data.access) << "]" << Color(Color::Reset);
if (data.type)
os << " " << Color(Color::BrightGreen) << ":" << *data.type << Color(Color::Reset);
if (data.is_static)
os << " " << Color(Color::Yellow) << "static" << Color(Color::Reset);
}
}
else if constexpr (std::is_same_v<std::decay_t<decltype(data)>, Variable>)
{
if (options_.show_details)
{
os << " " << Color(Color::Dim) << "(" << VariableScopeToString(data.storage) << ")" << Color(Color::Reset);
if (data.type)
os << " " << Color(Color::BrightGreen) << ":" << *data.type << Color(Color::Reset);
if (data.has_initializer)
os << " " << Color(Color::Gray) << "= ..." << Color(Color::Reset);
}
}
else if constexpr (std::is_same_v<std::decay_t<decltype(data)>, Constant>)
{
if (options_.show_details)
{
if (data.type)
os << " " << Color(Color::BrightGreen) << ":" << *data.type << Color(Color::Reset);
os << " " << Color(Color::Gray) << "= " << data.value << Color(Color::Reset);
}
}
os << "\n";
},
symbol.data());
}
void DebugPrinter::PrintSymbol(SymbolId id, std::ostream& os, int depth)
{
const auto* sym = table_.definition(id);
if (!sym)
{
os << Indent(depth) << "❓ Unknown symbol: " << id << "\n";
return;
}
PrintSymbolData(*sym, os, depth);
}
void DebugPrinter::PrintSymbolWithChildren(SymbolId id, const std::unordered_multimap<SymbolId, SymbolId>& children, std::ostream& os, int depth, int current_depth)
{
if (options_.max_depth >= 0 && current_depth > options_.max_depth)
return;
PrintSymbol(id, os, depth);
if (!options_.show_children)
return;
auto range = children.equal_range(id);
std::vector<SymbolId> child_list;
for (auto it = range.first; it != range.second; ++it)
{
child_list.push_back(it->second);
}
// Sort children by location for consistent output
std::sort(child_list.begin(), child_list.end(), [this](SymbolId a, SymbolId b) {
const auto* sym_a = table_.definition(a);
const auto* sym_b = table_.definition(b);
if (!sym_a || !sym_b)
return a < b;
auto loc_a = sym_a->selection_range();
auto loc_b = sym_b->selection_range();
if (loc_a.start_line != loc_b.start_line)
return loc_a.start_line < loc_b.start_line;
return loc_a.start_column < loc_b.start_column;
});
for (auto child_id : child_list)
{
PrintSymbolWithChildren(child_id, children, os, depth + 1, current_depth + 1);
}
}
void DebugPrinter::PrintSymbolTree(SymbolId id, std::ostream& os, int depth)
{
std::unordered_multimap<SymbolId, SymbolId> children;
for (const auto& [scope_id, scope] : table_.scopes().all_scopes())
{
if (scope.owner)
{
for (const auto& [_, sym_id] : scope.symbols)
{
children.emplace(*scope.owner, sym_id);
}
}
}
PrintSymbolWithChildren(id, children, os, depth, 0);
}
void DebugPrinter::PrintSymbolList(std::ostream& os)
{
PrintHeader("Symbols", os);
auto all_defs = table_.all_definitions();
// Group symbols by their parent (class/namespace)
std::unordered_multimap<SymbolId, SymbolId> children;
std::unordered_set<SymbolId> has_parent;
for (const auto& [scope_id, scope] : table_.scopes().all_scopes())
{
if (scope.owner)
{
for (const auto& [_, sym_id] : scope.symbols)
{
children.emplace(*scope.owner, sym_id);
has_parent.insert(sym_id);
}
}
}
// Find top-level symbols (no parent)
std::vector<const Symbol*> top_level;
for (const auto& ref : all_defs)
{
const auto& sym = ref.get();
if (has_parent.find(sym.id()) == has_parent.end())
{
top_level.push_back(&sym);
}
}
// Sort top-level symbols by location
std::sort(top_level.begin(), top_level.end(), [](const Symbol* a, const Symbol* b) {
auto loc_a = a->selection_range();
auto loc_b = b->selection_range();
if (loc_a.start_line != loc_b.start_line)
return loc_a.start_line < loc_b.start_line;
return loc_a.start_column < loc_b.start_column;
});
// Print each top-level symbol with its children
for (const auto* sym : top_level)
{
PrintSymbolWithChildren(sym->id(), children, os, 0, 0);
}
}
void DebugPrinter::PrintSymbolsByKind(SymbolKind kind, std::ostream& os)
{
PrintSubHeader("Symbols of kind: " + FormatSymbolKind(kind), os);
auto all_defs = table_.all_definitions();
for (const auto& ref : all_defs)
{
const auto& sym = ref.get();
if (sym.kind() == kind)
{
PrintSymbolData(sym, os, 0);
}
}
}
void DebugPrinter::PrintScope(ScopeId id, std::ostream& os, int depth)
{
const auto* scope = table_.scopes().scope(id);
if (!scope)
{
os << Indent(depth) << "❓ Unknown scope: " << id << "\n";
return;
}
os << Indent(depth) << "📁 " << FormatScopeKind(scope->kind)
<< " (id=" << id << ")";
if (scope->owner)
{
os << " owner=" << *scope->owner;
}
os << "\n";
for (const auto& [name, sym_id] : scope->symbols)
{
os << Indent(depth + 1) << "" << name << " (id=" << sym_id << ")\n";
}
}
void DebugPrinter::PrintScopeTree(ScopeId id, std::ostream& os, int depth)
{
const auto& all_scopes = table_.scopes().all_scopes();
std::unordered_multimap<ScopeId, ScopeId> children;
for (const auto& [scope_id, scope] : all_scopes)
{
if (scope.parent)
{
children.emplace(*scope.parent, scope_id);
}
}
std::function<void(ScopeId, int)> dfs = [&](ScopeId sid, int d) {
PrintScope(sid, os, d);
auto range = children.equal_range(sid);
for (auto it = range.first; it != range.second; ++it)
{
dfs(it->second, d + 1);
}
};
dfs(id, depth);
}
void DebugPrinter::PrintScopeHierarchy(std::ostream& os)
{
PrintHeader("Scopes", os);
const auto& scopes = table_.scopes().all_scopes();
if (scopes.empty())
{
os << "No scopes available\n";
return;
}
auto global = table_.scopes().global_scope();
if (global == kInvalidScopeId && !scopes.empty())
{
global = scopes.begin()->first;
}
PrintScopeTree(global, os, 0);
}
void DebugPrinter::FindAndPrint(const std::string& name, std::ostream& os)
{
PrintHeader("Search: " + name, os);
auto matches = table_.FindSymbolsByName(name);
if (matches.empty())
{
os << "No symbols found matching \"" << name << "\"\n";
return;
}
for (auto id : matches)
{
PrintSymbol(id, os);
}
}
void DebugPrinter::FindAtLocation(const ast::Location& loc, std::ostream& os)
{
auto id = table_.FindSymbolAt(loc);
if (!id)
{
os << "No symbol found at location " << FormatLocation(loc) << "\n";
return;
}
PrintSymbol(*id, os);
}
void DebugPrinter::PrintOverview(std::ostream& os)
{
PrintHeader("Overview", os);
os << "Symbols: " << stats_.total_symbols << "\n";
os << "Scopes: " << stats_.total_scopes << "\n";
}
void DebugPrinter::PrintStatistics(std::ostream& os)
{
stats_.Print(os, options_.use_color);
}
void DebugPrinter::PrintAll(std::ostream& os)
{
PrintOverview(os);
PrintSymbolList(os);
PrintScopeHierarchy(os);
PrintStatistics(os);
}
// ==================== Free functions ====================
void Print(const SymbolTable& table, std::ostream& os)
{
DebugPrinter printer(table, PrintOptions::Default());
printer.PrintAll(os);
}
void PrintOverview(const SymbolTable& table, std::ostream& os)
{
DebugPrinter printer(table, PrintOptions::Default());
printer.PrintOverview(os);
}
void PrintStats(const SymbolTable& table, std::ostream& os)
{
DebugPrinter printer(table, PrintOptions::Default());
printer.PrintStatistics(os);
}
void PrintSymbolTree(const SymbolTable& table, std::ostream& os)
{
DebugPrinter printer(table, PrintOptions::Default());
auto defs = table.all_definitions();
for (const auto& ref : defs)
{
printer.PrintSymbolTree(ref.get().id(), os);
}
}
void PrintScopeTree(const SymbolTable& table, std::ostream& os)
{
DebugPrinter printer(table, PrintOptions::Default());
printer.PrintScopeHierarchy(os);
}
void Find(const SymbolTable& table, const std::string& name, std::ostream& os)
{
DebugPrinter printer(table, PrintOptions::Default());
printer.FindAndPrint(name, os);
}
void PrintCompact(const SymbolTable& table, std::ostream& os)
{
DebugPrinter printer(table, PrintOptions::Compact());
printer.PrintOverview(os);
printer.PrintSymbolList(os);
}
void PrintVerbose(const SymbolTable& table, std::ostream& os)
{
DebugPrinter printer(table, PrintOptions::Verbose());
printer.PrintAll(os);
}
} // namespace lsp::language::symbol::debug