feat(semantic): implement tree-based string representation for SymbolTable

Signed-off-by: erick-alcachofa <erick@artichoke.dev>

- Add comprehensive toString() implementation for all symbol types.
- Support recursive tree rendering for debugging symbol hierarchies.
- Implement OverloadSet-based visitation for Symbol variants.
This commit is contained in:
erick-alcachofa 2025-12-30 23:19:05 -06:00
parent f235a72671
commit d92f39538b
Signed by: me
GPG Key ID: 6FA5F8643444BAFA
2 changed files with 802 additions and 0 deletions

View File

@ -185,4 +185,6 @@ namespace arti::lang::sym {
Optional<TypeQualifier> qualifier;
};
std::string toString(const SharedPtr<SymbolTable> &, const std::string &);
} // namespace arti::lang

View File

@ -0,0 +1,800 @@
//============================================================================//
// //
// artichoke programming language //
// //
// Copyright (C) 2025 Erick Saul Guzman Ramos, whoami.artichoke.dev //
// //
// //
// This program is free software: you can redistribute it and/or modify //
// it under the terms of the GNU Affero General Public License as published //
// by the Free Software Foundation, either version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU Affero General Public License for more details. //
// //
// You should have received a copy of the GNU Affero General Public License //
// along with this program. If not, see <https://www.gnu.org/licenses/>. //
// //
//============================================================================//
#include <artichoke/Semantics/SymbolTable.hpp>
#include <utility>
#include <format>
#include <sstream>
#include <string>
#include <string_view>
#include <vector>
#include <artichoke/Util/OverloadSet.hpp>
namespace arti::lang::sym {
constexpr std::string_view StrTreeNoNode = "";
constexpr std::string_view StrTreeChilds = "├─";
constexpr std::string_view StrTreeLast = "└─";
constexpr std::string_view StrTreeSpace = " ";
inline std::string nextPrefix(const std::string &prefix, bool isLast) {
return prefix +
(isLast ? std::string(StrTreeSpace) : std::string(StrTreeNoNode));
}
std::string toString(const SymbolTable &, const std::string &);
std::string toString(const SharedPtr<SymbolTable> &, const std::string &);
std::string toString(const sym::Symbol &, const std::string &);
std::string toString(const sym::Function &, const std::string &);
std::string toString(const sym::FunctionType &, const std::string &);
std::string toString(const sym::Enum &, const std::string &);
std::string toString(const sym::EnumMember &, const std::string &);
std::string toString(const sym::Struct &, const std::string &);
std::string toString(const sym::StructMember &, const std::string &);
std::string toString(const sym::TemplateParam &, const std::string &);
std::string toString(const sym::TemplatedSymbol &, const std::string &);
std::string toString(const sym::Variable &, const std::string &);
std::string toString(const sym::Primitive &, const std::string &);
std::string toString(const sym::TypeSymbol &, const std::string &);
std::string
toString(const SharedPtr<sym::FunctionType> &, const std::string &);
template <typename T>
void appendItem(
std::stringstream &ss,
const std::string &prefix,
const T &item,
bool isLastChild
) {
ss << "\n"
<< prefix << (isLastChild ? StrTreeLast : StrTreeChilds) << " "
<< toString(item, nextPrefix(prefix, isLastChild));
}
template <typename T>
void appendGroupVec(
std::stringstream &ss,
const std::string &prefix,
std::string_view title,
const std::vector<T> &items,
bool isLastGroup
) {
if (items.empty()) {
return;
}
ss << "\n"
<< prefix << (isLastGroup ? StrTreeLast : StrTreeChilds) << " " << title;
for (std::size_t i = 0; i < items.size(); ++i) {
appendItem(
ss,
nextPrefix(prefix, isLastGroup),
items[i],
i + 1 == items.size()
);
}
}
template <typename T>
void appendGroupOne(
std::stringstream &ss,
const std::string &prefix,
std::string_view title,
const T &item,
bool isLastGroup
) {
ss << "\n"
<< prefix << (isLastGroup ? StrTreeLast : StrTreeChilds) << " " << title;
appendItem(ss, nextPrefix(prefix, isLastGroup), item, true);
}
inline void appendGroupLeaf(
std::stringstream &ss,
const std::string &prefix,
std::string_view title,
std::string_view value,
bool isLastGroup
) {
ss << "\n"
<< prefix << (isLastGroup ? StrTreeLast : StrTreeChilds) << " " << title;
ss << "\n"
<< nextPrefix(prefix, isLastGroup) << StrTreeLast << " `" << value
<< "`";
}
inline void appendGroupLeafList(
std::stringstream &ss,
const std::string &prefix,
std::string_view title,
const std::vector<std::string> &values,
bool isLastGroup
) {
if (values.empty()) {
return;
}
ss << "\n"
<< prefix << (isLastGroup ? StrTreeLast : StrTreeChilds) << " " << title;
for (std::size_t i = 0; i < values.size(); ++i) {
ss << "\n"
<< nextPrefix(prefix, isLastGroup)
<< (i + 1 == values.size() ? StrTreeLast : StrTreeChilds) << " `"
<< values[i] << "`";
}
}
template <typename Map, typename Printer>
void appendGroupHashMap(
std::stringstream &ss,
const std::string &prefix,
std::string_view title,
const Map &map,
bool isLastGroup,
Printer printer
) {
if (map.empty()) {
return;
}
ss << "\n"
<< prefix << (isLastGroup ? StrTreeLast : StrTreeChilds) << " " << title;
const auto total = map.size();
std::size_t index = 0;
for (const auto &entry : map) {
const auto &[key, value] = entry;
const bool isLastEntry = ++index == total;
const auto entryPrefix = nextPrefix(prefix, isLastGroup);
ss << "\n"
<< entryPrefix << (isLastEntry ? StrTreeLast : StrTreeChilds) << " `"
<< key << "`";
printer(ss, nextPrefix(entryPrefix, isLastEntry), value);
}
}
inline std::string formatName(const String &name) {
return name.empty() ? std::string("<anonymous>") : std::string(name);
}
inline std::string formatLocation(const SourceLocation &location) {
return std::format("(line {}, col {})", location.line, location.column);
}
inline std::string describeScope(const SymbolTable &table) {
return std::format(
"Scope `{}` {}",
formatName(table.name),
formatLocation(table.location)
);
}
inline std::string describeWeakScope(const WeakPtr<SymbolTable> &weak) {
if (auto locked = weak.lock()) {
return describeScope(*locked);
}
return "<expired scope>";
}
std::string describeWeakFunction(const WeakPtr<sym::Function> &);
std::string describeWeakFunctionType(const WeakPtr<sym::FunctionType> &);
std::string describeWeakStruct(const WeakPtr<sym::Struct> &);
std::string describeWeakEnum(const WeakPtr<sym::Enum> &);
std::string describeWeakPrimitive(const WeakPtr<sym::Primitive> &);
std::string describeWeakTemplateParam(const WeakPtr<sym::TemplateParam> &);
std::string describeWeakTypeSymbol(const WeakPtr<sym::TypeSymbol> &);
std::string
describeWeakTemplatedSymbol(const WeakPtr<sym::TemplatedSymbol> &);
inline std::string
describeTemplatedSymbolTarget(const sym::WeakSymbol &symbol) {
return std::visit(
OverloadSet{
[](const WeakPtr<sym::Function> &fn) {
return describeWeakFunction(fn);
},
[](const WeakPtr<sym::Variable> &var) {
if (auto locked = var.lock()) {
return std::format("Variable `{}`", formatName(locked->name));
}
return std::string("<expired variable>");
},
[](const WeakPtr<sym::TypeSymbol> &) {
return std::string("TypeSymbol");
},
[](const WeakPtr<sym::FunctionType> &fnType) {
return describeWeakFunctionType(fnType);
},
[](const WeakPtr<sym::Primitive> &primitive) {
return describeWeakPrimitive(primitive);
},
[](const WeakPtr<sym::Struct> &structure) {
return describeWeakStruct(structure);
},
[](const WeakPtr<sym::Enum> &enumeration) {
return describeWeakEnum(enumeration);
},
[](const WeakPtr<sym::TemplatedSymbol> &templated) {
if (auto locked = templated.lock()) {
return std::format(
"TemplatedSymbol `{}`",
formatName(locked->name)
);
}
return std::string("<expired templated symbol>");
} },
symbol
);
}
inline std::string describeWeakFunction(const WeakPtr<sym::Function> &weak) {
if (auto locked = weak.lock()) {
return std::format("Function `{}`", formatName(locked->name));
}
return "<expired function>";
}
inline std::string
describeWeakFunctionType(const WeakPtr<sym::FunctionType> &weak) {
if (auto locked = weak.lock()) {
return std::format("FunctionType `{}`", formatName(locked->name));
}
return "<expired function type>";
}
inline std::string describeWeakStruct(const WeakPtr<sym::Struct> &weak) {
if (auto locked = weak.lock()) {
return std::format("Struct `{}`", formatName(locked->name));
}
return "<expired struct>";
}
inline std::string describeWeakEnum(const WeakPtr<sym::Enum> &weak) {
if (auto locked = weak.lock()) {
return std::format("Enum `{}`", formatName(locked->name));
}
return "<expired enum>";
}
inline std::string
describeWeakPrimitive(const WeakPtr<sym::Primitive> &weak) {
if (auto locked = weak.lock()) {
return std::format("Primitive `{}`", formatName(locked->name));
}
return "<expired primitive>";
}
inline std::string
describeWeakTemplateParam(const WeakPtr<sym::TemplateParam> &weak) {
if (auto locked = weak.lock()) {
return std::format("TemplateParam `{}`", formatName(locked->name));
}
return "<expired template param>";
}
inline std::string
describeWeakTemplatedSymbol(const WeakPtr<sym::TemplatedSymbol> &weak) {
if (auto locked = weak.lock()) {
return std::format("TemplatedSymbol `{}`", formatName(locked->name));
}
return "<expired template param>";
}
inline std::string
describeWeakTypeSymbol(const WeakPtr<sym::TypeSymbol> &weak) {
if (auto locked = weak.lock()) {
(void) locked;
return "TypeSymbol";
}
return "<expired type>";
}
inline std::string qualifierName(TypeQualifier qualifier) {
switch (qualifier) {
case TypeQualifier::Uninitialized: return "Uninitialized";
case TypeQualifier::Pointer: return "Pointer";
case TypeQualifier::Slice: return "Slice";
case TypeQualifier::Mutable: return "Mutable";
case TypeQualifier::Optional: return "Optional";
default: return "Unknown";
}
std::unreachable();
}
std::string describeTypeKind(const sym::TypeSymbol::Type &type) {
return std::visit(
OverloadSet{
[](const SharedPtr<sym::TypeSymbol> &nested) -> std::string {
if (! nested) {
return "<null type>";
}
return "TypeSymbol";
},
[](const WeakPtr<sym::FunctionType> &weak) -> std::string {
return describeWeakFunctionType(weak);
},
[](const WeakPtr<sym::Primitive> &weak) -> std::string {
return describeWeakPrimitive(weak);
},
[](const WeakPtr<sym::Struct> &weak) -> std::string {
return describeWeakStruct(weak);
},
[](const WeakPtr<sym::Enum> &weak) -> std::string {
return describeWeakEnum(weak);
},
[](const WeakPtr<sym::TemplatedSymbol> &weak) -> std::string {
return describeWeakTemplatedSymbol(weak);
},
[](const WeakPtr<sym::TemplateParam> &weak) -> std::string {
return describeWeakTemplateParam(weak);
},
},
type
);
}
std::string toString(const SymbolTable &table, const std::string &prefix) {
std::stringstream ss;
ss << describeScope(table);
int groups = 0;
if (! table.symbols.empty()) {
++groups;
}
if (! table.inner.empty()) {
++groups;
}
int emitted = 0;
if (! table.symbols.empty()) {
appendGroupHashMap(
ss,
prefix,
"Symbols",
table.symbols,
++emitted == groups,
[](
std::stringstream &stream,
const std::string &valuePrefix,
const sym::Symbol &value
) { appendItem(stream, valuePrefix, value, true); }
);
}
if (! table.inner.empty()) {
appendGroupHashMap(
ss,
prefix,
"NestedScopes",
table.inner,
++emitted == groups,
[](
std::stringstream &stream,
const std::string &valuePrefix,
const SharedPtr<SymbolTable> &child
) { appendItem(stream, valuePrefix, child, true); }
);
}
return ss.str();
}
std::string
toString(const SharedPtr<SymbolTable> &ptr, const std::string &prefix) {
if (! ptr) {
return "<null scope>";
}
return toString(*ptr, prefix);
}
std::string
toString(const SharedPtr<sym::FunctionType> &ptr, const std::string &prefix) {
if (! ptr) {
return "<null function type>";
}
return toString(*ptr, prefix);
}
std::string toString(const sym::Symbol &symbol, const std::string &prefix) {
return std::visit(
OverloadSet{
[&prefix](const SharedPtr<sym::Function> &value) -> std::string {
return value ? toString(*value, prefix)
: std::string("<null function>");
},
[&prefix](const SharedPtr<sym::Variable> &value) -> std::string {
return value ? toString(*value, prefix)
: std::string("<null variable>");
},
[&prefix](const SharedPtr<sym::TypeSymbol> &value) -> std::string {
return value ? toString(*value, prefix)
: std::string("<null type symbol>");
},
[&prefix](const SharedPtr<sym::FunctionType> &value) -> std::string {
return value ? toString(*value, prefix)
: std::string("<null function type>");
},
[&prefix](const SharedPtr<sym::Primitive> &value) -> std::string {
return value ? toString(*value, prefix)
: std::string("<null primitive>");
},
[&prefix](const SharedPtr<sym::Struct> &value) -> std::string {
return value ? toString(*value, prefix)
: std::string("<null struct>");
},
[&prefix](const SharedPtr<sym::Enum> &value) -> std::string {
return value ? toString(*value, prefix) : std::string("<null enum>");
},
[&prefix](const SharedPtr<sym::TemplatedSymbol> &value) -> std::string {
return value ? toString(*value, prefix)
: std::string("<null templated symbol>");
},
[&prefix](const SharedPtr<sym::TemplateParam> &value) -> std::string {
return value ? toString(*value, prefix)
: std::string("<null templated symbol>");
},
},
symbol
);
}
std::string
toString(const sym::TemplatedSymbol &templated, const std::string &prefix) {
std::stringstream ss;
ss << std::format(
"TemplatedSymbol `{}` {}",
formatName(templated.name),
formatLocation(templated.location)
);
int groups = 0;
if (! templated.symbol.valueless_by_exception()) {
++groups;
}
if (! templated.params.empty()) {
++groups;
}
int emitted = 0;
if (! templated.symbol.valueless_by_exception()) {
appendGroupLeaf(
ss,
prefix,
"BaseSymbol",
describeTemplatedSymbolTarget(templated.symbol),
++emitted == groups
);
}
if (! templated.params.empty()) {
appendGroupVec(
ss,
prefix,
"Params",
templated.params,
++emitted == groups
);
}
return ss.str();
}
std::string toString(const sym::Function &fn, const std::string &prefix) {
std::stringstream ss;
ss << std::format(
"Function `{}` {}",
formatName(fn.name),
formatLocation(fn.location)
);
int groups = 0;
if (fn.body) {
++groups;
}
auto typePtr = fn.type.lock();
const bool hasType = static_cast<bool>(typePtr);
if (hasType) {
++groups;
}
int emitted = 0;
if (fn.body) {
appendGroupOne(ss, prefix, "Body", fn.body, ++emitted == groups);
}
if (hasType) {
appendGroupOne(ss, prefix, "Type", typePtr, ++emitted == groups);
}
return ss.str();
}
std::string
toString(const sym::FunctionType &fnType, const std::string &prefix) {
std::stringstream ss;
ss << std::format(
"FunctionType `{}` {}",
formatName(fnType.name),
formatLocation(fnType.location)
);
int groups = 0;
if (! fnType.symbol.expired()) {
++groups;
}
if (! fnType.arguments.empty()) {
++groups;
}
if (fnType.returnType) {
++groups;
}
int emitted = 0;
if (! fnType.symbol.expired()) {
appendGroupLeaf(
ss,
prefix,
"Symbol",
describeWeakFunction(fnType.symbol),
++emitted == groups
);
}
if (! fnType.arguments.empty()) {
std::vector<std::string> arguments;
arguments.reserve(fnType.arguments.size());
for (const auto &argument : fnType.arguments) {
arguments.emplace_back(describeWeakTypeSymbol(argument));
}
appendGroupLeafList(
ss,
prefix,
"Arguments",
arguments,
++emitted == groups
);
}
if (fnType.returnType) {
appendGroupLeaf(
ss,
prefix,
"Return",
describeWeakTypeSymbol(*fnType.returnType),
++emitted == groups
);
}
return ss.str();
}
std::string
toString(const sym::Enum &enumeration, const std::string &prefix) {
std::stringstream ss;
ss << std::format(
"Enum `{}` {} size={} align={}",
formatName(enumeration.name),
formatLocation(enumeration.location),
enumeration.size,
enumeration.alignment
);
int groups = 0;
if (! enumeration.membersList.empty()) {
++groups;
}
if (! enumeration.methods.empty()) {
++groups;
}
int emitted = 0;
if (! enumeration.membersList.empty()) {
appendGroupVec(
ss,
prefix,
"Members",
enumeration.membersList,
++emitted == groups
);
}
if (! enumeration.methods.empty()) {
appendGroupHashMap(
ss,
prefix,
"Methods",
enumeration.methods,
++emitted == groups,
[](
std::stringstream &stream,
const std::string &valuePrefix,
const WeakPtr<sym::Function> &method
) {
appendGroupLeaf(
stream,
valuePrefix,
"Function",
describeWeakFunction(method),
true
);
}
);
}
return ss.str();
}
std::string
toString(const sym::EnumMember &member, const std::string &prefix) {
std::stringstream ss;
ss << std::format(
"EnumMember `{}` {} index={}",
formatName(member.name),
formatLocation(member.location),
member.index
);
if (member.type) {
appendGroupLeaf(
ss,
prefix,
"Type",
describeWeakTypeSymbol(*member.type),
true
);
}
return ss.str();
}
std::string
toString(const sym::Struct &structure, const std::string &prefix) {
std::stringstream ss;
ss << std::format(
"Struct `{}` {} size={} align={}",
formatName(structure.name),
formatLocation(structure.location),
structure.size,
structure.alignment
);
int groups = 0;
if (! structure.membersList.empty()) {
++groups;
}
if (! structure.methods.empty()) {
++groups;
}
int emitted = 0;
if (! structure.membersList.empty()) {
appendGroupVec(
ss,
prefix,
"Members",
structure.membersList,
++emitted == groups
);
}
if (! structure.methods.empty()) {
appendGroupHashMap(
ss,
prefix,
"Methods",
structure.methods,
++emitted == groups,
[](
std::stringstream &stream,
const std::string &valuePrefix,
const WeakPtr<sym::Function> &method
) {
appendGroupLeaf(
stream,
valuePrefix,
"Function",
describeWeakFunction(method),
true
);
}
);
}
return ss.str();
}
std::string
toString(const sym::StructMember &member, const std::string &prefix) {
std::stringstream ss;
ss << std::format(
"StructMember `{}` {} index={} offset={}",
formatName(member.name),
formatLocation(member.location),
member.index,
member.offset
);
appendGroupLeaf(
ss,
prefix,
"Type",
describeWeakTypeSymbol(member.type),
true
);
return ss.str();
}
std::string toString(const sym::TemplateParam &param, const std::string &) {
std::stringstream ss;
ss << std::format(
"TemplateParam `{}` {}",
formatName(param.name),
formatLocation(param.location)
);
return ss.str();
}
std::string toString(const sym::Variable &var, const std::string &prefix) {
std::stringstream ss;
ss << std::format(
"Variable `{}` {}",
formatName(var.name),
formatLocation(var.location)
);
appendGroupLeaf(ss, prefix, "Type", describeWeakTypeSymbol(var.type), true);
return ss.str();
}
std::string
toString(const sym::Primitive &primitive, const std::string &prefix) {
std::stringstream ss;
ss << std::format(
"Primitive `{}` size={} align={}",
formatName(primitive.name),
primitive.size,
primitive.alignment
);
(void) prefix;
return ss.str();
}
std::string toString(const sym::TypeSymbol &type, const std::string &prefix) {
std::stringstream ss;
ss << "TypeSymbol";
int groups = 1;
if (type.qualifier) {
++groups;
}
int emitted = 0;
appendGroupLeaf(
ss,
prefix,
"Kind",
describeTypeKind(type.type),
++emitted == groups
);
if (type.qualifier) {
appendGroupLeaf(
ss,
prefix,
"Qualifier",
qualifierName(*type.qualifier),
++emitted == groups
);
}
return ss.str();
} // namespace
std::string toPrettyString(const SymbolTable &table, std::string prefix) {
auto rendered = toString(table, prefix);
if (! prefix.empty()) {
rendered.insert(0, prefix);
}
return rendered;
}
} // namespace arti::lang::sym