Comment analyser les structs C++ et les commentaires de champ inline en JSON en utilisant clang
Dans notre post précédent Comment analyser la définition de ‘struct’ C++ du code source en utilisant clang nous avons appris comment analyser une définition de struct C++ en utilisant l’analyseur C++ clang. Dans ce post, nous allons étendre cela pour analyser les commentaires de champ inline et convertir la définition du struct en JSON.
Notre struct de test ressemblera à ceci :
parse_struct_with_comments.cpp
typedef struct {
double a; /* That weird parameter */
double b; /* Another weird parameter */
double c;
double d; // This is documentation for d
double e[8]; /* Array of doubles */
} myParameters;et notre objectif est non seulement d’analyser les champs individuels mais aussi leurs commentaires inline, et de convertir toute la définition du struct en JSON.
Code source
parse_struct_with_comments.cpp
#include <clang-c/Index.h>
#include <iostream>
#include <vector>
#include <string>
#include <cstring>
#include <rapidjson/document.h>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/prettywriter.h>
std::string extractInlineComment(CXCursor cursor, const char* sourceCode) {
if (sourceCode == nullptr) {
return "";
}
CXSourceRange range = clang_getCursorExtent(cursor);
CXSourceLocation start = clang_getRangeStart(range);
CXSourceLocation end = clang_getRangeEnd(range);
CXFile file;
unsigned startLine, startColumn, startOffset;
unsigned endLine, endColumn, endOffset;
clang_getExpansionLocation(start, &file, &startLine, &startColumn, &startOffset);
clang_getExpansionLocation(end, &file, &endLine, &endColumn, &endOffset);
// Trouver la ligne contenant cette déclaration de champ
const char* lineStart = sourceCode;
unsigned currentLine = 1;
// Naviguer jusqu'à la ligne contenant le champ
while (currentLine < startLine && *lineStart) {
if (*lineStart == '\n') {
currentLine++;
}
lineStart++;
}
// Trouver la fin de la ligne
const char* lineEnd = lineStart;
while (*lineEnd && *lineEnd != '\n') {
lineEnd++;
}
// Extraire la ligne comme une chaîne
std::string line(lineStart, lineEnd - lineStart);
// Rechercher des commentaires dans cette ligne
std::string comment = "";
// Rechercher les commentaires de style /* */
size_t blockStart = line.find("/*");
if (blockStart != std::string::npos) {
size_t blockEnd = line.find("*/", blockStart);
if (blockEnd != std::string::npos) {
comment = line.substr(blockStart, blockEnd - blockStart + 2);
}
}
// Rechercher les commentaires de style // si aucun commentaire de bloc trouvé
if (comment.empty()) {
size_t lineCommentStart = line.find("//");
if (lineCommentStart != std::string::npos) {
comment = line.substr(lineCommentStart);
}
}
return comment;
}
struct VisitorData {
const char* sourceCode;
rapidjson::Value* fieldsArray;
rapidjson::Document::AllocatorType* allocator;
};
void extractStructFields(CXCursor cursor, const char* sourceCode, rapidjson::Document& doc) {
CXCursorKind kind = clang_getCursorKind(cursor);
if (kind == CXCursor_StructDecl) {
CXString structName = clang_getCursorSpelling(cursor);
std::string structNameStr = clang_getCString(structName);
// Créer l'objet struct en JSON
rapidjson::Value structObj(rapidjson::kObjectType);
rapidjson::Value fieldsArray(rapidjson::kArrayType);
clang_disposeString(structName);
VisitorData data = {sourceCode, &fieldsArray, &doc.GetAllocator()};
clang_visitChildren(cursor, [](CXCursor c, CXCursor parent, CXClientData client_data) {
VisitorData* data = static_cast<VisitorData*>(client_data);
const char* sourceCode = data->sourceCode;
rapidjson::Value* fieldsArray = data->fieldsArray;
rapidjson::Document::AllocatorType* allocator = data->allocator;
CXCursorKind kind = clang_getCursorKind(c);
if (kind == CXCursor_FieldDecl) {
CXString fieldName = clang_getCursorSpelling(c);
CXType fieldType = clang_getCursorType(c);
CXString typeName = clang_getTypeSpelling(fieldType);
std::string comment = extractInlineComment(c, sourceCode);
std::string fieldNameStr = clang_getCString(fieldName);
std::string typeNameStr = clang_getCString(typeName);
// Créer l'objet champ
rapidjson::Value fieldObj(rapidjson::kObjectType);
rapidjson::Value nameVal(fieldNameStr.c_str(), *allocator);
rapidjson::Value typeVal(typeNameStr.c_str(), *allocator);
rapidjson::Value commentVal(comment.c_str(), *allocator);
fieldObj.AddMember("name", nameVal, *allocator);
fieldObj.AddMember("type", typeVal, *allocator);
fieldObj.AddMember("comment", commentVal, *allocator);
fieldsArray->PushBack(fieldObj, *allocator);
clang_disposeString(fieldName);
clang_disposeString(typeName);
}
return CXChildVisit_Continue;
}, &data);
// Ajouter le struct au document
structObj.AddMember("fields", fieldsArray, doc.GetAllocator());
rapidjson::Value structNameVal(structNameStr.c_str(), doc.GetAllocator());
doc.AddMember(structNameVal, structObj, doc.GetAllocator());
}
}
int main() {
CXIndex index = clang_createIndex(0, 0);
const char *code = R"(
typedef struct {
double a; /* That weird parameter */
double b; /* Another weird parameter */
double c;
double d; // This is documentation for d
double e[8]; /* Array of doubles */
} myParameters;
)";
CXUnsavedFile unsavedFile = {"test.cpp", code, (unsigned long)strlen(code)};
CXTranslationUnit unit = clang_parseTranslationUnit(index, "test.cpp", nullptr, 0, &unsavedFile, 1,
CXTranslationUnit_DetailedPreprocessingRecord |
CXTranslationUnit_SkipFunctionBodies);
if (unit == nullptr) {
std::cerr << "Failed to parse translation unit." << std::endl;
return 1;
}
// Créer le document JSON
rapidjson::Document doc;
doc.SetObject();
CXCursor cursor = clang_getTranslationUnitCursor(unit);
auto mainData = std::make_pair(code, &doc);
clang_visitChildren(cursor, [](CXCursor c, CXCursor parent, CXClientData client_data) {
auto* data = static_cast<std::pair<const char*, rapidjson::Document*>*>(client_data);
const char* sourceCode = data->first;
rapidjson::Document* doc = data->second;
extractStructFields(c, sourceCode, *doc);
return CXChildVisit_Continue;
}, &mainData);
// Convertir le JSON en chaîne et afficher
rapidjson::StringBuffer buffer;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
doc.Accept(writer);
std::cout << buffer.GetString() << std::endl;
clang_disposeTranslationUnit(unit);
clang_disposeIndex(index);
return 0;
}Comment compiler et exécuter
Sur Ubuntu, installez les bibliothèques requises avec
install_libclang19.sh
sudo apt -y install libclang-19-devet compilez le code avec
build_parse_struct_with_comments.sh
g++ parse_struct_with_comments.cpp -o parse_struct_with_comments -std=c++17 -I/usr/lib/llvm-19/include -L/usr/lib/llvm-19/lib -lclangEnsuite exécutez le programme :
run_parse_struct_with_comments.sh
./parse_struct_with_commentsIl affichera le JSON suivant :
parse_struct_output.json
{
"myParameters": {
"fields": [
{
"name": "a",
"type": "double",
"comment": "/* That weird parameter */"
},
{
"name": "b",
"type": "double",
"comment": "/* Another weird parameter */"
},
{
"name": "c",
"type": "double",
"comment": ""
},
{
"name": "d",
"type": "double",
"comment": "// This is documentation for d"
},
{
"name": "e",
"type": "double[8]",
"comment": "/* Array of doubles */"
}
]
}
}Variante du code source avec E/S de fichier
parse_struct_with_comments_fileio.cpp
#include <clang-c/Index.h>
#include <iostream>
#include <vector>
#include <string>
#include <cstring>
#include <fstream>
#include <rapidjson/document.h>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/prettywriter.h>
std::string extractInlineComment(CXCursor cursor, const char* sourceCode) {
if (sourceCode == nullptr) {
return "";
}
CXSourceRange range = clang_getCursorExtent(cursor);
CXSourceLocation start = clang_getRangeStart(range);
CXSourceLocation end = clang_getRangeEnd(range);
CXFile file;
unsigned startLine, startColumn, startOffset;
unsigned endLine, endColumn, endOffset;
clang_getExpansionLocation(start, &file, &startLine, &startColumn, &startOffset);
clang_getExpansionLocation(end, &file, &endLine, &endColumn, &endOffset);
// Trouver la ligne contenant cette déclaration de champ
const char* lineStart = sourceCode;
unsigned currentLine = 1;
// Naviguer jusqu'à la ligne contenant le champ
while (currentLine < startLine && *lineStart) {
if (*lineStart == '\n') {
currentLine++;
}
lineStart++;
}
// Trouver la fin de la ligne
const char* lineEnd = lineStart;
while (*lineEnd && *lineEnd != '\n') {
lineEnd++;
}
// Extraire la ligne comme une chaîne
std::string line(lineStart, lineEnd - lineStart);
// Rechercher des commentaires dans cette ligne
std::string comment = "";
// Rechercher les commentaires de style /* */
size_t blockStart = line.find("/*");
if (blockStart != std::string::npos) {
size_t blockEnd = line.find("*/", blockStart);
if (blockEnd != std::string::npos) {
comment = line.substr(blockStart, blockEnd - blockStart + 2);
}
}
// Rechercher les commentaires de style // si aucun commentaire de bloc trouvé
if (comment.empty()) {
size_t lineCommentStart = line.find("//");
if (lineCommentStart != std::string::npos) {
comment = line.substr(lineCommentStart);
}
}
return comment;
}
struct VisitorData {
const char* sourceCode;
rapidjson::Value* fieldsArray;
rapidjson::Document::AllocatorType* allocator;
};
void extractStructFields(CXCursor cursor, const char* sourceCode, rapidjson::Document& doc) {
CXCursorKind kind = clang_getCursorKind(cursor);
if (kind == CXCursor_StructDecl) {
CXString structName = clang_getCursorSpelling(cursor);
std::string structNameStr = clang_getCString(structName);
// Créer l'objet struct en JSON
rapidjson::Value structObj(rapidjson::kObjectType);
rapidjson::Value fieldsArray(rapidjson::kArrayType);
clang_disposeString(structName);
VisitorData data = {sourceCode, &fieldsArray, &doc.GetAllocator()};
clang_visitChildren(cursor, [](CXCursor c, CXCursor parent, CXClientData client_data) {
VisitorData* data = static_cast<VisitorData*>(client_data);
const char* sourceCode = data->sourceCode;
rapidjson::Value* fieldsArray = data->fieldsArray;
rapidjson::Document::AllocatorType* allocator = data->allocator;
CXCursorKind kind = clang_getCursorKind(c);
if (kind == CXCursor_FieldDecl) {
CXString fieldName = clang_getCursorSpelling(c);
CXType fieldType = clang_getCursorType(c);
CXString typeName = clang_getTypeSpelling(fieldType);
std::string comment = extractInlineComment(c, sourceCode);
std::string fieldNameStr = clang_getCString(fieldName);
std::string typeNameStr = clang_getCString(typeName);
// Créer l'objet champ
rapidjson::Value fieldObj(rapidjson::kObjectType);
rapidjson::Value nameVal(fieldNameStr.c_str(), *allocator);
rapidjson::Value typeVal(typeNameStr.c_str(), *allocator);
rapidjson::Value commentVal(comment.c_str(), *allocator);
fieldObj.AddMember("name", nameVal, *allocator);
fieldObj.AddMember("type", typeVal, *allocator);
fieldObj.AddMember("comment", commentVal, *allocator);
fieldsArray->PushBack(fieldObj, *allocator);
clang_disposeString(fieldName);
clang_disposeString(typeName);
}
return CXChildVisit_Continue;
}, &data);
// Ajouter le struct au document
structObj.AddMember("fields", fieldsArray, doc.GetAllocator());
rapidjson::Value structNameVal(structNameStr.c_str(), doc.GetAllocator());
doc.AddMember(structNameVal, structObj, doc.GetAllocator());
}
}
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <filename>" << std::endl;
return 1;
}
const char* filename = argv[1];
CXIndex index = clang_createIndex(0, 0);
CXTranslationUnit unit = clang_parseTranslationUnit(index, filename, nullptr, 0, nullptr, 0,
CXTranslationUnit_DetailedPreprocessingRecord |
CXTranslationUnit_SkipFunctionBodies);
if (unit == nullptr) {
std::cerr << "Failed to parse translation unit." << std::endl;
return 1;
}
// Créer le document JSON
rapidjson::Document doc;
doc.SetObject();
// Lire le contenu du fichier source
std::ifstream file(filename, std::ios::binary);
if (!file) {
std::cerr << "Failed to open file: " << filename << std::endl;
return 1;
}
file.seekg(0, std::ios::end);
size_t fileSize = file.tellg();
file.seekg(0, std::ios::beg);
std::string sourceCode(fileSize, '\0');
file.read(&sourceCode[0], fileSize);
file.close();
struct CallbackData {
rapidjson::Document* doc;
const char* sourceCode;
};
CallbackData callbackData = {&doc, sourceCode.c_str()};
CXCursor cursor = clang_getTranslationUnitCursor(unit);
clang_visitChildren(cursor, [](CXCursor c, CXCursor parent, CXClientData client_data) {
CallbackData* data = static_cast<CallbackData*>(client_data);
extractStructFields(c, data->sourceCode, *(data->doc));
return CXChildVisit_Continue;
}, &callbackData);
// Convertir le JSON en chaîne et afficher
rapidjson::StringBuffer buffer;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
doc.Accept(writer);
std::cout << buffer.GetString() << std::endl;
clang_disposeTranslationUnit(unit);
clang_disposeIndex(index);
return 0;
}Check out similar posts by category:
C/C++, Clang, Source Introspection
If this post helped you, please consider buying me a coffee or donating via PayPal to support research & publishing of new posts on TechOverflow