1 // Copyright 2004-9 Trustees of Indiana University
2 
3 // Distributed under the Boost Software License, Version 1.0.
4 // (See accompanying file LICENSE_1_0.txt or copy at
5 // http://www.boost.org/LICENSE_1_0.txt)
6 
7 //
8 // read_graphviz_spirit.hpp -
9 //   Initialize a model of the BGL's MutableGraph concept and an associated
10 //  collection of property maps using a graph expressed in the GraphViz
11 // DOT Language.
12 //
13 //   Based on the grammar found at:
14 //   http://www.graphviz.org/cvs/doc/info/lang.html
15 //
16 //   See documentation for this code at:
17 //     http://www.boost.org/libs/graph/doc/read-graphviz.html
18 //
19 
20 // Author: Ronald Garcia
21 //
22 
23 #ifndef BOOST_READ_GRAPHVIZ_SPIRIT_HPP
24 #define BOOST_READ_GRAPHVIZ_SPIRIT_HPP
25 
26 // Phoenix/Spirit set these limits to 3, but I need more.
27 #define PHOENIX_LIMIT 6
28 #define BOOST_SPIRIT_CLOSURE_LIMIT 6
29 
30 
31 #include <boost/spirit/include/classic_multi_pass.hpp>
32 #include <boost/spirit/include/classic_core.hpp>
33 #include <boost/spirit/include/classic_confix.hpp>
34 #include <boost/spirit/include/classic_distinct.hpp>
35 #include <boost/spirit/include/classic_lists.hpp>
36 #include <boost/spirit/include/classic_escape_char.hpp>
37 #include <boost/spirit/include/classic_attribute.hpp>
38 #include <boost/spirit/include/classic_dynamic.hpp>
39 #include <boost/spirit/include/classic_actor.hpp>
40 #include <boost/spirit/include/classic_closure.hpp>
41 #include <boost/spirit/include/phoenix1.hpp>
42 #include <boost/spirit/include/phoenix1_binders.hpp>
43 #include <boost/ref.hpp>
44 #include <boost/function/function2.hpp>
45 #include <boost/type_traits/is_same.hpp>
46 #include <boost/property_map/dynamic_property_map.hpp>
47 #include <boost/graph/graph_traits.hpp>
48 #include <boost/detail/workaround.hpp>
49 #include <algorithm>
50 #include <exception> // for std::exception
51 #include <string>
52 #include <vector>
53 #include <set>
54 #include <utility>
55 #include <map>
56 #include <boost/graph/graphviz.hpp>
57 #include <boost/throw_exception.hpp>
58 
59 namespace phoenix {
60 // Workaround:  std::map::operator[] uses a different return type than all
61 // other standard containers.  Phoenix doesn't account for that.
62 template <typename TK, typename T0, typename T1>
63 struct binary_operator<index_op, std::map<TK,T0>, T1>
64 {
65     typedef typename std::map<TK,T0>::mapped_type& result_type;
evalphoenix::binary_operator66     static result_type eval(std::map<TK,T0>& container, T1 const& index)
67     { return container[index]; }
68 };
69 } // namespace phoenix
70 
71 namespace boost {
72 namespace detail {
73 namespace graph {
74 
75 
76 /////////////////////////////////////////////////////////////////////////////
77 // Application-specific type definitions
78 /////////////////////////////////////////////////////////////////////////////
79 
80 typedef std::set<edge_t> edges_t;
81 typedef std::set<node_t> nodes_t;
82 typedef std::set<id_t> ids_t;
83 typedef std::map<edge_t,ids_t> edge_map_t;
84 typedef std::map<node_t,ids_t> node_map_t;
85 typedef std::map<id_t,id_t> props_t;
86 typedef std::map<id_t,props_t> subgraph_props_t;
87 typedef boost::function2<void, id_t const&, id_t const&> actor_t;
88 typedef std::vector<edge_t> edge_stack_t;
89 typedef std::map<id_t,nodes_t> subgraph_nodes_t;
90 typedef std::map<id_t,edges_t> subgraph_edges_t;
91 
92 
93 
94 /////////////////////////////////////////////////////////////////////////////
95 // Stack frames used by semantic actions
96 /////////////////////////////////////////////////////////////////////////////
97 struct id_closure : boost::spirit::classic::closure<id_closure, node_t> {
98   member1 name;
99 };
100 
101 
102 struct node_id_closure : boost::spirit::classic::closure<node_id_closure, node_t> {
103   member1 name;
104 };
105 
106 struct attr_list_closure : boost::spirit::classic::closure<attr_list_closure, actor_t> {
107   member1 prop_actor;
108 };
109 
110 struct property_closure : boost::spirit::classic::closure<property_closure, id_t, id_t> {
111   member1 key;
112   member2 value;
113 };
114 
115 struct data_stmt_closure : boost::spirit::classic::closure<data_stmt_closure,
116                            nodes_t,nodes_t,edge_stack_t,bool,node_t> {
117   member1 sources;
118   member2 dests;
119   member3 edge_stack;
120   member4 saw_node;
121   member5 active_node;
122 };
123 
124 struct subgraph_closure : boost::spirit::classic::closure<subgraph_closure,
125                           nodes_t, edges_t, node_t> {
126   member1 nodes;
127   member2 edges;
128   member3 name;
129 };
130 
131 /////////////////////////////////////////////////////////////////////////////
132 // Grammar and Actions for the DOT Language
133 /////////////////////////////////////////////////////////////////////////////
134 
135 // Grammar for a dot file.
136 struct dot_grammar : public boost::spirit::classic::grammar<dot_grammar> {
137   mutate_graph& graph_;
dot_grammarboost::detail::graph::dot_grammar138   explicit dot_grammar(mutate_graph& graph) : graph_(graph) { }
139 
140   template <class ScannerT>
141   struct definition {
142 
definitionboost::detail::graph::dot_grammar::definition143     definition(dot_grammar const& self) : self(self), subgraph_depth(0),
144     keyword_p("0-9a-zA-Z_") {
145       using namespace boost::spirit::classic;
146       using namespace phoenix;
147 
148       // RG - Future Work
149       // - Handle multi-line strings using \ line continuation
150       // - Make keywords case insensitive
151       ID
152           = ( lexeme_d[((alpha_p | ch_p('_')) >> *(alnum_p | ch_p('_')))]
153             | real_p
154             | lexeme_d[confix_p('"', *c_escape_ch_p, '"')]
155             | comment_nest_p('<', '>')
156             )[ID.name = construct_<std::string>(arg1,arg2)]
157           ;
158 
159       a_list
160           = list_p( ID[(a_list.key = arg1),
161                        (a_list.value = "true")
162                       ]
163                     >> !( ch_p('=')
164                           >> ID[a_list.value = arg1])
165                           [phoenix::bind(&definition::call_prop_actor)
166                           (var(*this),a_list.key,a_list.value)],!ch_p(','));
167 
168       attr_list = +(ch_p('[') >> !a_list >> ch_p(']'));
169 
170       // RG - disregard port id's for now.
171       port_location
172           = (ch_p(':') >> ID)
173           | (ch_p(':') >> ch_p('(') >> ID >> ch_p(',') >> ID >> ch_p(')'))
174           ;
175 
176       port_angle = ch_p('@') >> ID;
177 
178       port
179           = port_location >> (!port_angle)
180           | port_angle >> (!port_location);
181 
182 
183       node_id
184           = ( ID[node_id.name = arg1] >> (!port) )
185              [phoenix::bind(&definition::memoize_node)(var(*this))];
186 
187       graph_stmt
188           = (ID[graph_stmt.key = arg1] >>
189              ch_p('=') >>
190              ID[graph_stmt.value = arg1])
191         [phoenix::bind(&definition::call_graph_prop)
192          (var(*this),graph_stmt.key,graph_stmt.value)]
193         ; // Graph property.
194 
195       attr_stmt
196           = (as_lower_d[keyword_p("graph")]
197              >> attr_list(actor_t(phoenix::bind(&definition::default_graph_prop)
198                                      (var(*this),arg1,arg2))))
199           | (as_lower_d[keyword_p("node")]
200              >> attr_list(actor_t(phoenix::bind(&definition::default_node_prop)
201                                      (var(*this),arg1,arg2))))
202           | (as_lower_d[keyword_p("edge")]
203              >> attr_list(actor_t(phoenix::bind(&definition::default_edge_prop)
204                                      (var(*this),arg1,arg2))))
205           ;
206 
207       // edge_head is set depending on the graph type (directed/undirected)
208       edgeop = ch_p('-') >> ch_p(boost::ref(edge_head));
209 
210       edgeRHS
211           =  +(    edgeop[(data_stmt.sources = data_stmt.dests),
212                           (data_stmt.dests = construct_<nodes_t>())]
213                    >> ( subgraph[data_stmt.dests = arg1]
214                       | node_id[phoenix::bind(&definition::insert_node)
215                                 (var(*this),data_stmt.dests,arg1)]
216                       )
217                    [phoenix::bind(&definition::activate_edge)
218                     (var(*this),data_stmt.sources,data_stmt.dests,
219                      var(edges), var(default_edge_props))]
220               );
221 
222 
223       // To avoid backtracking, edge, node, and subgraph statements are
224       // processed as one nonterminal.
225       data_stmt
226           = ( subgraph[(data_stmt.dests = arg1),// will get moved in rhs
227                        (data_stmt.saw_node = false)]
228             | node_id[(phoenix::bind(&definition::insert_node)
229                        (var(*this),data_stmt.dests,arg1)),
230                       (data_stmt.saw_node = true),
231 #ifdef BOOST_GRAPH_DEBUG
232                       (std::cout << val("AcTive Node: ") << arg1 << "\n"),
233 #endif // BOOST_GRAPH_DEBUG
234                       (data_stmt.active_node = arg1)]
235             ) >> if_p(edgeRHS)[
236                      !attr_list(
237                        actor_t(phoenix::bind(&definition::edge_prop)
238                                (var(*this),arg1,arg2)))
239                   ].else_p[
240                      if_p(data_stmt.saw_node)[
241                          !attr_list(
242                            actor_t(phoenix::bind(&definition::node_prop)
243                                    (var(*this),arg1,arg2)))
244                      ] // otherwise it's a subgraph, nothing more to do.
245                   ];
246 
247 
248       stmt
249           = graph_stmt
250           | attr_stmt
251           | data_stmt
252           ;
253 
254       stmt_list = *( stmt >> !ch_p(';') );
255 
256       subgraph
257           = !(  as_lower_d[keyword_p("subgraph")]
258                 >> (!ID[(subgraph.name = arg1),
259                         (subgraph.nodes = (var(subgraph_nodes))[arg1]),
260                         (subgraph.edges = (var(subgraph_edges))[arg1])])
261                 )
262           >> ch_p('{')[++var(subgraph_depth)]
263           >> stmt_list
264           >> ch_p('}')[--var(subgraph_depth)]
265                       [(var(subgraph_nodes))[subgraph.name] = subgraph.nodes]
266                       [(var(subgraph_edges))[subgraph.name] = subgraph.edges]
267 
268           | as_lower_d[keyword_p("subgraph")]
269                 >> ID[(subgraph.nodes = (var(subgraph_nodes))[arg1]),
270                       (subgraph.edges = (var(subgraph_edges))[arg1])]
271           ;
272 
273       the_grammar
274           = (!as_lower_d[keyword_p("strict")])
275             >>  ( as_lower_d[keyword_p("graph")][
276                    (var(edge_head) = '-'),
277                    (phoenix::bind(&definition::check_undirected)(var(*this)))]
278                 | as_lower_d[keyword_p("digraph")][
279                    (var(edge_head) = '>'),
280                    (phoenix::bind(&definition::check_directed)(var(*this)))]
281                 )
282             >> (!ID) >> ch_p('{') >> stmt_list >> ch_p('}');
283 
284     } // definition()
285 
286     typedef boost::spirit::classic::rule<ScannerT> rule_t;
287 
startboost::detail::graph::dot_grammar::definition288     rule_t const& start() const { return the_grammar; }
289 
290 
291     //
292     // Semantic actions
293     //
294 
check_undirectedboost::detail::graph::dot_grammar::definition295     void check_undirected() {
296       if(self.graph_.is_directed())
297           boost::throw_exception(boost::undirected_graph_error());
298     }
299 
check_directedboost::detail::graph::dot_grammar::definition300     void check_directed() {
301       if(!self.graph_.is_directed())
302           boost::throw_exception(boost::directed_graph_error());
303     }
304 
memoize_nodeboost::detail::graph::dot_grammar::definition305     void memoize_node() {
306       id_t const& node = node_id.name();
307       props_t& node_props = default_node_props;
308 
309       if(nodes.find(node) == nodes.end()) {
310         nodes.insert(node);
311         self.graph_.do_add_vertex(node);
312 
313         node_map.insert(std::make_pair(node,ids_t()));
314 
315 #ifdef BOOST_GRAPH_DEBUG
316         std::cout << "Add new node " << node << std::endl;
317 #endif // BOOST_GRAPH_DEBUG
318         // Set the default properties for this edge
319         // RG: Here I  would actually set the properties
320         for(props_t::iterator i = node_props.begin();
321             i != node_props.end(); ++i) {
322           set_node_property(node,i->first,i->second);
323         }
324         if(subgraph_depth > 0) {
325           subgraph.nodes().insert(node);
326           // Set the subgraph's default properties as well
327           props_t& props = subgraph_node_props[subgraph.name()];
328           for(props_t::iterator i = props.begin(); i != props.end(); ++i) {
329             set_node_property(node,i->first,i->second);
330           }
331         }
332       } else {
333 #ifdef BOOST_GRAPH_DEBUG
334         std::cout << "See node " << node << std::endl;
335 #endif // BOOST_GRAPH_DEBUG
336       }
337     }
338 
activate_edgeboost::detail::graph::dot_grammar::definition339     void activate_edge(nodes_t& sources, nodes_t& dests, edges_t& edges,
340                        props_t& edge_props) {
341       edge_stack_t& edge_stack = data_stmt.edge_stack();
342       for(nodes_t::iterator i = sources.begin(); i != sources.end(); ++i) {
343         for(nodes_t::iterator j = dests.begin(); j != dests.end(); ++j) {
344           // Create the edge and push onto the edge stack.
345 #ifdef BOOST_GRAPH_DEBUG
346           std::cout << "Edge " << *i << " to " << *j << std::endl;
347 #endif // BOOST_GRAPH_DEBUG
348 
349           edge_t edge = edge_t::new_edge();
350           edge_stack.push_back(edge);
351           edges.insert(edge);
352           edge_map.insert(std::make_pair(edge,ids_t()));
353 
354           // Add the real edge.
355           self.graph_.do_add_edge(edge, *i, *j);
356 
357           // Set the default properties for this edge
358           for(props_t::iterator k = edge_props.begin();
359               k != edge_props.end(); ++k) {
360             set_edge_property(edge,k->first,k->second);
361           }
362           if(subgraph_depth > 0) {
363             subgraph.edges().insert(edge);
364             // Set the subgraph's default properties as well
365             props_t& props = subgraph_edge_props[subgraph.name()];
366             for(props_t::iterator k = props.begin(); k != props.end(); ++k) {
367               set_edge_property(edge,k->first,k->second);
368             }
369           }
370         }
371       }
372     }
373 
374     // node_prop - Assign the property for the current active node.
node_propboost::detail::graph::dot_grammar::definition375     void node_prop(id_t const& key, id_t const& value) {
376       node_t& active_object = data_stmt.active_node();
377       set_node_property(active_object, key, value);
378     }
379 
380     // edge_prop - Assign the property for the current active edges.
edge_propboost::detail::graph::dot_grammar::definition381     void edge_prop(id_t const& key, id_t const& value) {
382       edge_stack_t const& active_edges_ = data_stmt.edge_stack();
383       for (edge_stack_t::const_iterator i = active_edges_.begin();
384            i != active_edges_.end(); ++i) {
385         set_edge_property(*i,key,value);
386       }
387     }
388 
389     // default_graph_prop - Store as a graph property.
default_graph_propboost::detail::graph::dot_grammar::definition390     void default_graph_prop(id_t const& key, id_t const& value) {
391 #ifdef BOOST_GRAPH_DEBUG
392       std::cout << key << " = " << value << std::endl;
393 #endif // BOOST_GRAPH_DEBUG
394         self.graph_.set_graph_property(key, value);
395     }
396 
397     // default_node_prop - declare default properties for any future new nodes
default_node_propboost::detail::graph::dot_grammar::definition398     void default_node_prop(id_t const& key, id_t const& value) {
399       nodes_t& nodes_ =
400         subgraph_depth == 0 ? nodes : subgraph.nodes();
401       props_t& node_props_ =
402         subgraph_depth == 0 ?
403         default_node_props :
404         subgraph_node_props[subgraph.name()];
405 
406       // add this to the selected list of default node properties.
407       node_props_[key] = value;
408       // for each node, set its property to default-constructed value
409       //   if it hasn't been set already.
410       // set the dynamic property map value
411       for(nodes_t::iterator i = nodes_.begin(); i != nodes_.end(); ++i)
412         if(node_map[*i].find(key) == node_map[*i].end()) {
413           set_node_property(*i,key,id_t());
414         }
415     }
416 
417     // default_edge_prop - declare default properties for any future new edges
default_edge_propboost::detail::graph::dot_grammar::definition418    void default_edge_prop(id_t const& key, id_t const& value) {
419       edges_t& edges_ =
420         subgraph_depth == 0 ? edges : subgraph.edges();
421       props_t& edge_props_ =
422         subgraph_depth == 0 ?
423         default_edge_props :
424         subgraph_edge_props[subgraph.name()];
425 
426       // add this to the list of default edge properties.
427       edge_props_[key] = value;
428       // for each edge, set its property to be empty string
429       // set the dynamic property map value
430       for(edges_t::iterator i = edges_.begin(); i != edges_.end(); ++i)
431         if(edge_map[*i].find(key) == edge_map[*i].end())
432           set_edge_property(*i,key,id_t());
433     }
434 
435     // helper function
insert_nodeboost::detail::graph::dot_grammar::definition436     void insert_node(nodes_t& nodes, id_t const& name) {
437       nodes.insert(name);
438     }
439 
call_prop_actorboost::detail::graph::dot_grammar::definition440     void call_prop_actor(std::string const& lhs, std::string const& rhs) {
441       actor_t& actor = attr_list.prop_actor();
442       // If first and last characters of the rhs are double-quotes,
443       // remove them.
444       if (!rhs.empty() && rhs[0] == '"' && rhs[rhs.size() - 1] == '"')
445         actor(lhs, rhs.substr(1, rhs.size()-2));
446       else
447         actor(lhs,rhs);
448     }
449 
call_graph_propboost::detail::graph::dot_grammar::definition450     void call_graph_prop(std::string const& lhs, std::string const& rhs) {
451       // If first and last characters of the rhs are double-quotes,
452       // remove them.
453       if (!rhs.empty() && rhs[0] == '"' && rhs[rhs.size() - 1] == '"')
454         this->default_graph_prop(lhs, rhs.substr(1, rhs.size()-2));
455       else
456         this->default_graph_prop(lhs,rhs);
457     }
458 
set_node_propertyboost::detail::graph::dot_grammar::definition459     void set_node_property(node_t const& node, id_t const& key,
460                            id_t const& value) {
461 
462       // Add the property key to the "set" table to avoid default overwrite
463       node_map[node].insert(key);
464       // Set the user's property map
465       self.graph_.set_node_property(key, node, value);
466 #ifdef BOOST_GRAPH_DEBUG
467       // Tell the world
468       std::cout << node << ": " << key << " = " << value << std::endl;
469 #endif // BOOST_GRAPH_DEBUG
470     }
471 
set_edge_propertyboost::detail::graph::dot_grammar::definition472     void set_edge_property(edge_t const& edge, id_t const& key,
473                            id_t const& value) {
474 
475       // Add the property key to the "set" table to avoid default overwrite
476       edge_map[edge].insert(key);
477       // Set the user's property map
478       self.graph_.set_edge_property(key, edge, value);
479 #ifdef BOOST_GRAPH_DEBUG
480       // Tell the world
481 #if 0 // RG - edge representation changed,
482             std::cout << "(" << edge.first << "," << edge.second << "): "
483 #else
484             std::cout << "an edge: "
485 #endif // 0
486                 << key << " = " << value << std::endl;
487 #endif // BOOST_GRAPH_DEBUG
488     }
489 
490     // Variables explicitly initialized
491     dot_grammar const& self;
492     // if subgraph_depth > 0, then we're processing a subgraph.
493     int subgraph_depth;
494 
495     // Keywords;
496     const boost::spirit::classic::distinct_parser<> keyword_p;
497     //
498     // rules that make up the grammar
499     //
500     boost::spirit::classic::rule<ScannerT,id_closure::context_t> ID;
501     boost::spirit::classic::rule<ScannerT,property_closure::context_t> a_list;
502     boost::spirit::classic::rule<ScannerT,attr_list_closure::context_t> attr_list;
503     rule_t port_location;
504     rule_t port_angle;
505     rule_t port;
506     boost::spirit::classic::rule<ScannerT,node_id_closure::context_t> node_id;
507     boost::spirit::classic::rule<ScannerT,property_closure::context_t> graph_stmt;
508     rule_t attr_stmt;
509     boost::spirit::classic::rule<ScannerT,data_stmt_closure::context_t> data_stmt;
510     boost::spirit::classic::rule<ScannerT,subgraph_closure::context_t> subgraph;
511     rule_t edgeop;
512     rule_t edgeRHS;
513     rule_t stmt;
514     rule_t stmt_list;
515     rule_t the_grammar;
516 
517 
518     // The grammar uses edge_head to dynamically set the syntax for edges
519     // directed graphs: edge_head = '>', and so edgeop = "->"
520     // undirected graphs: edge_head = '-', and so edgeop = "--"
521     char edge_head;
522 
523 
524     //
525     // Support data structures
526     //
527 
528     nodes_t nodes;  // list of node names seen
529     edges_t edges;  // list of edges seen
530     node_map_t node_map; // remember the properties set for each node
531     edge_map_t edge_map; // remember the properties set for each edge
532 
533     subgraph_nodes_t subgraph_nodes;   // per-subgraph lists of nodes
534     subgraph_edges_t subgraph_edges;   // per-subgraph lists of edges
535 
536     props_t default_node_props;  // global default node properties
537     props_t default_edge_props;  // global default edge properties
538     subgraph_props_t subgraph_node_props;  // per-subgraph default node properties
539     subgraph_props_t subgraph_edge_props;  // per-subgraph default edge properties
540   }; // struct definition
541 }; // struct dot_grammar
542 
543 
544 
545 //
546 // dot_skipper - GraphViz whitespace and comment skipper
547 //
548 struct dot_skipper : public boost::spirit::classic::grammar<dot_skipper>
549 {
dot_skipperboost::detail::graph::dot_skipper550     dot_skipper() {}
551 
552     template <typename ScannerT>
553     struct definition
554     {
definitionboost::detail::graph::dot_skipper::definition555         definition(dot_skipper const& /*self*/)  {
556           using namespace boost::spirit::classic;
557           using namespace phoenix;
558           // comment forms
559           skip = eol_p >> comment_p("#")
560                | space_p
561                | comment_p("//")
562 #if BOOST_WORKAROUND(BOOST_MSVC, <= 1400)
563                | confix_p(str_p("/*") ,*anychar_p, str_p("*/"))
564 #else
565                | confix_p("/*" ,*anychar_p, "*/")
566 #endif
567                ;
568 
569 #ifdef BOOST_SPIRIT_DEBUG
570                BOOST_SPIRIT_DEBUG_RULE(skip);
571 #endif
572         }
573 
574       boost::spirit::classic::rule<ScannerT>  skip;
575       boost::spirit::classic::rule<ScannerT> const&
startboost::detail::graph::dot_skipper::definition576       start() const { return skip; }
577     }; // definition
578 }; // dot_skipper
579 
580 } // namespace graph
581 } // namespace detail
582 
583 template <typename MultiPassIterator, typename MutableGraph>
read_graphviz_spirit(MultiPassIterator begin,MultiPassIterator end,MutableGraph & graph,dynamic_properties & dp,std::string const & node_id="node_id")584 bool read_graphviz_spirit(MultiPassIterator begin, MultiPassIterator end,
585                           MutableGraph& graph, dynamic_properties& dp,
586                           std::string const& node_id = "node_id") {
587   using namespace boost;
588   using namespace boost::spirit::classic;
589 
590   typedef MultiPassIterator iterator_t;
591   typedef skip_parser_iteration_policy< boost::detail::graph::dot_skipper>
592     iter_policy_t;
593   typedef scanner_policies<iter_policy_t> scanner_policies_t;
594   typedef scanner<iterator_t, scanner_policies_t> scanner_t;
595 
596   ::boost::detail::graph::mutate_graph_impl<MutableGraph>
597       m_graph(graph, dp, node_id);
598 
599   ::boost::detail::graph::dot_grammar p(m_graph);
600   ::boost::detail::graph::dot_skipper skip_p;
601 
602   iter_policy_t iter_policy(skip_p);
603   scanner_policies_t policies(iter_policy);
604 
605   scanner_t scan(begin, end, policies);
606 
607   bool ok = p.parse(scan);
608   m_graph.finish_building_graph();
609   return ok;
610 }
611 
612 } // namespace boost
613 
614 #endif // BOOST_READ_GRAPHVIZ_SPIRIT_HPP
615