如何为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::functiontypedef,可以给Parser添加隐式转换到std::function<parse_result(parse_stream)>的运算符,但这样会丢失名称属性,所以更推荐直接使用自定义Parser类。 - 隐式构造带来了代码简洁性,但要注意避免意外转换(不过在解析器组合的场景下,这种风险很低)。
- 名称属性非常适合调试:比如解析失败时,可以打印哪个解析器出了问题,或者输出解析器的结构树。
内容的提问来源于stack exchange,提问作者gct
相关产品推荐
相关产品推荐

