如何使用强制性最小元素的Boost Spirit列表操作员

How to use boost spirit list operator with mandatory minimum amount of elements?

本文关键字:Boost Spirit 列表 操作员 元素 何使用 强制性      更新时间:2023-10-16

我想解析点语言(http://www.graphviz.org/content/dot-language)。这是一种图形定义语言,可以定义节点和之间的连接。一个典型的语句看起来像node1->node2->node3;。使用Boost :: Spirit List操作员%列出节点列表会很不错。天真的方法是:

edge_stmt %=
    (
        node_or_subgraph(_r1) % (qi::eps(_r1) >> tok.diredgeop | tok.undiredgeop)
    ) >> -attr_list;

_r1指示是指向还是无向图,diredgeop->的令牌,undiredgeop分别是--的令牌。

问题是上述代码将仅用于node1;,这是不正确的。为了获得正确的解析器,我必须以某种方式声明%构建的列表中必须至少有两个元素。如何?

文档说a % b等于a >> *(omit[b] >> a),这是不正确的。一个人可能想尝试一下:

edge_stmt %=
    (
        node_or_subgraph(_r1) >>
            +(
                qi::omit
                [
                    qi::eps(_r1) >> tok.diredgeop | tok.undiredgeop
                ] >>
                node_or_subgraph(_r1)
            )
    ) >> -attr_list;

但是此代码不会产生向量,其合成的属性是元组。

我当然可以尝试语义动作,但是有没有语义动作的优雅替代方案?

问题是上面的代码将仅用于node1;,这是不正确的。

您在溪流上游泳。只有 node1;在dot中很好。因此,安排语法也可以反映它可能更容易。

注意事项

GraphViz的语法具有许多特质,使得很难将语法树直接转换为有用的图表表示。

我认为这反映了他们自己的解析功能即时构建图形,而不是试图完全代表源语法树的事实。

这很明显,因为语义是有状态的,在全球状态,词汇范围和子图命名空间之间具有微妙的混合。图中的外观顺序很重要。可以隐式声明节点始终共享"全局"名称空间的事实是一个并不能真正简化事物的因素。

如何解决它

尽管我不是通常是语义动作的粉丝,但似乎在这里使用语义动作。您可以通过用"事件"来响应每个解析规则的"构建器"来模仿GraphViz解析器的行为,从而导致对域模型的适当更改。

但是,我尝试这样做,并且确实很复杂,主要是因为规则的合成类型并不方便构建。

关注点的分离是删除这种瓶颈²的关键。

如果您首先分析纯AST,并从中构建模型,那么解析器和语义逻辑都被大大简化了。

模型

i发明以下Model表示,我认为捕获了GraphViz域模型的语义:

todo(但请参见评论中的更新)

ast

让我们创建一个单独的类集体以表示源文档。注意:

  • 这近距离http://www.graphviz.org/doc/info/lang.html
  • 这是有意的1:1,以后在我们的精神规则上
  • 它与模型(id,noderef,commandf)共享基本类型,因为它们在输入中实际上表示
namespace Model {
    using Id = std::string;
    using Attributes = std::map<Id, std::string>;
    enum class GraphKind { directed, undirected };
    enum class CompassPoint { n, ne, e, se, s, sw, w, nw, c, _ };
    struct NodeRef {
        Id id;
        Id port;
        CompassPoint compass_pt = CompassPoint::_;
    };
}
namespace Ast {
    using Model::CompassPoint;
    using Model::Id;
    using Model::NodeRef;
    using Model::GraphKind;
    using OptionalId = boost::optional<Id>;
    using AList    = Model::Attributes;
    using AttrList = std::vector<AList>;
    struct AttrStmt {
        enum Group { graph, node, edge } group;
        AttrList attributes;
    };
    struct Attr {
        Id key, value;
        operator std::pair<Id, Id>() const { return {key, value}; }
    };
    struct NodeStmt {
        NodeRef node_id;
        AttrList attributes;
    };
    struct EdgeStmt;
    using Stmt = boost::variant<
            AttrStmt,
            Attr,
            NodeStmt,
            boost::recursive_wrapper<EdgeStmt> // includes sub graphs
        >;
    using StmtList = std::vector<Stmt>;
    struct Graph {
        OptionalId id;
        StmtList stmt_list;
    };
    struct EdgeStmt {
        std::vector<boost::variant<NodeRef, Graph> > hops;
        AttrList attributes;
    };
    struct GraphViz {
        bool strict;
        GraphKind kind;
        Graph graph;
    };
}

语法

语法简单地遵循规格,然后向AST映射1:1,因此我们不必做任何魔术(再次)。

namespace Parser {
    namespace qi = boost::spirit::qi;
    namespace px = boost::phoenix;
    template <typename It>
    struct GraphViz : qi::grammar<It, Ast::GraphViz()> {
        GraphViz() : GraphViz::base_type(start) {
            using namespace qi;
            using boost::spirit::repository::qi::distinct;
            auto kw = distinct(char_("a-zA-Z0-9_"));
            start   = skip(space) [matches[kw["strict"]] >> kind_ >> graph_];
            kind_  %= kw["digraph"] >> attr(GraphKind::directed)   [ set_arrow_(px::val("->")) ]
                    | kw["graph"]   >> attr(GraphKind::undirected) [ set_arrow_(px::val("--")) ]
                    ;
            graph_    = -ID_ >> stmt_list;
            subgraph_ = -(kw["subgraph"] >> -ID_) >> stmt_list;
            string_   = '"' >> *('' >> char_ | ~char_('"')) >> '"';
            ID_       = string_ | +char_("a-zA-Z0-9_");
            stmt_list = '{' >> *(stmt >> -lit(';')) >> '}';
            stmt      = attr_stmt
                      | attribute
                      | node_stmt
                      | edge_stmt
                      ;
            attr_stmt = kw[attr_group] >> attr_list;
            attribute = ID_ >> '=' >> ID_;
            node_stmt = node_id >> -attr_list >> !arrow_;
            edge_stmt
                = (node_id | subgraph_) % arrow_ >> -attr_list
                ;
            a_list    = '[' >> *(attribute >> -omit[char_(",;")]) >> ']';
            attr_list = +a_list;
            node_id 
                = ID_ >> (
                        (attr(Ast::Id{}))              >> (':' >> kw[compass_pt]) >> !lit(':')
                      | (':' >> ID_ | attr(Ast::Id{})) >> (':' >> kw[compass_pt] | attr(Ast::CompassPoint::_))
                )
                ;
            BOOST_SPIRIT_DEBUG_NODES(
                 (graph_) (subgraph_)
                 (a_list) (attr_list)
                 (stmt) (attr_stmt) (attribute) (node_stmt) (edge_stmt) (stmt_list)
                 (node_id)
                 (start)(kind_)(ID_)(string_)
            )
        }
      private:
        ////////////////////////
        using Skipper = qi::space_type;
        //////////////////////////////
        // Arrows depend on GraphKind
        qi::symbols<const char> arrow_;
        struct set_arrow_t { // allow dynamic setting
            qi::symbols<const char>& _ref;
            void operator()(const char* op) const { _ref.clear(); _ref.add(op); }
        };
        px::function<set_arrow_t> set_arrow_ { {arrow_} };
        ////////////////////////
        // enums using symbols<>
        struct AttrGroup : qi::symbols<const char, Ast::AttrStmt::Group> {
            AttrGroup() { add
                ("graph", Ast::AttrStmt::Group::graph)
                ("node", Ast::AttrStmt::Group::node)
                ("edge", Ast::AttrStmt::Group::edge);
            }
        } attr_group;
        struct CompassPoint : qi::symbols<const char, Ast::CompassPoint> {
            CompassPoint() { add
                ("n",  Ast::CompassPoint::n)
                ("ne", Ast::CompassPoint::ne)
                ("e",  Ast::CompassPoint::e)
                ("se", Ast::CompassPoint::se)
                ("s",  Ast::CompassPoint::s)
                ("sw", Ast::CompassPoint::sw)
                ("w",  Ast::CompassPoint::w)
                ("nw", Ast::CompassPoint::nw)
                ("c",  Ast::CompassPoint::c)
                ("_",  Ast::CompassPoint::_);
            }
        } compass_pt;
        ////////////////////////
        // productions
        qi::rule<It, Ast::Graph(),     Skipper> graph_, subgraph_;
        qi::rule<It, Ast::AList(),     Skipper> a_list;
        qi::rule<It, Ast::AttrList(),  Skipper> attr_list;
        qi::rule<It, Ast::NodeRef(),   Skipper> node_id; // misnomer
        qi::rule<It, Ast::Stmt(),      Skipper> stmt;
        qi::rule<It, Ast::AttrStmt(),  Skipper> attr_stmt;
        qi::rule<It, Ast::Attr(),      Skipper> attribute;
        qi::rule<It, Ast::NodeStmt(),  Skipper> node_stmt;
        qi::rule<It, Ast::EdgeStmt(),  Skipper> edge_stmt;
        qi::rule<It, Ast::StmtList(),  Skipper> stmt_list;
        // implicit lexemes
        using GraphKind = Ast::GraphKind;
        qi::rule<It, Ast::GraphViz()> start;
        qi::rule<It, GraphKind()> kind_;
        qi::rule<It, Ast::Id()> ID_;
        qi::rule<It, std::string()> string_;
    };
}

演示时间

实际上,本部分已经解析了GraphViz文档。没有在线编译器愿意接受这一点(超过资源限制)。这是此阶段的完全样本:https://wandbox.org/permlink/aymxpd6lzodhoeis

给定以下样本输入,我当时可以想到的许多边缘案例:

digraph G {
    graph [rankdir = LR];
    node[shape=record];
    Bar[label="{ "Bar"|{<p1>pin 1|<p2>     2|<p3>     3|<p4>     4|<p5>     5} }"];
    Foo[label="{ {<data0>data0|<data1>data1|<data2>data2|<data3>data3|<data4>data4}|"Foo" |{<out0>out0|<out1>out1|<out2>out2|<GND>gnd|<ex0>ex0|<hi>hi|<lo>lo} }"];
    Bew[label="{ {<clk>clk|<syn>syn|<mux0>mux0|<mux1>mux1|<signal>signal}|"Bew" |{<out0>out0|<out1>out1|<out2>out2} }"];
    Bar:p1 -> Foo:data0;
    Bar:p2 -> Foo:data1;
    Bar:p3 -> Foo:data2;
    Bar:p4 -> Foo:data3;
    Bar:p5 -> Foo:data4;
    hijacked;
    Foo:out0 -> Bew:mux0;
    Foo:out1 -> Bew:mux1;
    Bew:clk -> Foo:ex0;
    Gate[label="{ {<a>a|<b>b}|OR|{<ab>a|b} }"];
    Foo:hi -> Gate:a;
    Foo:lo -> Gate:b;
    Gate:ab -> Bew:signal;
    subgraph cluster1 {
        graph [
        label=G1];
        2;
        3;
        2 -> 4;
        3 -> 9;
        3 -> 12;
        9 -> 11;
        9 -> 10;
        10 -> 3;
    }
    subgraph cluster2 {
        graph [label=G2];
        10 -> 3;
        more;
        subgraph clusterNested {
            graph [label=nested];
            innermost;
            hijacked[shape=diamond];
        }
    }
    subgraph cluster1 {
        graph [label=G1_override];
        11 -> 4;
        last;
        hijacked;
        subgraph clusterNested {
            graph [label="can override nested?"];
            {
                unnested;
                first_override;
            } [color=red]
        };
    }
    10[shape=circle][color=red];
    10[shape=circle color=red];
    10[shape=circle; color=red,];
    subgraph clusterNested {
        graph [label="can't override nested"];
        unnested;
        second_override;
    }
    more -> last;
}

输出,来自我的机器(Pastebin中有完整的调试信息)

Parse success
(0 directed ( G {(graph {["rankdir"="LR"; ];
                  });
              (node {["shape"="record"; ];
               });
              ((Bar    _) {["label"="{ "Bar"|{<p1>pin 1|<p2>     2|<p3>     3|<p4>     4|<p5>     5} }"; ];
               });
              ((Foo    _) {["label"="{ {<data0>data0|<data1>data1|<data2>data2|<data3>data3|<data4>data4}|"Foo" |{<out0>out0|<out1>out1|<out2>out2|<GND>gnd|<ex0>ex0|<hi>hi|<lo>lo} }"; ];
               });
              ((Bew    _) {["label"="{ {<clk>clk|<syn>syn|<mux0>mux0|<mux1>mux1|<signal>signal}|"Bew" |{<out0>out0|<out1>out1|<out2>out2} }"; ];
               });
              ({(Bar p1    _);
               (Foo data0  _);
               } {});
              ({(Bar p2    _);
               (Foo data1  _);
               } {});
              ({(Bar p3    _);
               (Foo data2  _);
               } {});
              ({(Bar p4    _);
               (Foo data3  _);
               } {});
              ({(Bar p5    _);
               (Foo data4  _);
               } {});
              ((hijacked   _) {});
              ({(Foo out0  _);
               (Bew mux0   _);
               } {});
              ({(Foo out1  _);
               (Bew mux1   _);
               } {});
              ({(Bew clk   _);
               (Foo ex0    _);
               } {});
              ((Gate   _) {["label"="{ {<a>a|<b>b}|OR|{<ab>a|b} }"; ];
               });
              ({(Foo hi    _);
               (Gate a     _);
               } {});
              ({(Foo lo    _);
               (Gate b     _);
               } {});
              ({(Gate ab   _);
               (Bew signal     _);
               } {});
              ((subgraph   _) {});
              ((cluster1   _) {});
              ({(-- {(graph {["label"="G1"; ];
                      });
                 ((2   _) {});
                 ((3   _) {});
                 ({(2      _);
                  (4   _);
                  } {});
                 ({(3      _);
                  (9   _);
                  } {});
                 ({(3      _);
                  (12      _);
                  } {});
                 ({(9      _);
                  (11      _);
                  } {});
                 ({(9      _);
                  (10      _);
                  } {});
                 ({(10     _);
                  (3   _);
                  } {});
              });
              } {});
              ((subgraph   _) {});
              ((cluster2   _) {});
              ({(-- {(graph {["label"="G2"; ];
                      });
                 ({(10     _);
                  (3   _);
                  } {});
                 ((more    _) {});
                 ((subgraph    _) {});
                 ((clusterNested   _) {});
                 ({(-- {(graph {["label"="nested"; ];
                         });
                    ((innermost    _) {});
                    ((hijacked     _) {["shape"="diamond"; ];
                     });
                    });
                  } {});
                 });
               } {});
              ((subgraph   _) {});
              ((cluster1   _) {});
              ({(-- {(graph {["label"="G1_override"; ];
                      });
                 ({(11     _);
                  (4   _);
                  } {});
                 ((last    _) {});
                 ((hijacked    _) {});
                 ((subgraph    _) {});
                 ((clusterNested   _) {});
                 ({(-- {(graph {["label"="can override nested?"; ];
                         });
                    ({(-- {((unnested      _) {});
                       ((first_override    _) {});
                       });
                     } {["color"="red"; ];
                     });
                    });
                  } {});
                 });
               } {});
              ((10     _) {["shape"="circle"; ];
               ["color"="red"; ];
               });
              ((10     _) {["color"="red"; "shape"="circle"; ];
               });
              ((10     _) {["color"="red"; "shape"="circle"; ];
               });
              ((subgraph   _) {});
              ((clusterNested      _) {});
              ({(-- {(graph {["label"="can't override nested"; ];
                      });
                 ((unnested    _) {});
                 ((second_override     _) {});
                 });
               } {});
              ({(more      _);
               (last   _);
               } {});
}))
Remaining unparsed input: '
'

ÅB的促进精神:"语义动作是邪恶的"?

²我对此类复杂性进行了分类"阻抗不匹配",这是我最初从对象关联映射框架

中学到的术语

使列表操作员接受最少数量的元素需要创建一个全新的解析器介绍该行为,因为与repeat不同,它不配置为这样做。我希望以下示例可以帮助您了解如何使用a >> +(omit[b] >> a)实现所需的目标。

在wandbox上运行

#include <iostream>
#include <vector>
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/std_pair.hpp>
namespace qi= boost::spirit::qi;
void print(const std::vector<std::string>& data) 
{
    std::cout << "{ ";
    for(const auto& elem : data) {
        std::cout << elem << " ";
    }
    std::cout << "} ";
}
void print(const std::pair<std::string,double>& data) 
{
    std::cout << "[ " << data.first << ", " << data.second << " ]";
}

template <typename Parser,typename... Attrs>
void parse(const std::string& str, const Parser& parser, Attrs&... attrs)
{
    std::string::const_iterator iter=std::begin(str), end=std::end(str);
    bool result = qi::phrase_parse(iter,end,parser,qi::space,attrs...);
    if(result && iter==end) {
        std::cout << "Success.";
        int ignore[] = {(print(attrs),0)...};
        std::cout << "n";
    } else {
        std::cout << "Something failed. Unparsed: "" << std::string(iter,end) << ""n";
    }
}
template <typename Parser>
void parse_with_nodes(const std::string& str, const Parser& parser) 
{
    std::vector<std::string> nodes;
    parse(str,parser,nodes);
}
template <typename Parser>
void parse_with_nodes_and_attr(const std::string& str, const Parser& parser) 
{
    std::vector<std::string> nodes;
    std::pair<std::string,double> attr_pair;
    parse(str,parser,nodes,attr_pair);
}
int main()
{
    qi::rule<std::string::const_iterator,std::string()> node=+qi::alnum;
    qi::rule<std::string::const_iterator,std::pair<std::string,double>(),qi::space_type> attr = +qi::alpha >> '=' >> qi::double_;

    parse_with_nodes("node1->node2", node % "->");
    parse_with_nodes_and_attr("node1->node2 arrowsize=1.0", node % "->" >> attr);
    parse_with_nodes("node1->node2", node >> +("->" >> node));
    //parse_with_nodes_and_attr("node1->node2 arrowsize=1.0", node >> +("->" >> node) >> attr); 
    qi::rule<std::string::const_iterator,std::vector<std::string>(),qi::space_type> at_least_two_nodes = node >> +("->" >> node);
    parse_with_nodes_and_attr("node1->node2 arrowsize=1.0", at_least_two_nodes >> attr);
}