使用boost::spirit QI能否强制函数参数的类型顺序与数量?
解决Boost Spirit中统一AST下的函数参数校验问题
要实现统一AST结构体+严格匹配参数数量/类型顺序的需求,核心是在解析阶段绑定每个函数的参数规则,既避免重复定义AST,又能过滤非法输入。以下是两种可行方案:
方案一:基于语义动作的动态校验
通过预定义每个函数的参数类型元数据,在解析后触发语义动作校验参数的数量与类型顺序,校验通过再填充统一AST。
1. 定义元数据与AST
#include <boost/spirit/home/x3.hpp> #include <boost/variant.hpp> #include <unordered_map> #include <vector> #include <string> namespace x3 = boost::spirit::x3; // 标记参数类型 enum class ParamType { Int, String }; // 存储每个函数的预期参数序列:函数名 -> 参数类型列表 const std::unordered_map<std::string, std::vector<ParamType>> func_specs = { {"Function1", {ParamType::Int, ParamType::String}}, {"Function2", {ParamType::String, ParamType::Int, ParamType::String}} }; // 统一AST结构体 struct FunctionTemplate { std::string name; std::vector<boost::variant<int, std::string>> params; };
2. 带类型标记的参数解析规则
先解析单个参数并记录其类型,方便后续校验:
// 存储参数值+类型的临时结构体 struct TypedParam { boost::variant<int, std::string> value; ParamType type; }; // 基础参数解析规则(假设字符串是带双引号的字面量) auto int_parser = x3::int_; auto string_parser = x3::lexeme['"' >> +(x3::char_ - '"') >> '"']; // 绑定解析结果与类型标记 auto typed_int = int_parser[([](auto& ctx) { _val(ctx) = TypedParam{_attr(ctx), ParamType::Int}; })]; auto typed_string = string_parser[([](auto& ctx) { _val(ctx) = TypedParam{_attr(ctx), ParamType::String}; })]; auto any_typed_param = typed_int | typed_string;
3. 带校验的函数解析规则
匹配函数名后解析参数列表,通过语义动作完成校验:
auto func_template = x3::rule<class FuncTplRule, FunctionTemplate>{} = (x3::raw[x3::lit("Function1") | x3::lit("Function2")] >> '(' >> (any_typed_param % ',') >> ')') [([](auto& ctx) { // 提取解析到的函数名和带类型参数 auto& raw_name = _attr(ctx).first; std::string func_name(raw_name.begin(), raw_name.end()); auto& parsed_params = _attr(ctx).second; // 查找当前函数的预期参数规则 auto spec_it = func_specs.find(func_name); if (spec_it == func_specs.end()) { _pass(ctx) = false; // 未知函数,解析失败 return; } auto& expected_types = spec_it->second; // 校验参数数量 if (parsed_params.size() != expected_types.size()) { _pass(ctx) = false; return; } // 校验每个参数的类型顺序 for (size_t i = 0; i < parsed_params.size(); ++i) { if (parsed_params[i].type != expected_types[i]) { _pass(ctx) = false; return; } } // 校验通过,填充统一AST FunctionTemplate result; result.name = func_name; for (auto& p : parsed_params) { result.params.push_back(p.value); } _val(ctx) = result; })];
方案二:绑定特定参数序列到函数名
如果函数数量可控,直接为每个函数定义严格匹配的参数序列规则,复用统一AST结构体,解析规则本身就保证参数合法性,无需额外校验:
// 定义每个函数的严格参数序列 auto func1_params = int_parser >> ',' >> string_parser; auto func2_params = string_parser >> ',' >> int_parser >> ',' >> string_parser; // 统一函数解析规则,分别绑定到AST auto func_template = (x3::lit("Function1") >> '(' >> func1_params >> ')') [([](auto& ctx) { FunctionTemplate ft; ft.name = "Function1"; ft.params.push_back(std::get<0>(_attr(ctx))); ft.params.push_back(std::get<1>(_attr(ctx))); _val(ctx) = ft; })] | (x3::lit("Function2") >> '(' >> func2_params >> ')') [([](auto& ctx) { FunctionTemplate ft; ft.name = "Function2"; ft.params.push_back(std::get<0>(_attr(ctx))); ft.params.push_back(std::get<1>(_attr(ctx))); ft.params.push_back(std::get<2>(_attr(ctx))); _val(ctx) = ft; })];
方案对比
- 方案一适合函数数量极多的场景,只需维护
func_specs元数据即可扩展,无需修改解析规则结构。 - 方案二更直观,解析性能略优,适合函数数量中等的场景,规则本身就明确了参数要求。
内容的提问来源于stack exchange,提问作者Dylan
相关产品推荐
相关产品推荐

