You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++中如何用结构体表示编程语言解析器的嵌套令牌块

C++中如何用结构体表示编程语言解析器的嵌套令牌块

看起来你已经搞定了词法分析的部分,现在卡在了语法块的嵌套表示上——想用结构体但原来的设计没法容纳嵌套的Block(比如表达式里包含标识符块),对吧?我之前做小型解析器的时候也遇到过类似的需求,给你几个不用any的方案,都是基于结构体的,而且比any更靠谱、更符合解析器的类型安全需求:

最推荐:用C++17 std::variant 实现类型安全的嵌套

std::variant 相当于类型安全的标签联合体,它明确限定了能存储的类型集合(比如你的场景里就是Token或Block),比any更可控,不会出现莫名其妙的类型错误,完全适配你用结构体的需求。

具体实现代码

// 假设你已经实现的Token结构体(按你的描述简化)
struct Token {
    std::string value;
    int type; // 比如你定义的TTYPE_IDTYPE、TTYPE_ID等
};

// 前向声明Block,因为BlockContent要用到它
struct Block;

// 定义Block的内容:要么是单个Token,要么是另一个Block
using BlockContent = std::variant<Token, Block>;

// 用枚举代替裸int表示块类型,可读性更强
enum BlockType {
    BLOCK_LINE,
    BLOCK_ID,
    BLOCK_EXPR
};

// 最终的Block结构体,完美支持嵌套
struct Block {
    std::vector<BlockContent> contents; // 容纳Token或子Block
    BlockType type; // 标记当前块是行、标识符还是表达式
};

怎么用这个结构构建你示例中的嵌套块

比如你给出的示例行[F]set [V]v2, ([V]v1 = 12),可以这样构建:

// 构建标识符块:[F]set
Block idSetBlock;
idSetBlock.type = BLOCK_ID;
idSetBlock.contents.emplace_back(Token{"[F]", TTYPE_IDTYPE});
idSetBlock.contents.emplace_back(Token{"set", TTYPE_ID});

// 构建标识符块:[V]v2
Block idV2Block;
idV2Block.type = BLOCK_ID;
idV2Block.contents.emplace_back(Token{"[V]", TTYPE_IDTYPE});
idV2Block.contents.emplace_back(Token{"v2", TTYPE_ID});

// 构建表达式里的标识符块:[V]v1
Block idV1Block;
idV1Block.type = BLOCK_ID;
idV1Block.contents.emplace_back(Token{"[V]", TTYPE_IDTYPE});
idV1Block.contents.emplace_back(Token{"v1", TTYPE_ID});

// 构建表达式块:[V]v1 = 12
Block exprBlock;
exprBlock.type = BLOCK_EXPR;
exprBlock.contents.emplace_back(idV1Block); // 嵌套标识符块
exprBlock.contents.emplace_back(Token{"=", TTYPE_OPERATOR});
exprBlock.contents.emplace_back(Token{"12", TTYPE_INTEGER});

// 构建整个行块
Block lineBlock;
lineBlock.type = BLOCK_LINE;
lineBlock.contents.emplace_back(idSetBlock);
lineBlock.contents.emplace_back(idV2Block);
lineBlock.contents.emplace_back(exprBlock);

怎么访问嵌套内容

用std::visit可以安全地遍历Block的内容,区分是Token还是子Block,比如写个递归打印函数调试:

#include <iostream>
#include <variant>
#include <vector>
#include <string>

// 递归打印Block结构
void printBlock(const Block& block, int indent = 0) {
    std::string indentStr(indent, ' ');
    const char* blockName = nullptr;
    switch(block.type) {
        case BLOCK_LINE: blockName = "line"; break;
        case BLOCK_ID: blockName = "id"; break;
        case BLOCK_EXPR: blockName = "expr"; break;
    }
    std::cout << indentStr << blockName << " {\n";

    for(const auto& content : block.contents) {
        std::visit([&](const auto& val) {
            using T = std::decay_t<decltype(val)>;
            if constexpr (std::is_same_v<T, Token>) {
                // 打印Token
                std::cout << indentStr << "  '" << val.value << "' : TTYPE_XXX\n";
            } else if constexpr (std::is_same_v<T, Block>) {
                // 递归打印子Block
                printBlock(val, indent + 2);
            }
        }, content);
    }

    std::cout << indentStr << "}\n";
}

// 调用示例:printBlock(lineBlock);

兼容C++17之前的方案:手动实现标签联合

如果你的项目不能用C++17,可以自己实现标签联合(Tagged Union),本质是用枚举标记当前联合里存的是什么类型:

struct Token {
    std::string value;
    int type;
};

struct Block;

// 手动实现的Block内容标签联合
struct BlockContent {
    enum Tag { TOKEN, BLOCK } tag;
    union {
        Token token;
        Block block;
    };

    // 因为union里有非POD类型(Block里的vector),需要手动管理构造/析构
    BlockContent(const Token& t) : tag(TOKEN), token(t) {}
    BlockContent(const Block& b) : tag(BLOCK), block(b) {}
    BlockContent(const BlockContent& other) {
        if (other.tag == TOKEN) {
            tag = TOKEN;
            new (&token) Token(other.token);
        } else {
            tag = BLOCK;
            new (&block) Block(other.block);
        }
    }
    ~BlockContent() {
        if (tag == TOKEN) token.~Token();
        else block.~Block();
    }
};

enum BlockType { BLOCK_LINE, BLOCK_ID, BLOCK_EXPR };

struct Block {
    std::vector<BlockContent> contents;
    BlockType type;
};

不过这个方案需要手动管理析构和拷贝,容易出错,所以优先推荐std::variant。

为什么不推荐用any?

any虽然能存任意类型,但它的类型信息是动态的:

  1. 你永远不知道里面实际存的是什么,访问时需要强制类型转换,很容易写出运行时错误;
  2. 调试的时候很难直接看到any里的内容,排查问题麻烦;
  3. 性能比variant差,因为any通常需要堆分配,而variant是栈分配(除非内容本身是堆分配的,比如vector)。

总结

用std::variant的方案完全符合你的需求:

  • 全程用结构体(Token、Block都是结构体),没有复杂的类继承;
  • 完美支持嵌套块(Block里可以放Token或另一个Block);
  • 类型安全,编译时就能检查类型错误,调试和维护都方便。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 11:58:00