基于Spirit的解析器能否保留AST节点对应的原始输入字符串(或起止位置)?
当然可以!Boost.Spirit完全能帮你搞定这个需求——不管是保留AST节点对应的原始输入字符串,还是追踪它们的起止位置都没问题,刚好适配你这种过渡阶段要兼容老数据库的场景~
给你几个实用的实现思路,都是Spirit原生支持的:
用位置迭代器追踪全局位置
先把你的输入迭代器包装成qi::position_iterator(如果用的是Spirit Qi)或者x3::position_iterator(X3版本),这样Spirit就能自动追踪每个解析元素的行号、列号,以及精准的起止迭代器位置。之后你只需要在AST节点(比如你的KeyValue结构体)里加几个成员变量,比如boost::iterator_range<It> value_source_range;或者直接存起止迭代器,用来保存对应的输入片段范围。用
raw指令捕获原始匹配范围
这是最直接的方式!对于你需要保留原始字符串的规则(比如Expression),用qi::raw(或X3的x3::raw)指令包裹,它会返回规则匹配到的原始输入范围,而不是解析后的AST节点。你可以把这个范围和解析出的AST节点绑定在一起存储。
举个简化的代码例子:
假设你原来的表达式规则是:qi::rule<Iterator, ExprAST()> Expression;可以改成同时捕获AST和原始范围:
qi::rule<Iterator, std::pair<ExprAST, boost::iterator_range<Iterator>>()> Expression; Expression %= qi::raw[(/* 你原来的表达式解析逻辑,比如加减乘除、括号嵌套等 */)] [qi::_val = std::make_pair(parse_expr_ast(qi::_1), qi::_1)];或者更灵活一点,在解析动作里把原始范围直接存到你的AST节点里:
struct ExprAST { // 原来的AST成员 std::unique_ptr<ExprAST> left; Op op; std::unique_ptr<ExprAST> right; // 新增的原始范围成员 boost::iterator_range<Iterator> source_range; }; Expression %= qi::raw[(/* 原始表达式规则 */)] [qi::_val = build_expr_ast_with_range(qi::_1)];这里的
build_expr_ast_with_range是你自己实现的函数,负责把解析出的AST结构和原始输入范围一起封装到ExprAST对象里。复杂嵌套场景也能精准捕获
不用担心括号嵌套、多层表达式的问题——qi::raw会严格匹配整个规则对应的输入片段,不管内部嵌套多深,都能准确捕获到你需要的原始字符串(包括空格、格式符号这些细节)。如果需要更细粒度的位置(比如每个子表达式的起止),只需要在对应的子规则里重复这个逻辑,给每个子AST节点也加上位置成员就行。把范围转成原始字符串
如果你存储的是iterator_range,之后只需要用std::string(range.begin(), range.end())就能直接转换成原始输入字符串,连空格、换行这些格式细节都能原封不动保留,完全满足老数据库的要求。
最后给你个小提示:如果用的是Spirit X3(较新的版本),API会更简洁,对迭代器的支持也更友好,推荐试试~
备注:内容来源于stack exchange,提问作者llm

