#include "xref.hpp"
#include "CgStr.hpp"
#include "MultiTuProcessor.hpp"
#include "config.hpp"
#include "debug.hpp"
#include "output.hpp"
#include "highlight.hpp"
#include <boost/filesystem/path.hpp>
#include <iostream>
#include <regex>
using namespace synth;
// A cursor is the main cursor for a declaration in its file iff:
// - It is a definition; or
// - It is the cannonical cursor (as defined by libclang) and the definition
// is not in the same file (≠ translation unit).
static bool isMainCursor(CXCursor cur)
{
CXCursor def = clang_getCursorDefinition(cur);
if (clang_equalCursors(cur, def))
return true;
CXCursor canon = clang_getCanonicalCursor(cur);
if (!clang_equalCursors(cur, canon))
return false;
CXFile defF;
clang_getFileLocation(
clang_getCursorLocation(def), &defF, nullptr, nullptr, nullptr);
CXFile curF;
clang_getFileLocation(
clang_getCursorLocation(canon), &curF, nullptr, nullptr, nullptr);
return !clang_File_isEqual(defF, curF);
}
static std::pair<bool, CXCursor> typeAliasRedeclares(CXCursor decl)
{
CXType aliasTy = clang_getCursorType(decl);
CXType canonTy = clang_getCanonicalType(aliasTy);
CXCursor canonDecl = clang_getTypeDeclaration(canonTy);
std::string aliasQName = simpleQualifiedName(decl);
std::string canonQName = simpleQualifiedName(canonDecl);
return {aliasQName == canonQName, canonDecl};
}
// Returns clang_getCursorReferenced(cur), unless the referenced cursor is a
// type alias or typedef used in an occurence of the "typedef struct S { } S;"
// pattern. In this case, it returns the declaration cursor of the struct
// instead of the type alias.
static CXCursor effectiveReferencedCursor(CXCursor cur)
{
CXCursor refd = clang_getCursorReferenced(cur);
CXCursorKind k = clang_getCursorKind(refd);
if (k != CXCursor_TypedefDecl && k != CXCursor_TypeAliasDecl)
return refd;
CXCursor tyRefd = clang_getCursorReferenced(refd);
if (!clang_equalCursors(refd, tyRefd))
return refd;
auto sameAndCanon = typeAliasRedeclares(refd);
return sameAndCanon.first ? sameAndCanon.second : refd;
}
static std::string relativeUrl(fs::path const& from, fs::path const& to)
{
if (to == from)
return std::string();
fs::path r = to.lexically_relative(from.parent_path());
return r == "." ? std::string() : r.string();
}
static std::string locationUrl(
fs::path const& outPath, SymbolDeclaration const& dst)
{
std::string r = relativeUrl(outPath, dst.file->dstPath());
if (!dst.fileUniqueName.empty()) {
r.reserve(r.size() + 1 + dst.fileUniqueName.size());
r += '#';
r += dst.fileUniqueName;
} else if (dst.lineno != 0) {
std::string anchor = lineId(dst.lineno);
r.reserve(r.size() + 1 + anchor.size());
r += "#";
r += std::move(anchor);
}
return r;
}
static void linkSymbol(Markup& m, SymbolDeclaration const* sym)
{
if (!sym)
return;
m.refd = [sym](fs::path const& outPath, MultiTuProcessor&) {
return locationUrl(outPath, *sym);
};
}
static void linkDeclCursor(Markup& m, CXCursor decl, MultiTuProcessor& state)
{
CXFile file;
unsigned lineno, offset;
clang_getFileLocation(
clang_getCursorLocation(decl), &file, &lineno, nullptr, &offset);
linkSymbol(m, state.referenceSymbol(file, lineno, offset));
}
static void linkInclude(Markup& m, CXCursor incCursor, MultiTuProcessor& state)
{
CXFile file = clang_getIncludedFile(incCursor);
linkSymbol(m, state.referenceSymbol(file, 0, UINT_MAX));
}
static void linkExternalDef(Markup& m, CXCursor cur, MultiTuProcessor& state)
{
CgStr hUsr(clang_getCursorUSR(cur));
if (hUsr.empty())
return;
state.linkExternalRef(m, cur);
m.refd = [usr = hUsr.copy(), extRef = std::move(m.refd)] (
fs::path const& outPath, MultiTuProcessor& state_)
{
SymbolDeclaration const* sym = state_.findMissingDef(usr);
if (sym)
return locationUrl(outPath, *sym);
if (extRef)
return extRef(outPath, state_);
return std::string();
};
}
void synth::linkCursor(Markup& m, CXCursor cur, MultiTuProcessor& state)
{
CXCursorKind k = clang_getCursorKind(cur);
bool shouldRef = false;
if (k == CXCursor_InclusionDirective) {
linkInclude(m, cur, state);
shouldRef = true;
} else {
CXCursor referenced = effectiveReferencedCursor(cur);
bool isref = !clang_Cursor_isNull(referenced)
&& !clang_equalCursors(cur, referenced);
shouldRef = isref;
if (isref) {
CgStr sp = clang_getCursorSpelling(referenced);
linkDeclCursor(m, referenced, state);
} else if (
(m.attrs & (TokenAttributes::flagDef | TokenAttributes::flagDecl))
!= TokenAttributes::none
) {
if ((m.attrs & TokenAttributes::flagDef) == TokenAttributes::none)
linkExternalDef(m, cur, state);
shouldRef = true;
}
}
if (!shouldRef || m.isRef())
return;
state.linkExternalRef(m, std::move(cur));
if (m.isRef())
return;
CXCursor specialized = clang_getSpecializedCursorTemplate(cur);
if (!clang_Cursor_isNull(specialized)
&& !clang_equalCursors(cur, specialized)
) {
linkDeclCursor(m, specialized, state);
}
}
std::string synth::fileUniqueName(CXCursor cur, bool isC)
{
if (!isNamespaceLevelDeclaration(cur))
return std::string();
if (!isMainCursor(cur))
return std::string();
CXCursorKind k = clang_getCursorKind(cur);
if (isTypeCursorKind(k)) {
if (!isC) {
if ((k == CXCursor_TypeAliasDecl || k == CXCursor_TypedefDecl)
&& typeAliasRedeclares(cur).first
) {
return std::string();
}
return simpleQualifiedName(cur);
}
std::string qname = simpleQualifiedName(cur);
if (qname.empty())
return std::string();
// C allows to have struct S and another non-struct symbol S at the
// same time. They are distinguished by writing struct in front e.g.
// "void S(struct S arg) { /* ... */ }"
// A common pattern is thus "typedef struct S { } S;" We need to make
// sure that we handle that correctly.
// Same goes for enum and union.
std::string prefix;
SYNTH_DISCLANGWARN_BEGIN("-Wswitch-enum")
switch (k) {
case CXCursor_StructDecl:
prefix = 's';
break;
case CXCursor_EnumDecl:
prefix = 'e';
break;
case CXCursor_UnionDecl:
prefix = 'u';
break;
case CXCursor_TypedefDecl:
break;
default:
assert("unreachable" && false);
}
SYNTH_DISCLANGWARN_END
if (!prefix.empty())
prefix += ':';
qname.insert(0, std::move(prefix));
return qname;
}
if (isFunctionCursorKind(k)) {
if (isC)
return CgStr(clang_getCursorSpelling(cur)).gets();
std::string r = simpleQualifiedName(cur);
CXType ty = clang_getCursorType(cur);
int nargs = clang_getNumArgTypes(ty);
assert(nargs >= 0);
if (nargs > 0 || clang_isFunctionTypeVariadic(ty)) {
r += ':';
for (int i = 0; i < nargs; ++i) {
if (i != 0)
r += ',';
CXType argTy = clang_getArgType(ty, static_cast<unsigned>(i));
r += CgStr(clang_getTypeSpelling(argTy)).gets();
}
// Leading space, trailing space and space adjancent to a non-word
// character is discardable.
SYNTH_DISCLANGWARN_BEGIN("-Wexit-time-destructors")
static const std::regex discardableSpace(
R"((?:^ )|(?: $)|(\W) | (\W))");
SYNTH_DISCLANGWARN_END
r = std::regex_replace(std::move(r), discardableSpace, "$1$2");
std::replace(r.begin(), r.end(), ' ', '-');
if (clang_isFunctionTypeVariadic(ty)) {
if (nargs > 0)
r += ',';
r += "...";
}
}
return r;
}
return simpleQualifiedName(cur);
}
std::string synth::simpleQualifiedName(CXCursor cur)
{
CXCursorKind k = clang_getCursorKind(cur);
if (clang_isInvalid(k) || clang_isTranslationUnit(k))
return std::string();
CXCursor tpl = clang_getSpecializedCursorTemplate(cur);
CgStr name =
clang_equalCursors(tpl, cur)
|| clang_isInvalid(clang_getCursorKind(tpl))
? clang_getCursorSpelling(cur) : clang_getCursorDisplayName(cur);
CXCursor parent = clang_getCursorSemanticParent(cur);
if (name.empty()) {
if (isTypeCursorKind(k)) {
name = clang_getTypeSpelling(clang_getCursorType(cur));
}
if (name.empty())
return simpleQualifiedName(parent);
}
std::string pname = simpleQualifiedName(parent);
return pname.empty() ? name.get() : std::move(pname) + "::" + name.get();
}
bool synth::isNamespaceLevelDeclaration(CXCursor cur)
{
CXLinkageKind linkage = clang_getCursorLinkage(cur);
if (linkage == CXLinkage_Invalid)
return false;
if (linkage != CXLinkage_NoLinkage)
return true;
CXCursorKind k = clang_getCursorKind(cur);
if (!isTypeAliasCursorKind(k))
return false;
// We need to walk up the parents to check if they are inside a
// function.
do {
cur = clang_getCursorSemanticParent(cur);
k = clang_getCursorKind(cur);
if (isFunctionCursorKind(k))
return false;
} while (!clang_isInvalid(k) && !clang_isTranslationUnit(k));
return true;
}