blob: 7f94e45fbbb8febe4ca7d4e900e003891147c1ed [file] [log] [blame]
// Copyright 2004-9 Trustees of Indiana University
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
// read_graphviz_spirit.hpp -
// Initialize a model of the BGL's MutableGraph concept and an associated
// collection of property maps using a graph expressed in the GraphViz
// DOT Language.
//
// Based on the grammar found at:
// http://www.graphviz.org/cvs/doc/info/lang.html
//
// See documentation for this code at:
// http://www.boost.org/libs/graph/doc/read-graphviz.html
//
// Author: Ronald Garcia
//
#ifndef BOOST_READ_GRAPHVIZ_SPIRIT_HPP
#define BOOST_READ_GRAPHVIZ_SPIRIT_HPP
// Phoenix/Spirit set these limits to 3, but I need more.
#define PHOENIX_LIMIT 6
#define BOOST_SPIRIT_CLOSURE_LIMIT 6
#include <boost/spirit/include/classic_multi_pass.hpp>
#include <boost/spirit/include/classic_core.hpp>
#include <boost/spirit/include/classic_confix.hpp>
#include <boost/spirit/include/classic_distinct.hpp>
#include <boost/spirit/include/classic_lists.hpp>
#include <boost/spirit/include/classic_escape_char.hpp>
#include <boost/spirit/include/classic_attribute.hpp>
#include <boost/spirit/include/classic_dynamic.hpp>
#include <boost/spirit/include/classic_actor.hpp>
#include <boost/spirit/include/classic_closure.hpp>
#include <boost/spirit/include/phoenix1.hpp>
#include <boost/spirit/include/phoenix1_binders.hpp>
#include <boost/ref.hpp>
#include <boost/function/function2.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/property_map/dynamic_property_map.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/detail/workaround.hpp>
#include <algorithm>
#include <exception> // for std::exception
#include <string>
#include <vector>
#include <set>
#include <utility>
#include <map>
#include <boost/graph/graphviz.hpp>
#include <boost/throw_exception.hpp>
namespace phoenix {
// Workaround: std::map::operator[] uses a different return type than all
// other standard containers. Phoenix doesn't account for that.
template <typename TK, typename T0, typename T1>
struct binary_operator<index_op, std::map<TK,T0>, T1>
{
typedef typename std::map<TK,T0>::mapped_type& result_type;
static result_type eval(std::map<TK,T0>& container, T1 const& index)
{ return container[index]; }
};
} // namespace phoenix
namespace boost {
namespace detail {
namespace graph {
/////////////////////////////////////////////////////////////////////////////
// Application-specific type definitions
/////////////////////////////////////////////////////////////////////////////
typedef std::set<edge_t> edges_t;
typedef std::set<node_t> nodes_t;
typedef std::set<id_t> ids_t;
typedef std::map<edge_t,ids_t> edge_map_t;
typedef std::map<node_t,ids_t> node_map_t;
typedef std::map<id_t,id_t> props_t;
typedef std::map<id_t,props_t> subgraph_props_t;
typedef boost::function2<void, id_t const&, id_t const&> actor_t;
typedef std::vector<edge_t> edge_stack_t;
typedef std::map<id_t,nodes_t> subgraph_nodes_t;
typedef std::map<id_t,edges_t> subgraph_edges_t;
/////////////////////////////////////////////////////////////////////////////
// Stack frames used by semantic actions
/////////////////////////////////////////////////////////////////////////////
struct id_closure : boost::spirit::classic::closure<id_closure, node_t> {
member1 name;
};
struct node_id_closure : boost::spirit::classic::closure<node_id_closure, node_t> {
member1 name;
};
struct attr_list_closure : boost::spirit::classic::closure<attr_list_closure, actor_t> {
member1 prop_actor;
};
struct property_closure : boost::spirit::classic::closure<property_closure, id_t, id_t> {
member1 key;
member2 value;
};
struct data_stmt_closure : boost::spirit::classic::closure<data_stmt_closure,
nodes_t,nodes_t,edge_stack_t,bool,node_t> {
member1 sources;
member2 dests;
member3 edge_stack;
member4 saw_node;
member5 active_node;
};
struct subgraph_closure : boost::spirit::classic::closure<subgraph_closure,
nodes_t, edges_t, node_t> {
member1 nodes;
member2 edges;
member3 name;
};
/////////////////////////////////////////////////////////////////////////////
// Grammar and Actions for the DOT Language
/////////////////////////////////////////////////////////////////////////////
// Grammar for a dot file.
struct dot_grammar : public boost::spirit::classic::grammar<dot_grammar> {
mutate_graph& graph_;
explicit dot_grammar(mutate_graph& graph) : graph_(graph) { }
template <class ScannerT>
struct definition {
definition(dot_grammar const& self) : self(self), subgraph_depth(0),
keyword_p("0-9a-zA-Z_") {
using namespace boost::spirit::classic;
using namespace phoenix;
// RG - Future Work
// - Handle multi-line strings using \ line continuation
// - Make keywords case insensitive
ID
= ( lexeme_d[((alpha_p | ch_p('_')) >> *(alnum_p | ch_p('_')))]
| real_p
| lexeme_d[confix_p('"', *c_escape_ch_p, '"')]
| comment_nest_p('<', '>')
)[ID.name = construct_<std::string>(arg1,arg2)]
;
a_list
= list_p( ID[(a_list.key = arg1),
(a_list.value = "true")
]
>> !( ch_p('=')
>> ID[a_list.value = arg1])
[phoenix::bind(&definition::call_prop_actor)
(var(*this),a_list.key,a_list.value)],!ch_p(','));
attr_list = +(ch_p('[') >> !a_list >> ch_p(']'));
// RG - disregard port id's for now.
port_location
= (ch_p(':') >> ID)
| (ch_p(':') >> ch_p('(') >> ID >> ch_p(',') >> ID >> ch_p(')'))
;
port_angle = ch_p('@') >> ID;
port
= port_location >> (!port_angle)
| port_angle >> (!port_location);
node_id
= ( ID[node_id.name = arg1] >> (!port) )
[phoenix::bind(&definition::memoize_node)(var(*this))];
graph_stmt
= (ID[graph_stmt.key = arg1] >>
ch_p('=') >>
ID[graph_stmt.value = arg1])
[phoenix::bind(&definition::call_graph_prop)
(var(*this),graph_stmt.key,graph_stmt.value)]
; // Graph property.
attr_stmt
= (as_lower_d[keyword_p("graph")]
>> attr_list(actor_t(phoenix::bind(&definition::default_graph_prop)
(var(*this),arg1,arg2))))
| (as_lower_d[keyword_p("node")]
>> attr_list(actor_t(phoenix::bind(&definition::default_node_prop)
(var(*this),arg1,arg2))))
| (as_lower_d[keyword_p("edge")]
>> attr_list(actor_t(phoenix::bind(&definition::default_edge_prop)
(var(*this),arg1,arg2))))
;
// edge_head is set depending on the graph type (directed/undirected)
edgeop = ch_p('-') >> ch_p(boost::ref(edge_head));
edgeRHS
= +( edgeop[(data_stmt.sources = data_stmt.dests),
(data_stmt.dests = construct_<nodes_t>())]
>> ( subgraph[data_stmt.dests = arg1]
| node_id[phoenix::bind(&definition::insert_node)
(var(*this),data_stmt.dests,arg1)]
)
[phoenix::bind(&definition::activate_edge)
(var(*this),data_stmt.sources,data_stmt.dests,
var(edges), var(default_edge_props))]
);
// To avoid backtracking, edge, node, and subgraph statements are
// processed as one nonterminal.
data_stmt
= ( subgraph[(data_stmt.dests = arg1),// will get moved in rhs
(data_stmt.saw_node = false)]
| node_id[(phoenix::bind(&definition::insert_node)
(var(*this),data_stmt.dests,arg1)),
(data_stmt.saw_node = true),
#ifdef BOOST_GRAPH_DEBUG
(std::cout << val("AcTive Node: ") << arg1 << "\n"),
#endif // BOOST_GRAPH_DEBUG
(data_stmt.active_node = arg1)]
) >> if_p(edgeRHS)[
!attr_list(
actor_t(phoenix::bind(&definition::edge_prop)
(var(*this),arg1,arg2)))
].else_p[
if_p(data_stmt.saw_node)[
!attr_list(
actor_t(phoenix::bind(&definition::node_prop)
(var(*this),arg1,arg2)))
] // otherwise it's a subgraph, nothing more to do.
];
stmt
= graph_stmt
| attr_stmt
| data_stmt
;
stmt_list = *( stmt >> !ch_p(';') );
subgraph
= !( as_lower_d[keyword_p("subgraph")]
>> (!ID[(subgraph.name = arg1),
(subgraph.nodes = (var(subgraph_nodes))[arg1]),
(subgraph.edges = (var(subgraph_edges))[arg1])])
)
>> ch_p('{')[++var(subgraph_depth)]
>> stmt_list
>> ch_p('}')[--var(subgraph_depth)]
[(var(subgraph_nodes))[subgraph.name] = subgraph.nodes]
[(var(subgraph_edges))[subgraph.name] = subgraph.edges]
| as_lower_d[keyword_p("subgraph")]
>> ID[(subgraph.nodes = (var(subgraph_nodes))[arg1]),
(subgraph.edges = (var(subgraph_edges))[arg1])]
;
the_grammar
= (!as_lower_d[keyword_p("strict")])
>> ( as_lower_d[keyword_p("graph")][
(var(edge_head) = '-'),
(phoenix::bind(&definition::check_undirected)(var(*this)))]
| as_lower_d[keyword_p("digraph")][
(var(edge_head) = '>'),
(phoenix::bind(&definition::check_directed)(var(*this)))]
)
>> (!ID) >> ch_p('{') >> stmt_list >> ch_p('}');
} // definition()
typedef boost::spirit::classic::rule<ScannerT> rule_t;
rule_t const& start() const { return the_grammar; }
//
// Semantic actions
//
void check_undirected() {
if(self.graph_.is_directed())
boost::throw_exception(boost::undirected_graph_error());
}
void check_directed() {
if(!self.graph_.is_directed())
boost::throw_exception(boost::directed_graph_error());
}
void memoize_node() {
id_t const& node = node_id.name();
props_t& node_props = default_node_props;
if(nodes.find(node) == nodes.end()) {
nodes.insert(node);
self.graph_.do_add_vertex(node);
node_map.insert(std::make_pair(node,ids_t()));
#ifdef BOOST_GRAPH_DEBUG
std::cout << "Add new node " << node << std::endl;
#endif // BOOST_GRAPH_DEBUG
// Set the default properties for this edge
// RG: Here I would actually set the properties
for(props_t::iterator i = node_props.begin();
i != node_props.end(); ++i) {
set_node_property(node,i->first,i->second);
}
if(subgraph_depth > 0) {
subgraph.nodes().insert(node);
// Set the subgraph's default properties as well
props_t& props = subgraph_node_props[subgraph.name()];
for(props_t::iterator i = props.begin(); i != props.end(); ++i) {
set_node_property(node,i->first,i->second);
}
}
} else {
#ifdef BOOST_GRAPH_DEBUG
std::cout << "See node " << node << std::endl;
#endif // BOOST_GRAPH_DEBUG
}
}
void activate_edge(nodes_t& sources, nodes_t& dests, edges_t& edges,
props_t& edge_props) {
edge_stack_t& edge_stack = data_stmt.edge_stack();
for(nodes_t::iterator i = sources.begin(); i != sources.end(); ++i) {
for(nodes_t::iterator j = dests.begin(); j != dests.end(); ++j) {
// Create the edge and push onto the edge stack.
#ifdef BOOST_GRAPH_DEBUG
std::cout << "Edge " << *i << " to " << *j << std::endl;
#endif // BOOST_GRAPH_DEBUG
edge_t edge = edge_t::new_edge();
edge_stack.push_back(edge);
edges.insert(edge);
edge_map.insert(std::make_pair(edge,ids_t()));
// Add the real edge.
self.graph_.do_add_edge(edge, *i, *j);
// Set the default properties for this edge
for(props_t::iterator k = edge_props.begin();
k != edge_props.end(); ++k) {
set_edge_property(edge,k->first,k->second);
}
if(subgraph_depth > 0) {
subgraph.edges().insert(edge);
// Set the subgraph's default properties as well
props_t& props = subgraph_edge_props[subgraph.name()];
for(props_t::iterator k = props.begin(); k != props.end(); ++k) {
set_edge_property(edge,k->first,k->second);
}
}
}
}
}
// node_prop - Assign the property for the current active node.
void node_prop(id_t const& key, id_t const& value) {
node_t& active_object = data_stmt.active_node();
set_node_property(active_object, key, value);
}
// edge_prop - Assign the property for the current active edges.
void edge_prop(id_t const& key, id_t const& value) {
edge_stack_t const& active_edges_ = data_stmt.edge_stack();
for (edge_stack_t::const_iterator i = active_edges_.begin();
i != active_edges_.end(); ++i) {
set_edge_property(*i,key,value);
}
}
// default_graph_prop - Store as a graph property.
void default_graph_prop(id_t const& key, id_t const& value) {
#ifdef BOOST_GRAPH_DEBUG
std::cout << key << " = " << value << std::endl;
#endif // BOOST_GRAPH_DEBUG
self.graph_.set_graph_property(key, value);
}
// default_node_prop - declare default properties for any future new nodes
void default_node_prop(id_t const& key, id_t const& value) {
nodes_t& nodes_ =
subgraph_depth == 0 ? nodes : subgraph.nodes();
props_t& node_props_ =
subgraph_depth == 0 ?
default_node_props :
subgraph_node_props[subgraph.name()];
// add this to the selected list of default node properties.
node_props_[key] = value;
// for each node, set its property to default-constructed value
// if it hasn't been set already.
// set the dynamic property map value
for(nodes_t::iterator i = nodes_.begin(); i != nodes_.end(); ++i)
if(node_map[*i].find(key) == node_map[*i].end()) {
set_node_property(*i,key,id_t());
}
}
// default_edge_prop - declare default properties for any future new edges
void default_edge_prop(id_t const& key, id_t const& value) {
edges_t& edges_ =
subgraph_depth == 0 ? edges : subgraph.edges();
props_t& edge_props_ =
subgraph_depth == 0 ?
default_edge_props :
subgraph_edge_props[subgraph.name()];
// add this to the list of default edge properties.
edge_props_[key] = value;
// for each edge, set its property to be empty string
// set the dynamic property map value
for(edges_t::iterator i = edges_.begin(); i != edges_.end(); ++i)
if(edge_map[*i].find(key) == edge_map[*i].end())
set_edge_property(*i,key,id_t());
}
// helper function
void insert_node(nodes_t& nodes, id_t const& name) {
nodes.insert(name);
}
void call_prop_actor(std::string const& lhs, std::string const& rhs) {
actor_t& actor = attr_list.prop_actor();
// If first and last characters of the rhs are double-quotes,
// remove them.
if (!rhs.empty() && rhs[0] == '"' && rhs[rhs.size() - 1] == '"')
actor(lhs, rhs.substr(1, rhs.size()-2));
else
actor(lhs,rhs);
}
void call_graph_prop(std::string const& lhs, std::string const& rhs) {
// If first and last characters of the rhs are double-quotes,
// remove them.
if (!rhs.empty() && rhs[0] == '"' && rhs[rhs.size() - 1] == '"')
this->default_graph_prop(lhs, rhs.substr(1, rhs.size()-2));
else
this->default_graph_prop(lhs,rhs);
}
void set_node_property(node_t const& node, id_t const& key,
id_t const& value) {
// Add the property key to the "set" table to avoid default overwrite
node_map[node].insert(key);
// Set the user's property map
self.graph_.set_node_property(key, node, value);
#ifdef BOOST_GRAPH_DEBUG
// Tell the world
std::cout << node << ": " << key << " = " << value << std::endl;
#endif // BOOST_GRAPH_DEBUG
}
void set_edge_property(edge_t const& edge, id_t const& key,
id_t const& value) {
// Add the property key to the "set" table to avoid default overwrite
edge_map[edge].insert(key);
// Set the user's property map
self.graph_.set_edge_property(key, edge, value);
#ifdef BOOST_GRAPH_DEBUG
// Tell the world
#if 0 // RG - edge representation changed,
std::cout << "(" << edge.first << "," << edge.second << "): "
#else
std::cout << "an edge: "
#endif // 0
<< key << " = " << value << std::endl;
#endif // BOOST_GRAPH_DEBUG
}
// Variables explicitly initialized
dot_grammar const& self;
// if subgraph_depth > 0, then we're processing a subgraph.
int subgraph_depth;
// Keywords;
const boost::spirit::classic::distinct_parser<> keyword_p;
//
// rules that make up the grammar
//
boost::spirit::classic::rule<ScannerT,id_closure::context_t> ID;
boost::spirit::classic::rule<ScannerT,property_closure::context_t> a_list;
boost::spirit::classic::rule<ScannerT,attr_list_closure::context_t> attr_list;
rule_t port_location;
rule_t port_angle;
rule_t port;
boost::spirit::classic::rule<ScannerT,node_id_closure::context_t> node_id;
boost::spirit::classic::rule<ScannerT,property_closure::context_t> graph_stmt;
rule_t attr_stmt;
boost::spirit::classic::rule<ScannerT,data_stmt_closure::context_t> data_stmt;
boost::spirit::classic::rule<ScannerT,subgraph_closure::context_t> subgraph;
rule_t edgeop;
rule_t edgeRHS;
rule_t stmt;
rule_t stmt_list;
rule_t the_grammar;
// The grammar uses edge_head to dynamically set the syntax for edges
// directed graphs: edge_head = '>', and so edgeop = "->"
// undirected graphs: edge_head = '-', and so edgeop = "--"
char edge_head;
//
// Support data structures
//
nodes_t nodes; // list of node names seen
edges_t edges; // list of edges seen
node_map_t node_map; // remember the properties set for each node
edge_map_t edge_map; // remember the properties set for each edge
subgraph_nodes_t subgraph_nodes; // per-subgraph lists of nodes
subgraph_edges_t subgraph_edges; // per-subgraph lists of edges
props_t default_node_props; // global default node properties
props_t default_edge_props; // global default edge properties
subgraph_props_t subgraph_node_props; // per-subgraph default node properties
subgraph_props_t subgraph_edge_props; // per-subgraph default edge properties
}; // struct definition
}; // struct dot_grammar
//
// dot_skipper - GraphViz whitespace and comment skipper
//
struct dot_skipper : public boost::spirit::classic::grammar<dot_skipper>
{
dot_skipper() {}
template <typename ScannerT>
struct definition
{
definition(dot_skipper const& /*self*/) {
using namespace boost::spirit::classic;
using namespace phoenix;
// comment forms
skip = eol_p >> comment_p("#")
| space_p
| comment_p("//")
#if BOOST_WORKAROUND(BOOST_MSVC, <= 1400)
| confix_p(str_p("/*") ,*anychar_p, str_p("*/"))
#else
| confix_p("/*" ,*anychar_p, "*/")
#endif
;
#ifdef BOOST_SPIRIT_DEBUG
BOOST_SPIRIT_DEBUG_RULE(skip);
#endif
}
boost::spirit::classic::rule<ScannerT> skip;
boost::spirit::classic::rule<ScannerT> const&
start() const { return skip; }
}; // definition
}; // dot_skipper
} // namespace graph
} // namespace detail
template <typename MultiPassIterator, typename MutableGraph>
bool read_graphviz_spirit(MultiPassIterator begin, MultiPassIterator end,
MutableGraph& graph, dynamic_properties& dp,
std::string const& node_id = "node_id") {
using namespace boost;
using namespace boost::spirit::classic;
typedef MultiPassIterator iterator_t;
typedef skip_parser_iteration_policy< boost::detail::graph::dot_skipper>
iter_policy_t;
typedef scanner_policies<iter_policy_t> scanner_policies_t;
typedef scanner<iterator_t, scanner_policies_t> scanner_t;
::boost::detail::graph::mutate_graph_impl<MutableGraph>
m_graph(graph, dp, node_id);
::boost::detail::graph::dot_grammar p(m_graph);
::boost::detail::graph::dot_skipper skip_p;
iter_policy_t iter_policy(skip_p);
scanner_policies_t policies(iter_policy);
scanner_t scan(begin, end, policies);
return p.parse(scan);
}
} // namespace boost
#endif // BOOST_READ_GRAPHVIZ_SPIRIT_HPP