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

如何为std::function标记名称?解析器组合子库功能扩展咨询

实现解析器组合子的两个增强功能

这两个需求确实能让你的解析器组合子库用起来更顺手,咱们一步步来解决——核心思路是用自定义类包装std::function,因为单纯的std::function没法扩展额外属性,也没法自定义隐式转换规则。

1. 让字符串字面量自动转为parser

首先,我们需要把原来的parser typedef从单纯的std::function替换成一个自定义类,给它添加隐式构造函数,接受字符串字面量或std::string,并在构造时自动生成对应的字面量解析逻辑。

2. 给parser添加可访问的名称属性

在自定义类中加入一个std::string成员存储名称,同时提供访问和修改接口,并且在组合子生成新解析器时,自动拼接子解析器的名称,方便调试和日志输出。

完整代码示例

首先假设你已经定义了基础的解析类型:

#include <functional>
#include <string>
#include <utility>

// 输入流包装类(示例实现,根据你的需求调整)
struct parse_stream {
    std::string input;
    size_t pos = 0;

    bool starts_with(const std::string& s) const {
        return input.substr(pos).find(s) == 0;
    }

    void consume(size_t n) {
        pos += n;
    }

    parse_stream remaining() const {
        parse_stream copy = *this;
        return copy;
    }
};

// 解析结果类(示例实现,根据你的需求调整)
struct parse_result {
    bool success = false;
    std::string matched;
    parse_stream remaining;
};

然后是核心的Parser类实现:

class Parser {
private:
    std::function<parse_result(parse_stream)> impl_;
    std::string name_;

public:
    // 从函数对象构造(用于组合子生成新解析器)
    Parser(std::function<parse_result(parse_stream)> impl, std::string name = "")
        : impl_(std::move(impl)), name_(std::move(name)) {}

    // 隐式构造:字符串字面量/string自动转为字面量解析器
    Parser(const char* s) : Parser(std::string(s)) {}
    Parser(std::string s) 
        : impl_([s = std::move(s)](parse_stream stream) -> parse_result {
            if (stream.starts_with(s)) {
                parse_result res;
                res.success = true;
                res.matched = s;
                res.remaining = stream;
                res.remaining.consume(s.size());
                return res;
            }
            return {};
        }),
          name_("literal(\"" + s + "\")") {}

    // 保留可调用性,和原来的std::function用法一致
    parse_result operator()(parse_stream stream) const {
        return impl_(std::move(stream));
    }

    // 名称属性的访问接口
    const std::string& get_name() const { return name_; }
    void set_name(std::string name) { name_ = std::move(name); }
};

// 替换原来的typedef
using parser = Parser;

接下来实现你的组合子(以both和&运算符为例):

// 组合子:同时匹配两个解析器
parser both(parser a, parser b) {
    // 自动生成组合后的名称
    std::string combined_name = "both(" + a.get_name() + ", " + b.get_name() + ")";
    return parser([a = std::move(a), b = std::move(b)](parse_stream stream) {
        auto res_a = a(stream);
        if (!res_a.success) return res_a;
        auto res_b = b(res_a.remaining);
        if (!res_b.success) return res_b;
        
        parse_result final_res;
        final_res.success = true;
        final_res.matched = res_a.matched + res_b.matched;
        final_res.remaining = res_b.remaining;
        return final_res;
    }, combined_name);
}

// 重载&运算符,简化组合写法
parser operator&(parser a, parser b) {
    return both(std::move(a), std::move(b));
}

// 示例:正则表达式解析器(你可以替换成实际的正则实现)
parser regexp(const std::string& pattern, std::string name = "") {
    std::string actual_name = name.empty() ? "regexp(\"" + pattern + "\")" : name;
    return parser([pattern = std::move(pattern)](parse_stream stream) {
        // 这里写你的正则匹配逻辑,返回对应的parse_result
        // 示例:简单匹配第一个符合的字符序列
        parse_result res;
        // ...实际实现
        return res;
    }, actual_name);
}

使用示例

现在你可以像预期的那样写代码了:

// 字符串字面量自动转为parser,直接和regexp组合
parser option = "<" & regexp("[^+>]+");

// 访问解析器的名称
std::cout << option.get_name() << std::endl;
// 输出:both(literal("<"), regexp("[^+>]+"))

额外说明

  • 如果不想完全替换原来的std::function typedef,可以给Parser添加隐式转换到std::function<parse_result(parse_stream)>的运算符,但这样会丢失名称属性,所以更推荐直接使用自定义Parser类。
  • 隐式构造带来了代码简洁性,但要注意避免意外转换(不过在解析器组合的场景下,这种风险很低)。
  • 名称属性非常适合调试:比如解析失败时,可以打印哪个解析器出了问题,或者输出解析器的结构树。

内容的提问来源于stack exchange,提问作者gct

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 11:12:34