开发几何图形生成字符串解析器的算法及相关实现问题咨询
几何图形字符串解析器:算法与实现方案
我来帮你一步步拆解这个几何图形DSL(领域特定语言)解析器的实现问题,从核心算法到具体代码细节都给你理清楚:
整体核心算法思路
这个解析器的核心流程分为三步,是典型的DSL解析逻辑:
- 分词(Tokenization):把原始字符串拆分成有意义的最小单元(比如标识符、运算符、数字、括号等)
- 语法解析(Parsing):根据预设的语法规则,把token流转换成可执行的对象或抽象语法树(AST)
- 求值/执行(Evaluation):基于解析结果,实现点是否在对象内的重复判断逻辑
逐个解决你的具体问题
问题1:解析PRIMITIVE1=SPHERE(RADIUS=5.5)并传递构造参数
要完成这个解析,需要拆分字符串结构并映射到C++对象的构造逻辑:
- 第一步:拆分出变量名(PRIMITIVE1)、图元类型(SPHERE)、参数键值对(RADIUS=5.5)
- 第二步:用工厂模式创建对应图元对象:
- 识别图元类型后,提取参数值(把字符串"5.5"转为double类型)
- 调用对应类的构造函数,比如
std::make_unique<SPHERE>(5.5)
- 示例伪代码逻辑:
// 假设已经拆分出name="PRIMITIVE1", type="SPHERE", params={{"RADIUS",5.5}} if (type == "SPHERE") { double radius = std::stod(params["RADIUS"]); primitives_map[name] = std::make_unique<SPHERE>(radius); } else if (type == "BOX") { double A = std::stod(params["A"]); double B = std::stod(params["B"]); primitives_map[name] = std::make_unique<BOX>(A, B); }
问题2:通过名称标识图元以便OBJECT调用
最直接且高效的方案是用哈希表存储图元实例:
- 使用
std::unordered_map<std::string, std::unique_ptr<PRIMITIVE>>,键是PRIMITIVE1/PRIMITIVE2这类名称,值是对应图元的智能指针(既避免内存泄漏,又支持多态调用) - 当解析OBJECT表达式时,通过名称从map中取出对应的PRIMITIVE实例即可
问题3:是否可以创建pair<PRIMITIVE1,SPHERE(5.5)>并存储在map中
直接用pair<string, PRIMITIVE>不行,因为PRIMITIVE是抽象类,无法直接实例化。正确的做法是:
- 用
std::unordered_map<std::string, std::unique_ptr<PRIMITIVE>>存储,键是名称,值是指向具体图元(SPHERE/BOX)的智能指针 - 如果一定要用pair,可以是
std::pair<std::string, std::unique_ptr<PRIMITIVE>>,但map本身已经是键值结构,直接用map更直观
问题4:解析OBJECT表达式PRIMITIVE2*(-PRIMITIVE1)并构造可重复调用的函数
这里需要把表达式转换成可执行的逻辑,推荐两种实用方案:
方案1:用std::function封装表达式逻辑
- 解析表达式时,把每个标识符替换为map中对应的PRIMITIVE指针
- 把运算符映射到对应的几何操作(比如
*是交集,-是补集,+是并集),结合check_point_inside方法生成逻辑:
比如PRIMITIVE2*(-PRIMITIVE1)对应的逻辑是:[&primitives_map](const Point& p) { auto& box = *primitives_map["PRIMITIVE2"]; auto& sphere = *primitives_map["PRIMITIVE1"]; return box.check_point_inside(p) && !sphere.check_point_inside(p); } - 把这个
std::function<bool(const Point&)>存储到OBJECT类中,每次判断点位置时直接调用即可
方案2:生成抽象语法树(AST)
- 定义AST节点类型(比如OperandNode、UnaryOpNode、BinaryOpNode)
- 解析表达式时构建AST,每次判断点位置时遍历AST计算结果
- 这种方案更灵活,支持复杂表达式的扩展(比如嵌套运算)
问题5:用Boost.Spirit实现的方案
完全可以用Boost.Spirit.Lex分词 + Boost.Spirit.Qi解析的组合,分工更清晰,代码更健壮:
第一步:用Lex分词
定义token类型,把原始字符串拆分成标准化的token流:
- 比如
IDENTIFIER(PRIMITIVE1、SPHERE)、NUMBER(5.5)、OPERATOR(=、*、-、+)、PAREN(())、COMMA(,)、SEMICOLON(;)等
第二步:用Qi定义解析规则
基于token流定义语法规则,同时通过语义动作把解析结果直接绑定到C++对象:
- 图元定义规则:
identifier >> '=' >> primitive_type >> '(' >> parameter_list >> ')' >> ';' - 表达式规则:支持运算符优先级(负号 > * > +/-),比如:
expression = term >> *(('+' >> term) | ('-' >> term)); term = factor >> *('*' >> factor); factor = identifier | ('-' >> identifier); - 语义动作示例:解析图元定义时,直接调用工厂函数创建实例并存储到map中
简化代码示例
#include <boost/spirit/include/qi.hpp> #include <boost/spirit/include/phoenix.hpp> #include <unordered_map> #include <memory> namespace qi = boost::spirit::qi; namespace phx = boost::phoenix; // 修正后的PRIMITIVE基类 class PRIMITIVE { public: virtual bool check_point_inside(const Point&) const = 0; virtual ~PRIMITIVE() = default; // 虚析构避免多态内存泄漏 }; // 辅助结构体存储参数 struct SphereParams { double radius; }; struct BoxParams { double A; double B; }; using PrimitiveParams = boost::variant<SphereParams, BoxParams>; // 工厂函数:创建图元 void create_primitive(const std::string& name, const PrimitiveParams& params, std::unordered_map<std::string, std::unique_ptr<PRIMITIVE>>& primitives) { if (auto* sphere = boost::get<SphereParams>(¶ms)) { primitives[name] = std::make_unique<SPHERE>(sphere->radius); } else if (auto* box = boost::get<BoxParams>(¶ms)) { primitives[name] = std::make_unique<BOX>(box->A, box->B); } } // Qi解析器定义 template<typename Iterator> struct GeometryParser : qi::grammar<Iterator, qi::space_type> { GeometryParser(std::unordered_map<std::string, std::unique_ptr<PRIMITIVE>>& primitives) : GeometryParser::base_type(start) { using qi::double_; using qi::lexeme; using qi::lit; // 匹配标识符 identifier %= lexeme[+(qi::alpha | qi::digit)]; // 解析参数 sphere_param %= lit("RADIUS") >> '=' >> double_; box_param %= (lit("A") >> '=' >> double_) >> ',' >> (lit("B") >> '=' >> double_); // 解析图元定义 primitive_def = identifier >> '=' >> (lit("SPHERE") >> '(' >> sphere_param[phx::construct<SphereParams>(_1)] >> ')' | lit("BOX") >> '(' >> box_param[phx::construct<BoxParams>(_1, _2)] >> ')') >> ';'; // 语义动作:存储图元 primitive_def[phx::bind(&create_primitive, _1, _2, phx::ref(primitives))]; // 起始规则 start = lit("[GEOMETRY]") >> *(primitive_def); } qi::rule<Iterator, std::string(), qi::space_type> identifier; qi::rule<Iterator, double(), qi::space_type> sphere_param; qi::rule<Iterator, std::pair<double, double>(), qi::space_type> box_param; qi::rule<Iterator, std::pair<std::string, PrimitiveParams>(), qi::space_type> primitive_def; qi::rule<Iterator, void(), qi::space_type> start; };
额外的类定义修正建议
你提供的C++类有几个小问题需要调整:
PRIMITIVE的纯虚函数签名错误,应该改为:virtual bool check_point_inside(const Point&) const = 0;(加const,参数为const引用)OBJECT类不应该直接持有PRIMITIVE&,而是持有表达式的std::function或AST节点,这样才能支持任意复杂的表达式- 给
PRIMITIVE添加虚析构函数,避免多态对象的内存泄漏
内容的提问来源于stack exchange,提问作者1604C6F229V
相关产品推荐
相关产品推荐

