aboutsummaryrefslogtreecommitdiff
path: root/gcc/graphviz.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/graphviz.cc')
-rw-r--r--gcc/graphviz.cc691
1 files changed, 662 insertions, 29 deletions
diff --git a/gcc/graphviz.cc b/gcc/graphviz.cc
index 65bb6cb..f8cedc0 100644
--- a/gcc/graphviz.cc
+++ b/gcc/graphviz.cc
@@ -18,19 +18,39 @@ You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
+#define INCLUDE_MAP
+#define INCLUDE_STRING
+#define INCLUDE_VECTOR
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "graphviz.h"
+#include "xml.h"
+#include "xml-printer.h"
+#include "pex.h"
+#include "selftest.h"
-/* graphviz_out's ctor, wrapping PP. */
-
-graphviz_out::graphviz_out (pretty_printer *pp)
+dot::writer::writer (pretty_printer &pp)
: m_pp (pp),
m_indent (0)
{
}
+/* Print the current indent to the underlying pp. */
+
+void
+dot::writer::write_indent ()
+{
+ for (int i = 0; i < m_indent * 4; ++i)
+ pp_space (get_pp ());
+}
+
+graphviz_out::graphviz_out (pretty_printer *pp)
+: writer (*pp)
+{
+ gcc_assert (pp);
+}
+
/* Formatted print of FMT. */
void
@@ -40,8 +60,8 @@ graphviz_out::print (const char *fmt, ...)
va_start (ap, fmt);
text_info text (fmt, &ap, errno);
- pp_format (m_pp, &text);
- pp_output_formatted_text (m_pp);
+ pp_format (get_pp (), &text);
+ pp_output_formatted_text (get_pp ());
va_end (ap);
}
@@ -57,20 +77,11 @@ graphviz_out::println (const char *fmt, ...)
va_start (ap, fmt);
text_info text (fmt, &ap, errno);
- pp_format (m_pp, &text);
- pp_output_formatted_text (m_pp);
+ pp_format (get_pp (), &text);
+ pp_output_formatted_text (get_pp ());
va_end (ap);
- pp_newline (m_pp);
-}
-
-/* Print the current indent to the underlying pp. */
-
-void
-graphviz_out::write_indent ()
-{
- for (int i = 0; i < m_indent * 2; ++i)
- pp_space (m_pp);
+ pp_newline (get_pp ());
}
/* Write the start of an HTML-like row via <TR>, writing to the stream
@@ -79,8 +90,8 @@ graphviz_out::write_indent ()
void
graphviz_out::begin_tr ()
{
- pp_string (m_pp, "<TR>");
- pp_write_text_to_stream (m_pp);
+ pp_string (get_pp (), "<TR>");
+ pp_write_text_to_stream (get_pp ());
}
/* Write the end of an HTML-like row via </TR>, writing to the stream
@@ -89,8 +100,8 @@ graphviz_out::begin_tr ()
void
graphviz_out::end_tr ()
{
- pp_string (m_pp, "</TR>");
- pp_write_text_to_stream (m_pp);
+ pp_string (get_pp (), "</TR>");
+ pp_write_text_to_stream (get_pp ());
}
/* Write the start of an HTML-like <TD>, writing to the stream
@@ -99,8 +110,8 @@ graphviz_out::end_tr ()
void
graphviz_out::begin_td ()
{
- pp_string (m_pp, "<TD ALIGN=\"LEFT\">");
- pp_write_text_to_stream (m_pp);
+ pp_string (get_pp (), "<TD ALIGN=\"LEFT\">");
+ pp_write_text_to_stream (get_pp ());
}
/* Write the end of an HTML-like </TD>, writing to the stream
@@ -109,8 +120,8 @@ graphviz_out::begin_td ()
void
graphviz_out::end_td ()
{
- pp_string (m_pp, "</TD>");
- pp_write_text_to_stream (m_pp);
+ pp_string (get_pp (), "</TD>");
+ pp_write_text_to_stream (get_pp ());
}
/* Write the start of an HTML-like row via <TR><TD>, writing to the stream
@@ -119,8 +130,8 @@ graphviz_out::end_td ()
void
graphviz_out::begin_trtd ()
{
- pp_string (m_pp, "<TR><TD ALIGN=\"LEFT\">");
- pp_write_text_to_stream (m_pp);
+ pp_string (get_pp (), "<TR><TD ALIGN=\"LEFT\">");
+ pp_write_text_to_stream (get_pp ());
}
/* Write the end of an HTML-like row via </TD></TR>, writing to the stream
@@ -129,6 +140,628 @@ graphviz_out::begin_trtd ()
void
graphviz_out::end_tdtr ()
{
- pp_string (m_pp, "</TD></TR>");
- pp_write_text_to_stream (m_pp);
+ pp_string (get_pp (), "</TD></TR>");
+ pp_write_text_to_stream (get_pp ());
+}
+
+namespace dot {
+
+// Definitions
+
+// struct ast_node
+
+void
+ast_node::dump () const
+{
+ pretty_printer pp;
+ pp.set_output_stream (stderr);
+ writer w (pp);
+ print (w);
+ pp_newline (&pp);
+ pp_flush (&pp);
+}
+
+// struct id
+
+id::id (std::string str)
+: m_str (std::move (str)),
+ m_kind (is_identifier_p (m_str.c_str ())
+ ? kind::identifier
+ : kind::quoted)
+{
+}
+
+id::id (const xml::node &n)
+: m_kind (kind::html)
+{
+ pretty_printer pp;
+ n.write_as_xml (&pp, 0, true);
+ m_str = pp_formatted_text (&pp);
+}
+
+void
+id::print (writer &w) const
+{
+ switch (m_kind)
+ {
+ default:
+ gcc_unreachable ();
+
+ case kind::identifier:
+ w.write_string (m_str.c_str ());
+ break;
+
+ case kind::quoted:
+ w.write_character ('"');
+ for (auto ch : m_str)
+ if (ch == '"')
+ w.write_string ("\\\"");
+ else
+ w.write_character (ch);
+ w.write_character ('"');
+ break;
+
+ case kind::html:
+ w.write_character ('<');
+ w.write_string (m_str.c_str ());
+ w.write_character ('>');
+ break;
+
+ }
+}
+
+bool
+id::is_identifier_p (const char *str)
+{
+ const char initial_ch = *str;
+ if (initial_ch != '_' && !ISALPHA (initial_ch))
+ return false;
+ for (const char *iter = str + 1; *iter; ++iter)
+ {
+ const char iter_ch = *iter;
+ if (iter_ch != '_' && !ISALNUM (iter_ch))
+ return false;
+ }
+ return true;
+}
+
+// struct kv_pair
+
+void
+kv_pair::print (writer &w) const
+{
+ m_key.print (w);
+ w.write_character ('=');
+ m_value.print (w);
+}
+
+// struct attr_list
+
+void
+attr_list::print (writer &w) const
+{
+ if (m_kvs.empty ())
+ return;
+ w.write_string (" [");
+ for (auto iter = m_kvs.begin (); iter != m_kvs.end (); ++iter)
+ {
+ if (iter != m_kvs.begin ())
+ w.write_string ("; ");
+ iter->print (w);
+ }
+ w.write_string ("]");
+}
+
+// struct stmt_list
+
+void
+stmt_list::print (writer &w) const
+{
+ for (auto &stmt : m_stmts)
+ {
+ w.write_indent ();
+ stmt->print (w);
+ w.write_string (";");
+ w.write_newline ();
+ }
+}
+
+void
+stmt_list::add_edge (node_id src_id, node_id dst_id)
+{
+ m_stmts.push_back
+ (std::make_unique<dot::edge_stmt>
+ (std::move (src_id), std::move (dst_id)));
+}
+
+void
+stmt_list::add_attr (id key, id value)
+{
+ add_stmt
+ (std::make_unique <kv_stmt> (kv_pair (std::move (key),
+ std::move (value))));
+}
+
+// struct graph
+
+void
+graph::print (writer &w) const
+{
+ w.write_indent ();
+ w.write_string ("digraph ");
+ if (m_id)
+ {
+ m_id->print (w);
+ w.write_character (' ');
+ }
+ w.write_string ("{");
+ w.write_newline ();
+
+ w.indent ();
+ m_stmt_list.print (w);
+ w.outdent ();
+
+ w.write_indent ();
+ w.write_string ("}");
+ w.write_newline ();
+}
+
+// struct stmt_with_attr_list : public stmt
+
+void
+stmt_with_attr_list::set_label (dot::id value)
+{
+ m_attrs.add (dot::id ("label"), std::move (value));
+}
+
+// struct node_stmt : public stmt_with_attr_list
+
+void
+node_stmt::print (writer &w) const
+{
+ m_id.print (w);
+ m_attrs.print (w);
+}
+
+// struct attr_stmt : public stmt_with_attr_list
+
+void
+attr_stmt::print (writer &w) const
+{
+ switch (m_kind)
+ {
+ default:
+ gcc_unreachable ();
+ case kind::graph:
+ w.write_string ("graph");
+ break;
+ case kind::node:
+ w.write_string ("node");
+ break;
+ case kind::edge:
+ w.write_string ("edge");
+ break;
+ }
+ m_attrs.print (w);
+}
+
+// struct kv_stmt : public stmt
+
+void
+kv_stmt::print (writer &w) const
+{
+ m_kv.print (w);
+}
+
+// struct node_id
+
+void
+node_id::print (writer &w) const
+{
+ m_id.print (w);
+ if (m_port)
+ m_port->print (w);
+}
+
+// struct port
+
+void
+port::print (writer &w) const
+{
+ if (m_id)
+ {
+ w.write_character (':');
+ m_id->print (w);
+ }
+ if (m_compass_pt)
+ {
+ w.write_character (':');
+ switch (*m_compass_pt)
+ {
+ default:
+ gcc_unreachable ();
+ case compass_pt::n:
+ w.write_string ("n");
+ break;
+ case compass_pt::ne:
+ w.write_string ("ne");
+ break;
+ case compass_pt::e:
+ w.write_string ("e");
+ break;
+ case compass_pt::se:
+ w.write_string ("se");
+ break;
+ case compass_pt::s:
+ w.write_string ("s");
+ break;
+ case compass_pt::sw:
+ w.write_string ("sw");
+ break;
+ case compass_pt::w:
+ w.write_string ("w");
+ break;
+ case compass_pt::nw:
+ w.write_string ("nw");
+ break;
+ case compass_pt::c:
+ w.write_string ("c");
+ break;
+ }
+ }
+}
+
+// struct edge_stmt : public stmt_with_attr_list
+
+void
+edge_stmt::print (writer &w) const
+{
+ for (auto iter = m_node_ids.begin (); iter != m_node_ids.end (); ++iter)
+ {
+ if (iter != m_node_ids.begin ())
+ w.write_string (" -> ");
+ iter->print (w);
+ }
+ m_attrs.print (w);
+}
+
+// struct subgraph : public stmt
+
+void
+subgraph::print (writer &w) const
+{
+ w.write_newline ();
+ w.write_indent ();
+ w.write_string ("subgraph ");
+ m_id.print (w);
+ w.write_string (" {");
+ w.write_newline ();
+
+ w.indent ();
+ m_stmt_list.print (w);
+ w.outdent ();
+ w.write_newline ();
+
+ w.write_indent ();
+ w.write_string ("}"); // newline and semicolon added by stmt_list
+}
+
+/* Convert G to graphviz source, attempt to invoke "dot -Tsvg" on it
+ as a subprocess, and get the SVG source from stdout, or nullptr
+ if there was a problem. */
+
+static std::unique_ptr<std::string>
+make_svg_document_buffer_from_graph (const graph &g)
+{
+ /* Ideally there would be a way of doing this without
+ invoking dot as a subprocess. */
+
+ std::vector<std::string> args;
+ args.push_back ("dot");
+ args.push_back ("-Tsvg");
+
+ pex p (0, "dot", nullptr);
+
+ {
+ auto pipe_stdin = p.input_file (true, nullptr);
+ gcc_assert (pipe_stdin.m_file);
+ pretty_printer pp;
+ pp.set_output_stream (pipe_stdin.m_file);
+ writer w (pp);
+ g.print (w);
+ pp_flush (&pp);
+ }
+
+ int err = 0;
+ const char * errmsg
+ = p.run (PEX_SEARCH,
+ "dot", args, nullptr, nullptr, &err);
+ auto pipe_stdout = p.read_output ();
+ auto content = pipe_stdout.read_all ();
+
+ if (errmsg)
+ return nullptr;
+ if (err)
+ return nullptr;
+
+ std::string result;
+ result.reserve (content->size () + 1);
+ for (auto &iter : *content)
+ result.push_back (iter);
+ return std::make_unique<std::string> (std::move (result));
+}
+
+/* Convert G to graphviz source, attempt to invoke "dot -Tsvg" on it
+ as a subprocess, and get the SVG source from stdout, and extract
+ the "svg" subtree as an xml::raw node.
+
+ Note that this
+ (a) invokes "dot" as a subprocess
+ (b) assumes that we trust the output from "dot".
+
+ Return nullptr if there was a problem. */
+
+std::unique_ptr<xml::node>
+make_svg_from_graph (const graph &g)
+{
+ auto svg_src = make_svg_document_buffer_from_graph (g);
+ if (!svg_src)
+ return nullptr;
+
+ /* Skip past the XML header to the parts we care about. */
+ auto pos = svg_src->find ("<!-- Generated by graphviz");
+ if (pos == svg_src->npos)
+ return nullptr;
+
+ auto substring = std::string (*svg_src, pos);
+ return std::make_unique<xml::raw> (std::move (substring));
+}
+
+} // namespace dot
+
+#if CHECKING_P
+
+namespace selftest {
+
+static void
+test_ids ()
+{
+ ASSERT_TRUE (dot::id::is_identifier_p ("foo"));
+ ASSERT_FALSE (dot::id::is_identifier_p ("hello world"));
+ ASSERT_TRUE (dot::id::is_identifier_p ("foo42"));
+ ASSERT_FALSE (dot::id::is_identifier_p ("42"));
+ ASSERT_TRUE (dot::id::is_identifier_p ("_"));
+}
+
+static void
+test_trivial_graph ()
+{
+ dot::graph g;
+ // node "a"
+ {
+ g.add_stmt (std::make_unique<dot::node_stmt> (dot::id ("a")));
+ }
+ // node "b"
+ {
+ auto n = std::make_unique<dot::node_stmt> (dot::id ("b"));
+ n->m_attrs.add (dot::id ("label"), dot::id ("This is node b"));
+ n->m_attrs.add (dot::id ("color"), dot::id ("green"));
+ g.add_stmt (std::move (n));
+ }
+ // an edge between them
+ {
+ auto e = std::make_unique<dot::edge_stmt> (dot::id ("a"),
+ dot::id ("b"));
+ e->m_attrs.add (dot::id ("label"), dot::id ("I'm an edge"));
+ g.add_stmt (std::move (e));
+ }
+ pretty_printer pp;
+ dot::writer w (pp);
+ g.print (w);
+ ASSERT_STREQ
+ (pp_formatted_text (&pp),
+ ("digraph {\n"
+ " a;\n"
+ " b [label=\"This is node b\"; color=green];\n"
+ " a -> b [label=\"I'm an edge\"];\n"
+ "}\n"));
+}
+
+/* Recreating the HTML record example from
+ https://graphviz.org/doc/info/shapes.html#html */
+
+static void
+test_layout_example ()
+{
+ dot::graph g (dot::id ("structs"));
+
+ // "node [shape=plaintext]\n"
+ {
+ auto attr_stmt
+ = std::make_unique<dot::attr_stmt> (dot::attr_stmt::kind::node);
+ attr_stmt->m_attrs.add (dot::id ("shape"), dot::id ("plaintext"));
+ g.add_stmt (std::move (attr_stmt));
+ }
+
+ // struct1
+ {
+ auto n = std::make_unique<dot::node_stmt> (dot::id ("struct1"));
+
+ xml::element table ("TABLE", false);
+ xml::printer xp (table);
+ xp.set_attr ("BORDER", "0");
+ xp.set_attr ("CELLBORDER", "1");
+ xp.set_attr ("CELLSPACING", "0");
+
+ xp.push_tag ("TR", true);
+
+ xp.push_tag ("TD", false);
+ xp.add_text ("left");
+ xp.pop_tag ("TD");
+
+ xp.push_tag ("TD", false);
+ xp.set_attr ("PORT", "f1");
+ xp.add_text ("mid dle");
+ xp.pop_tag ("TD");
+
+ xp.push_tag ("TD", false);
+ xp.set_attr ("PORT", "f2");
+ xp.add_text ("right");
+ xp.pop_tag ("TD");
+
+ n->set_label (table);
+ g.add_stmt (std::move (n));
+ }
+
+ // struct2
+ {
+ auto n = std::make_unique<dot::node_stmt> (dot::id ("struct2"));
+ xml::element table ("TABLE", false);
+ xml::printer xp (table);
+ xp.set_attr ("BORDER", "0");
+ xp.set_attr ("CELLBORDER", "1");
+ xp.set_attr ("CELLSPACING", "0");
+
+ xp.push_tag ("TR", true);
+
+ xp.push_tag ("TD", false);
+ xp.set_attr ("PORT", "f0");
+ xp.add_text ("one");
+ xp.pop_tag ("TD");
+
+ xp.push_tag ("TD", false);
+ xp.add_text ("two");
+ xp.pop_tag ("TD");
+
+ n->set_label (table);
+ g.add_stmt (std::move (n));
+ }
+
+ // struct3
+ {
+ auto n = std::make_unique<dot::node_stmt> (dot::id ("struct3"));
+ xml::element table ("TABLE", false);
+ xml::printer xp (table);
+ xp.set_attr ("BORDER", "0");
+ xp.set_attr ("CELLBORDER", "1");
+ xp.set_attr ("CELLSPACING", "0");
+ xp.set_attr ("CELLPADDING", "4");
+
+ xp.push_tag ("TR", false);
+
+ xp.push_tag ("TD", true);
+ xp.set_attr ("ROWSPAN", "3");
+ xp.add_text ("hello");
+ xp.append (std::make_unique<xml::element> ("BR", false));
+ xp.add_text ("world");
+ xp.pop_tag ("TD");
+
+ xp.push_tag ("TD", true);
+ xp.set_attr ("COLSPAN", "3");
+ xp.add_text ("b");
+ xp.pop_tag ("TD");
+
+ xp.push_tag ("TD", true);
+ xp.set_attr ("ROWSPAN", "3");
+ xp.add_text ("g");
+ xp.pop_tag ("TD");
+
+ xp.push_tag ("TD", true);
+ xp.set_attr ("ROWSPAN", "3");
+ xp.add_text ("h");
+ xp.pop_tag ("TD");
+
+ xp.pop_tag ("TR");
+
+ xp.push_tag ("TR", false);
+
+ xp.push_tag ("TD", true);
+ xp.add_text ("c");
+ xp.pop_tag ("TD");
+
+ xp.push_tag ("TD", true);
+ xp.set_attr ("PORT", "here");
+ xp.add_text ("d");
+ xp.pop_tag ("TD");
+
+ xp.push_tag ("TD", true);
+ xp.add_text ("e");
+ xp.pop_tag ("TD");
+
+ xp.pop_tag ("TR");
+
+ xp.push_tag ("TR", false);
+
+ xp.push_tag ("TD", true);
+ xp.set_attr ("COLSPAN", "3");
+ xp.add_text ("f");
+ xp.pop_tag ("TD");
+
+ n->set_label (table);
+ g.add_stmt (std::move (n));
+ }
+
+ g.m_stmt_list.add_edge
+ (dot::node_id (dot::id ("struct1"),
+ dot::port (dot::id ("f1"))),
+ dot::node_id (dot::id ("struct2"),
+ dot::port (dot::id ("f0"))));
+ g.m_stmt_list.add_edge
+ (dot::node_id (dot::id ("struct1"),
+ dot::port (dot::id ("f2"))),
+ dot::node_id (dot::id ("struct3"),
+ dot::port (dot::id ("here"))));
+
+ pretty_printer pp;
+ dot::writer w (pp);
+ g.print (w);
+
+ /* There are some whitespace differences with the example in the
+ GraphViz docs. */
+ ASSERT_STREQ
+ (pp_formatted_text (&pp),
+ ("digraph structs {\n"
+ " node [shape=plaintext];\n" // added semicolon
+ " struct1 [label=<<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n"
+ " <TR><TD>left</TD><TD PORT=\"f1\">mid dle</TD><TD PORT=\"f2\">right</TD></TR>\n"
+ "</TABLE>\n"
+ ">];\n"
+ " struct2 [label=<<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n"
+ " <TR><TD PORT=\"f0\">one</TD><TD>two</TD></TR>\n"
+ "</TABLE>\n"
+ ">];\n"
+ " struct3 [label=<<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"4\">\n"
+ " <TR>\n"
+ " <TD ROWSPAN=\"3\">hello<BR/>world</TD>\n"
+ " <TD COLSPAN=\"3\">b</TD>\n"
+ " <TD ROWSPAN=\"3\">g</TD>\n"
+ " <TD ROWSPAN=\"3\">h</TD>\n"
+ " </TR>\n"
+ " <TR>\n"
+ " <TD>c</TD>\n"
+ " <TD PORT=\"here\">d</TD>\n"
+ " <TD>e</TD>\n"
+ " </TR>\n"
+ " <TR>\n"
+ " <TD COLSPAN=\"3\">f</TD>\n"
+ " </TR>\n"
+ "</TABLE>\n"
+ ">];\n"
+ " struct1:f1 -> struct2:f0;\n"
+ " struct1:f2 -> struct3:here;\n"
+ "}\n"));
+}
+
+/* Run all of the selftests within this file. */
+
+void
+graphviz_cc_tests ()
+{
+ test_ids ();
+ test_trivial_graph ();
+ test_layout_example ();
}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */