340 lines
10 KiB
C++
340 lines
10 KiB
C++
/**
|
|
* src/cx/util.cc
|
|
*
|
|
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "cx/util.h"
|
|
#include "util/util.h"
|
|
|
|
#include <cppast/cpp_class.hpp>
|
|
#include <cppast/cpp_entity_kind.hpp>
|
|
#include <cppast/cpp_namespace.hpp>
|
|
#include <cppast/cpp_template.hpp>
|
|
#include <spdlog/spdlog.h>
|
|
|
|
#include <class_diagram/model/template_parameter.h>
|
|
#include <list>
|
|
|
|
namespace clanguml {
|
|
namespace cx {
|
|
namespace util {
|
|
|
|
std::string full_name(
|
|
const common::model::namespace_ ¤t_ns, const cppast::cpp_entity &e)
|
|
{
|
|
if (e.name().empty())
|
|
return "";
|
|
else if (cppast::is_parameter(e.kind()))
|
|
// parameters don't have a full name
|
|
return e.name();
|
|
|
|
std::vector<std::string> fn;
|
|
|
|
for (const auto &ns : current_ns) {
|
|
if (!ns.empty())
|
|
fn.push_back(ns);
|
|
}
|
|
|
|
fn.push_back(e.name());
|
|
|
|
return fmt::format("{}", fmt::join(fn, "::"));
|
|
}
|
|
|
|
std::string full_name(const cppast::cpp_type &t,
|
|
const cppast::cpp_entity_index &idx, bool inside_class)
|
|
{
|
|
std::string t_ns;
|
|
if (!inside_class) {
|
|
t_ns = ns(cppast::remove_cv(unreferenced(t)), idx);
|
|
}
|
|
|
|
auto t_name = cppast::to_string(t);
|
|
|
|
if (t_ns.size() > 0 &&
|
|
t_name.substr(0, t_name.find("<")).find("::") == std::string::npos)
|
|
return t_ns + "::" + t_name;
|
|
|
|
return cppast::to_string(t);
|
|
}
|
|
|
|
std::string ns(const cppast::cpp_entity &e)
|
|
{
|
|
std::vector<std::string> res{};
|
|
|
|
auto it = e.parent();
|
|
while (it) {
|
|
if (it.value().kind() == cppast::cpp_entity_kind::namespace_t) {
|
|
const auto &ns =
|
|
static_cast<const cppast::cpp_namespace &>(it.value());
|
|
if (!ns.name().empty() && !ns.is_inline())
|
|
res.push_back(it.value().name());
|
|
}
|
|
it = it.value().parent();
|
|
}
|
|
|
|
if (res.empty())
|
|
return "";
|
|
|
|
std::reverse(res.begin(), res.end());
|
|
|
|
return fmt::format("{}", fmt::join(res, "::"));
|
|
}
|
|
|
|
type_safe::optional_ref<const cppast::cpp_namespace> entity_ns(
|
|
const cppast::cpp_entity &e)
|
|
{
|
|
std::vector<std::string> res{};
|
|
|
|
if (e.kind() == cppast::cpp_entity_kind::namespace_t)
|
|
return type_safe::optional_ref<const cppast::cpp_namespace>(
|
|
static_cast<const cppast::cpp_namespace &>(e));
|
|
|
|
auto it = e.parent();
|
|
while (it) {
|
|
if (it.value().kind() == cppast::cpp_entity_kind::namespace_t) {
|
|
return type_safe::optional_ref<const cppast::cpp_namespace>(
|
|
static_cast<const cppast::cpp_namespace &>(it.value()));
|
|
}
|
|
it = it.value().parent();
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
bool is_inside_class(const cppast::cpp_entity &e)
|
|
{
|
|
auto it = e.parent();
|
|
while (it) {
|
|
if (it.value().kind() == cppast::cpp_entity_kind::class_t) {
|
|
return true;
|
|
}
|
|
it = it.value().parent();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::pair<common::model::namespace_, std::string> split_ns(
|
|
const std::string &full_name)
|
|
{
|
|
assert(!full_name.empty());
|
|
|
|
auto name_before_template = ::clanguml::util::split(full_name, "<")[0];
|
|
auto ns = common::model::namespace_{
|
|
::clanguml::util::split(name_before_template, "::")};
|
|
auto name = ns.name();
|
|
ns.pop_back();
|
|
return {ns, name};
|
|
}
|
|
|
|
std::string ns(const cppast::cpp_type &t, const cppast::cpp_entity_index &idx)
|
|
{
|
|
if (t.kind() == cppast::cpp_type_kind::user_defined_t &&
|
|
(static_cast<const cppast::cpp_user_defined_type &>(t)
|
|
.entity()
|
|
.get(idx)
|
|
.size() > 0)) {
|
|
// If this is a user defined type - return the namespace of the
|
|
// entity
|
|
return ns(static_cast<const cppast::cpp_user_defined_type &>(t)
|
|
.entity()
|
|
.get(idx)[0]
|
|
.get());
|
|
}
|
|
else if (t.kind() == cppast::cpp_type_kind::template_instantiation_t) {
|
|
if (static_cast<const cppast::cpp_template_instantiation_type &>(t)
|
|
.primary_template()
|
|
.get(idx)
|
|
.size() > 0) {
|
|
return ns(
|
|
static_cast<const cppast::cpp_template_instantiation_type &>(t)
|
|
.primary_template()
|
|
.get(idx)[0]
|
|
.get());
|
|
}
|
|
else {
|
|
return {};
|
|
}
|
|
}
|
|
else {
|
|
auto canon = cppast::to_string(t.canonical());
|
|
auto full_name = canon.substr(0, canon.find("<"));
|
|
if (full_name.empty()) {
|
|
return "";
|
|
}
|
|
else if (canon.find("type-parameter-") == std::string::npos) {
|
|
// This is an easy case, canonical representation contains full
|
|
// namespace
|
|
auto ns_toks = clanguml::util::split(full_name, "::");
|
|
if (ns_toks.size() > 0)
|
|
ns_toks.pop_back();
|
|
return fmt::format(
|
|
"{}", fmt::join(ns_toks.begin(), ns_toks.end(), "::"));
|
|
}
|
|
else if (canon.find("type-parameter-") == 0) {
|
|
return "";
|
|
}
|
|
else {
|
|
// This is a bug/feature in libclang, where canonical representation
|
|
// of a template type with incomplete specialization doesn't have a
|
|
// full namespace. We have to extract it from the primary template
|
|
const auto &primary_template =
|
|
static_cast<const cppast::cpp_template_instantiation_type &>(t)
|
|
.primary_template();
|
|
if (!primary_template.is_overloaded()) {
|
|
LOG_DBG(
|
|
"Cannot establish namespace for ", cppast::to_string(t));
|
|
return "";
|
|
}
|
|
return ns(primary_template.get(idx)[0].get());
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string fully_prefixed(
|
|
const common::model::namespace_ ¤t_ns, const cppast::cpp_entity &e)
|
|
{
|
|
if (e.name().find("::") != std::string::npos) {
|
|
// the name already contains namespace, but it could be not
|
|
// absolute, i.e. relative to some supernamespace of current
|
|
// namespace context
|
|
std::list<std::string> res;
|
|
|
|
for (const auto &n : clanguml::util::split(e.name(), "::"))
|
|
res.push_back(n);
|
|
|
|
std::list<std::string> prefix_ns;
|
|
for (const auto &n : current_ns) {
|
|
if (!n.empty() && n != res.front())
|
|
prefix_ns.push_back(n);
|
|
else
|
|
break;
|
|
}
|
|
|
|
prefix_ns.reverse();
|
|
for (const auto &n : prefix_ns)
|
|
res.push_front(n);
|
|
|
|
return fmt::format("{}", fmt::join(res, "::"));
|
|
}
|
|
|
|
std::vector<std::string> res{e.name()};
|
|
|
|
auto it = e.parent();
|
|
while (it) {
|
|
if (it.value().kind() == cppast::cpp_entity_kind::namespace_t) {
|
|
if (!it.value().name().empty())
|
|
res.push_back(it.value().name());
|
|
}
|
|
it = it.value().parent();
|
|
}
|
|
|
|
return fmt::format("{}", fmt::join(res.rbegin(), res.rend(), "::"));
|
|
}
|
|
|
|
const cppast::cpp_type &unreferenced(const cppast::cpp_type &t)
|
|
{
|
|
if (t.kind() == cppast::cpp_type_kind::pointer_t)
|
|
return unreferenced(
|
|
static_cast<const cppast::cpp_pointer_type &>(t).pointee());
|
|
else if (t.kind() == cppast::cpp_type_kind::reference_t)
|
|
return unreferenced(
|
|
static_cast<const cppast::cpp_reference_type &>(t).referee());
|
|
|
|
return t;
|
|
}
|
|
|
|
std::vector<class_diagram::model::template_parameter>
|
|
parse_unexposed_template_params(const std::string ¶ms,
|
|
std::function<std::string(const std::string &)> ns_resolve)
|
|
{
|
|
using class_diagram::model::template_parameter;
|
|
|
|
std::vector<template_parameter> res;
|
|
|
|
auto it = params.begin();
|
|
|
|
std::string type{};
|
|
std::vector<template_parameter> nested_params;
|
|
bool complete_class_template{false};
|
|
|
|
while (it != params.end()) {
|
|
if (*it == '<') {
|
|
int nested_level{0};
|
|
auto bracket_match_begin = it + 1;
|
|
auto bracket_match_end = bracket_match_begin;
|
|
while (bracket_match_end != params.end()) {
|
|
if (*bracket_match_end == '<') {
|
|
nested_level++;
|
|
}
|
|
else if (*bracket_match_end == '>') {
|
|
if (nested_level > 0)
|
|
nested_level--;
|
|
else
|
|
break;
|
|
}
|
|
else {
|
|
}
|
|
bracket_match_end++;
|
|
}
|
|
std::string nested_params_str(
|
|
bracket_match_begin, bracket_match_end);
|
|
nested_params =
|
|
parse_unexposed_template_params(nested_params_str, ns_resolve);
|
|
if (nested_params.empty())
|
|
nested_params.emplace_back(
|
|
template_parameter{nested_params_str});
|
|
it = bracket_match_end - 1;
|
|
}
|
|
else if (*it == '>') {
|
|
complete_class_template = true;
|
|
}
|
|
else if (*it == ',') {
|
|
complete_class_template = true;
|
|
}
|
|
else {
|
|
type += *it;
|
|
}
|
|
if (complete_class_template) {
|
|
template_parameter t;
|
|
t.set_type(ns_resolve(clanguml::util::trim(type)));
|
|
type = "";
|
|
for (auto &¶m : nested_params)
|
|
t.add_template_param(std::move(param));
|
|
|
|
res.emplace_back(std::move(t));
|
|
complete_class_template = false;
|
|
}
|
|
it++;
|
|
}
|
|
|
|
if (!type.empty()) {
|
|
template_parameter t;
|
|
t.set_type(ns_resolve(clanguml::util::trim(type)));
|
|
type = "";
|
|
for (auto &¶m : nested_params)
|
|
t.add_template_param(std::move(param));
|
|
|
|
res.emplace_back(std::move(t));
|
|
complete_class_template = false;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
} // namespace util
|
|
} // namespace cx
|
|
} // namespace clanguml
|