利用libtooling提取C++中enum值与名的映射

本文介绍了一种利用LibTooling库实现C++枚举类型名与值映射的方法,通过解析AST节点,获取enum声明信息及枚举量的值和名称,最终输出为易于处理的数据格式。

之前的一篇文章中,有思考过如何将enum的值与名进行映射,其中一种方法是利用工具进行预处理生成。最近由于项目中有类似的需求,所以学习了libtooling,写了一个小工具实现映射。

整体流程非常简单:

  1. 匹配enum声明的AST节点。
  2. 获取enum声明AST节点的类型(即枚举类名前面附上作用域)
  3. 获取enum声明所在文件名(用于过滤系统头文件中的枚举)
  4. 获取enum声明所在文件行号(可用于区分同一命名空间的不同匿名enum声明)
  5. 获取enum声明下具体枚举量的值和名

代码如下:

#include "clang/Frontend/FrontendActions.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/AST/PrettyPrinter.h"
#include "clang/AST/Type.h"
#include "llvm/Support/CommandLine.h"

using namespace clang;
using namespace clang::ast_matchers;
using namespace clang::tooling;
using namespace llvm;

DeclarationMatcher EnumDeclMatcher = enumDecl().bind("enum");

class EnumPrinter : public MatchFinder::MatchCallback
{
public:
    virtual void run(const MatchFinder::MatchResult &Result)
    {
        if (const EnumDecl *node = Result.Nodes.getNodeAs<clang::EnumDecl>("enum"))
        {
            LangOptions LO;
            PrintingPolicy PrintPolicy(LO);
            PrintPolicy.AnonymousTagLocations = false;
            PrintPolicy.SuppressTagKeyword = true;

            std::string typeString = QualType::getAsString((QualType(node->getTypeForDecl(), 0)).split(), PrintPolicy);
            PresumedLoc pLoc = node->getASTContext().getSourceManager().getPresumedLoc(node->getLocation());
            printf("%s [%s:%d]\n", typeString.c_str(), pLoc.getFilename(), pLoc.getLine());

            for (auto iter = node->enumerator_begin(); iter != node->enumerator_end(); iter++)
            {
                printf("    %s %ld\n", iter->getNameAsString().c_str(), iter->getInitVal().getExtValue());
            }
            printf("\n");
        }
    }
};

static llvm::cl::OptionCategory MyToolCategory("enumname options");
static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage);

int main(int argc, const char **argv)
{
    CommonOptionsParser OptionsParser(argc, argv, MyToolCategory);
    ClangTool Tool(OptionsParser.getCompilations(),
                   OptionsParser.getSourcePathList());

    EnumPrinter Printer;
    MatchFinder Finder;
    Finder.addMatcher(EnumDeclMatcher, &Printer);

    return Tool.run(newFrontendActionFactory(&Finder).get());
}

测试输入如下:

enum ENUM0
{
    ENUM00,
    ENUM01,
    ENUM02
};

enum class ENUMC0
{
    ENUMC00,
    ENUMC01,
    ENUMC02
};

namespace
{
    enum ENUM1
    {
        ENUM10,
        ENUM11,
        ENUM12
    };
    
    enum class ENUMC1
    {
        ENUMC10,
        ENUMC11,
        ENUMC12
    };
}

namespace ns
{
    enum ENUM2
    {
        ENUM20,
        ENUM21,
        ENUM22
    };

    enum class ENUMC2
    {
        ENUMC20,
        ENUMC21,
        ENUMC22
    };
}

class FOO
{
    enum ENUM3
    {
        ENUM30,
        ENUM31,
        ENUM32
    };

    enum class ENUMC3
    {
        ENUMC30,
        ENUMC31,
        ENUMC32
    };
};

namespace 
{
    namespace 
    {
        enum
        {
            ENUM40,
            ENUM41,
            ENUM42
        };
    }
}

输出为:

ENUM0 [/home/imred/Documents/workspace/enumname/build/../test/main.cpp:1]
    ENUM00 0
    ENUM01 1
    ENUM02 2

ENUMC0 [/home/imred/Documents/workspace/enumname/build/../test/main.cpp:8]
    ENUMC00 0
    ENUMC01 1
    ENUMC02 2

(anonymous namespace)::ENUM1 [/home/imred/Documents/workspace/enumname/build/../test/main.cpp:17]
    ENUM10 0
    ENUM11 1
    ENUM12 2

(anonymous namespace)::ENUMC1 [/home/imred/Documents/workspace/enumname/build/../test/main.cpp:24]
    ENUMC10 0
    ENUMC11 1
    ENUMC12 2

ns::ENUM2 [/home/imred/Documents/workspace/enumname/build/../test/main.cpp:34]
    ENUM20 0
    ENUM21 1
    ENUM22 2

ns::ENUMC2 [/home/imred/Documents/workspace/enumname/build/../test/main.cpp:41]
    ENUMC20 0
    ENUMC21 1
    ENUMC22 2

FOO::ENUM3 [/home/imred/Documents/workspace/enumname/build/../test/main.cpp:51]
    ENUM30 0
    ENUM31 1
    ENUM32 2

FOO::ENUMC3 [/home/imred/Documents/workspace/enumname/build/../test/main.cpp:58]
    ENUMC30 0
    ENUMC31 1
    ENUMC32 2

(anonymous namespace)::(anonymous namespace)::(anonymous) [/home/imred/Documents/workspace/enumname/build/../test/main.cpp:70]
    ENUM40 0
    ENUM41 1
    ENUM42 2

为了容易理解,提取出来的数据都直接打印在屏幕上了,实际应用时,可按照某种数据格式进行输出(xmljson等),或者拼接输出成std::map<int, std::string>

### 自动生成 Enum 映射代码的工具或宏实现 在 C 语言中,可以通过一些工具或宏来实现 `enum` 称之间的映射。以下是一个常见的方法,使用宏定义结合字符串化操作符来生成映射代码。 #### 使用宏实现 Enum 映射 通过宏定义的方式,可以同时定义 `enum` 的对应的字符串称。这种方法利用了预处理器的功能,确保枚举字符串保持同步[^3]。 ```c #define ENUM_ENTRY(name) name, #define ENUM_STRING(name) #name, // 定义枚举 enum InfoStateE { eInfoStateIdle, eInfoStateIniting, eInfoStateInited, eInfoStateReady, eInfoStateActive, eInfoStateStandby, eInfoStateRelease }; // 使用宏生成枚举 enum InfoStateE { ENUM_LIST(ENUM_ENTRY) }; // 使用宏生成字符串数组 const char* InfoStateNames[] = { ENUM_LIST(ENUM_STRING) }; ``` 上述代码中,`ENUM_ENTRY` `ENUM_STRING` 是两个宏,分别用于生成枚举对应的字符串。通过将宏传递给一个统一的宏列表(如 `ENUM_LIST`),可以确保枚举字符串的一致性。 #### 动态生成 Enum 映射代码的工具 如果需要更复杂的场景,可以使用外部工具来自动生成 `enum` 的映射代码。例如,`xmacro` 是一种常用的技术,允许通过一个头文件来定义多个用途的枚举。 **example.h** ```c #define INFO_STATE_ENTRIES \ X(eInfoStateIdle) \ X(eInfoStateIniting) \ X(eInfoStateInited) \ X(eInfoStateReady) \ X(eInfoStateActive) \ X(eInfoStateStandby) \ X(eInfoStateRelease) ``` **example.c** ```c #include "example.h" #define X(name) name, enum InfoStateE { INFO_STATE_ENTRIES }; #undef X #define X(name) #name, const char* InfoStateNames[] = { INFO_STATE_ENTRIES }; #undef X ``` 通过这种方式,`INFO_STATE_ENTRIES` 可以被多次使用,每次根据不同的宏定义生成不同的内容。 #### 示例:查找 Enum 称 以下是一个示例函数,用于根据 `enum` 查找其对应的称。 ```c #include <stdio.h> #include <string.h> const char* getEnumName(enum InfoStateE value) { static const char* names[] = { ENUM_LIST(ENUM_STRING) }; if (value >= 0 && value < sizeof(names) / sizeof(names[0])) { return names[value]; } return "Unknown"; } int main() { printf("%s\n", getEnumName(eInfoStateActive)); // 输出 "eInfoStateActive" return 0; } ``` 此方法确保了 `enum` 字符串称之间的映射关系始终一致,并且易于维护。 ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值