Line data Source code
1 : /* Helper code for graphviz output.
2 : Copyright (C) 2019-2026 Free Software Foundation, Inc.
3 : Contributed by David Malcolm <dmalcolm@redhat.com>.
4 :
5 : This file is part of GCC.
6 :
7 : GCC is free software; you can redistribute it and/or modify it
8 : under the terms of the GNU General Public License as published by
9 : the Free Software Foundation; either version 3, or (at your option)
10 : any later version.
11 :
12 : GCC is distributed in the hope that it will be useful, but
13 : WITHOUT ANY WARRANTY; without even the implied warranty of
14 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 : General Public License for more details.
16 :
17 : You should have received a copy of the GNU General Public License
18 : along with GCC; see the file COPYING3. If not see
19 : <http://www.gnu.org/licenses/>. */
20 :
21 : #define INCLUDE_MAP
22 : #define INCLUDE_STRING
23 : #define INCLUDE_VECTOR
24 : #include "config.h"
25 : #include "system.h"
26 : #include "coretypes.h"
27 : #include "graphviz.h"
28 : #include "xml.h"
29 : #include "xml-printer.h"
30 : #include "pex.h"
31 : #include "selftest.h"
32 :
33 550 : dot::writer::writer (pretty_printer &pp)
34 550 : : m_pp (pp),
35 550 : m_indent (0)
36 : {
37 550 : }
38 :
39 : /* Print the current indent to the underlying pp. */
40 :
41 : void
42 8045 : dot::writer::write_indent ()
43 : {
44 65717 : for (int i = 0; i < m_indent * 4; ++i)
45 57672 : pp_space (get_pp ());
46 8045 : }
47 :
48 40 : graphviz_out::graphviz_out (pretty_printer *pp)
49 40 : : writer (*pp)
50 : {
51 40 : gcc_assert (pp);
52 40 : }
53 :
54 : /* Formatted print of FMT. */
55 :
56 : void
57 1287 : graphviz_out::print (const char *fmt, ...)
58 : {
59 1287 : va_list ap;
60 :
61 1287 : va_start (ap, fmt);
62 1287 : text_info text (fmt, &ap, errno);
63 1287 : pp_format (get_pp (), &text);
64 1287 : pp_output_formatted_text (get_pp ());
65 1287 : va_end (ap);
66 1287 : }
67 :
68 : /* Formatted print of FMT. The text is indented by the current
69 : indent, and a newline is added. */
70 :
71 : void
72 3524 : graphviz_out::println (const char *fmt, ...)
73 : {
74 3524 : va_list ap;
75 :
76 3524 : write_indent ();
77 :
78 3524 : va_start (ap, fmt);
79 3524 : text_info text (fmt, &ap, errno);
80 3524 : pp_format (get_pp (), &text);
81 3524 : pp_output_formatted_text (get_pp ());
82 3524 : va_end (ap);
83 :
84 3524 : pp_newline (get_pp ());
85 3524 : }
86 :
87 : /* Write the start of an HTML-like row via <TR>, writing to the stream
88 : so that followup text can be escaped. */
89 :
90 : void
91 195 : graphviz_out::begin_tr ()
92 : {
93 195 : pp_string (get_pp (), "<TR>");
94 195 : pp_write_text_to_stream (get_pp ());
95 195 : }
96 :
97 : /* Write the end of an HTML-like row via </TR>, writing to the stream
98 : so that followup text can be escaped. */
99 :
100 : void
101 191 : graphviz_out::end_tr ()
102 : {
103 191 : pp_string (get_pp (), "</TR>");
104 191 : pp_write_text_to_stream (get_pp ());
105 191 : }
106 :
107 : /* Write the start of an HTML-like <TD>, writing to the stream
108 : so that followup text can be escaped. */
109 :
110 : void
111 0 : graphviz_out::begin_td ()
112 : {
113 0 : pp_string (get_pp (), "<TD ALIGN=\"LEFT\">");
114 0 : pp_write_text_to_stream (get_pp ());
115 0 : }
116 :
117 : /* Write the end of an HTML-like </TD>, writing to the stream
118 : so that followup text can be escaped. */
119 :
120 : void
121 0 : graphviz_out::end_td ()
122 : {
123 0 : pp_string (get_pp (), "</TD>");
124 0 : pp_write_text_to_stream (get_pp ());
125 0 : }
126 :
127 : /* Write the start of an HTML-like row via <TR><TD>, writing to the stream
128 : so that followup text can be escaped. */
129 :
130 : void
131 823 : graphviz_out::begin_trtd ()
132 : {
133 823 : pp_string (get_pp (), "<TR><TD ALIGN=\"LEFT\">");
134 823 : pp_write_text_to_stream (get_pp ());
135 823 : }
136 :
137 : /* Write the end of an HTML-like row via </TD></TR>, writing to the stream
138 : so that followup text can be escaped. */
139 :
140 : void
141 827 : graphviz_out::end_tdtr ()
142 : {
143 827 : pp_string (get_pp (), "</TD></TR>");
144 827 : pp_write_text_to_stream (get_pp ());
145 827 : }
146 :
147 : namespace dot {
148 :
149 : // Definitions
150 :
151 : // struct ast_node
152 :
153 : void
154 0 : ast_node::dump () const
155 : {
156 0 : pretty_printer pp;
157 0 : pp.set_output_stream (stderr);
158 0 : writer w (pp);
159 0 : print (w);
160 0 : pp_newline (&pp);
161 0 : pp_flush (&pp);
162 0 : }
163 :
164 : // struct id
165 :
166 9149 : id::id (std::string str)
167 9149 : : m_str (std::move (str)),
168 9149 : m_kind (is_identifier_p (m_str.c_str ())
169 9149 : ? kind::identifier
170 9149 : : kind::quoted)
171 : {
172 9149 : }
173 :
174 193 : id::id (const xml::node &n)
175 193 : : m_kind (kind::html)
176 : {
177 193 : pretty_printer pp;
178 193 : n.write_as_xml (&pp, 0, true);
179 193 : m_str = pp_formatted_text (&pp);
180 193 : }
181 :
182 : void
183 9341 : id::print (writer &w) const
184 : {
185 9341 : switch (m_kind)
186 : {
187 0 : default:
188 0 : gcc_unreachable ();
189 :
190 6718 : case kind::identifier:
191 6718 : w.write_string (m_str.c_str ());
192 6718 : break;
193 :
194 2430 : case kind::quoted:
195 2430 : w.write_character ('"');
196 28078 : for (auto ch : m_str)
197 25648 : if (ch == '"')
198 0 : w.write_string ("\\\"");
199 : else
200 25648 : w.write_character (ch);
201 2430 : w.write_character ('"');
202 2430 : break;
203 :
204 193 : case kind::html:
205 193 : w.write_character ('<');
206 193 : w.write_string (m_str.c_str ());
207 193 : w.write_character ('>');
208 193 : break;
209 :
210 : }
211 9341 : }
212 :
213 : bool
214 9169 : id::is_identifier_p (const char *str)
215 : {
216 9169 : const char initial_ch = *str;
217 9169 : if (initial_ch != '_' && !ISALPHA (initial_ch))
218 : return false;
219 51260 : for (const char *iter = str + 1; *iter; ++iter)
220 : {
221 44529 : const char iter_ch = *iter;
222 44529 : if (iter_ch != '_' && !ISALNUM (iter_ch))
223 : return false;
224 : }
225 : return true;
226 : }
227 :
228 : // struct kv_pair
229 :
230 : void
231 3560 : kv_pair::print (writer &w) const
232 : {
233 3560 : m_key.print (w);
234 3560 : w.write_character ('=');
235 3560 : m_value.print (w);
236 3560 : }
237 :
238 : // struct attr_list
239 :
240 : void
241 1871 : attr_list::print (writer &w) const
242 : {
243 1871 : if (m_kvs.empty ())
244 : return;
245 1853 : w.write_string (" [");
246 5356 : for (auto iter = m_kvs.begin (); iter != m_kvs.end (); ++iter)
247 : {
248 3503 : if (iter != m_kvs.begin ())
249 1650 : w.write_string ("; ");
250 3503 : iter->print (w);
251 : }
252 1853 : w.write_string ("]");
253 : }
254 :
255 : // struct stmt_list
256 :
257 : void
258 122 : stmt_list::print (writer &w) const
259 : {
260 1662 : for (auto &stmt : m_stmts)
261 : {
262 1540 : w.write_indent ();
263 1540 : stmt->print (w);
264 1540 : w.write_string (";");
265 1540 : w.write_newline ();
266 : }
267 122 : }
268 :
269 : void
270 8 : stmt_list::add_edge (node_id src_id, node_id dst_id)
271 : {
272 8 : m_stmts.push_back
273 16 : (std::make_unique<dot::edge_stmt>
274 8 : (std::move (src_id), std::move (dst_id)));
275 8 : }
276 :
277 : void
278 52 : stmt_list::add_attr (id key, id value)
279 : {
280 52 : add_stmt
281 52 : (std::make_unique <kv_stmt> (kv_pair (std::move (key),
282 104 : std::move (value))));
283 52 : }
284 :
285 : // struct graph
286 :
287 : void
288 65 : graph::print (writer &w) const
289 : {
290 65 : w.write_indent ();
291 65 : w.write_string ("digraph ");
292 65 : if (m_id)
293 : {
294 4 : m_id->print (w);
295 4 : w.write_character (' ');
296 : }
297 65 : w.write_string ("{");
298 65 : w.write_newline ();
299 :
300 65 : w.indent ();
301 65 : m_stmt_list.print (w);
302 65 : w.outdent ();
303 :
304 65 : w.write_indent ();
305 65 : w.write_string ("}");
306 65 : w.write_newline ();
307 65 : }
308 :
309 : // struct stmt_with_attr_list : public stmt
310 :
311 : void
312 947 : stmt_with_attr_list::set_label (dot::id value)
313 : {
314 947 : m_attrs.add (dot::id ("label"), std::move (value));
315 947 : }
316 :
317 : // struct node_stmt : public stmt_with_attr_list
318 :
319 : void
320 698 : node_stmt::print (writer &w) const
321 : {
322 698 : m_id.print (w);
323 698 : m_attrs.print (w);
324 698 : }
325 :
326 : // struct attr_stmt : public stmt_with_attr_list
327 :
328 : void
329 5 : attr_stmt::print (writer &w) const
330 : {
331 5 : switch (m_kind)
332 : {
333 0 : default:
334 0 : gcc_unreachable ();
335 0 : case kind::graph:
336 0 : w.write_string ("graph");
337 0 : break;
338 5 : case kind::node:
339 5 : w.write_string ("node");
340 5 : break;
341 0 : case kind::edge:
342 0 : w.write_string ("edge");
343 0 : break;
344 : }
345 5 : m_attrs.print (w);
346 5 : }
347 :
348 : // struct kv_stmt : public stmt
349 :
350 : void
351 57 : kv_stmt::print (writer &w) const
352 : {
353 57 : m_kv.print (w);
354 57 : }
355 :
356 : bool
357 90 : get_compass_pt_from_string (const char *str, enum compass_pt &out)
358 : {
359 90 : if (strcmp (str, "n") == 0)
360 : {
361 45 : out = compass_pt::n;
362 45 : return true;
363 : }
364 45 : if (strcmp (str, "ne") == 0)
365 : {
366 0 : out = compass_pt::ne;
367 0 : return true;
368 : }
369 45 : if (strcmp (str, "e") == 0)
370 : {
371 0 : out = compass_pt::e;
372 0 : return true;
373 : }
374 45 : if (strcmp (str, "se") == 0)
375 : {
376 0 : out = compass_pt::se;
377 0 : return true;
378 : }
379 45 : if (strcmp (str, "s") == 0)
380 : {
381 45 : out = compass_pt::s;
382 45 : return true;
383 : }
384 0 : if (strcmp (str, "sw") == 0)
385 : {
386 0 : out = compass_pt::sw;
387 0 : return true;
388 : }
389 0 : if (strcmp (str, "w") == 0)
390 : {
391 0 : out = compass_pt::w;
392 0 : return true;
393 : }
394 0 : if (strcmp (str, "nw") == 0)
395 : {
396 0 : out = compass_pt::nw;
397 0 : return true;
398 : }
399 0 : if (strcmp (str, "c") == 0)
400 : {
401 0 : out = compass_pt::c;
402 0 : return true;
403 : }
404 :
405 : return false;
406 : }
407 :
408 : // struct node_id
409 :
410 : void
411 1446 : node_id::print (writer &w) const
412 : {
413 1446 : m_id.print (w);
414 1446 : if (m_port)
415 106 : m_port->print (w);
416 1446 : }
417 :
418 : // struct port
419 :
420 : void
421 106 : port::print (writer &w) const
422 : {
423 106 : if (m_id)
424 : {
425 16 : w.write_character (':');
426 16 : m_id->print (w);
427 : }
428 106 : if (m_compass_pt)
429 : {
430 90 : w.write_character (':');
431 90 : switch (*m_compass_pt)
432 : {
433 0 : default:
434 0 : gcc_unreachable ();
435 45 : case compass_pt::n:
436 45 : w.write_string ("n");
437 45 : break;
438 0 : case compass_pt::ne:
439 0 : w.write_string ("ne");
440 0 : break;
441 0 : case compass_pt::e:
442 0 : w.write_string ("e");
443 0 : break;
444 0 : case compass_pt::se:
445 0 : w.write_string ("se");
446 0 : break;
447 45 : case compass_pt::s:
448 45 : w.write_string ("s");
449 45 : break;
450 0 : case compass_pt::sw:
451 0 : w.write_string ("sw");
452 0 : break;
453 0 : case compass_pt::w:
454 0 : w.write_string ("w");
455 0 : break;
456 0 : case compass_pt::nw:
457 0 : w.write_string ("nw");
458 0 : break;
459 0 : case compass_pt::c:
460 0 : w.write_string ("c");
461 0 : break;
462 : }
463 : }
464 106 : }
465 :
466 : // struct edge_stmt : public stmt_with_attr_list
467 :
468 : void
469 723 : edge_stmt::print (writer &w) const
470 : {
471 2169 : for (auto iter = m_node_ids.begin (); iter != m_node_ids.end (); ++iter)
472 : {
473 1446 : if (iter != m_node_ids.begin ())
474 723 : w.write_string (" -> ");
475 1446 : iter->print (w);
476 : }
477 723 : m_attrs.print (w);
478 723 : }
479 :
480 : // struct subgraph : public stmt
481 :
482 : void
483 57 : subgraph::print (writer &w) const
484 : {
485 57 : w.write_newline ();
486 57 : w.write_indent ();
487 57 : w.write_string ("subgraph ");
488 57 : m_id.print (w);
489 57 : w.write_string (" {");
490 57 : w.write_newline ();
491 :
492 57 : w.indent ();
493 57 : m_stmt_list.print (w);
494 57 : w.outdent ();
495 57 : w.write_newline ();
496 :
497 57 : w.write_indent ();
498 57 : w.write_string ("}"); // newline and semicolon added by stmt_list
499 57 : }
500 :
501 : /* Convert G to graphviz source, attempt to invoke "dot -Tsvg" on it
502 : as a subprocess, and get the SVG source from stdout, or nullptr
503 : if there was a problem. */
504 :
505 : static std::unique_ptr<std::string>
506 49 : make_svg_document_buffer_from_graph (const graph &g)
507 : {
508 : /* Ideally there would be a way of doing this without
509 : invoking dot as a subprocess. */
510 :
511 49 : std::vector<std::string> args;
512 49 : args.push_back ("dot");
513 49 : args.push_back ("-Tsvg");
514 :
515 49 : pex p (0, "dot", nullptr);
516 :
517 49 : {
518 49 : auto pipe_stdin = p.input_file (true, nullptr);
519 49 : gcc_assert (pipe_stdin.m_file);
520 49 : pretty_printer pp;
521 49 : pp.set_output_stream (pipe_stdin.m_file);
522 49 : writer w (pp);
523 49 : g.print (w);
524 49 : pp_flush (&pp);
525 49 : }
526 :
527 49 : int err = 0;
528 49 : const char * errmsg
529 49 : = p.run (PEX_SEARCH,
530 : "dot", args, nullptr, nullptr, &err);
531 49 : auto pipe_stdout = p.read_output ();
532 49 : auto content = pipe_stdout.read_all ();
533 :
534 49 : if (errmsg)
535 0 : return nullptr;
536 49 : if (err)
537 0 : return nullptr;
538 :
539 49 : std::string result;
540 49 : result.reserve (content->size () + 1);
541 787880 : for (auto &iter : *content)
542 787831 : result.push_back (iter);
543 49 : return std::make_unique<std::string> (std::move (result));
544 49 : }
545 :
546 : /* Convert G to graphviz source, attempt to invoke "dot -Tsvg" on it
547 : as a subprocess, and get the SVG source from stdout, and extract
548 : the "svg" subtree as an xml::raw node.
549 :
550 : Note that this
551 : (a) invokes "dot" as a subprocess
552 : (b) assumes that we trust the output from "dot".
553 :
554 : Return nullptr if there was a problem. */
555 :
556 : std::unique_ptr<xml::node>
557 49 : make_svg_from_graph (const graph &g)
558 : {
559 49 : auto svg_src = make_svg_document_buffer_from_graph (g);
560 49 : if (!svg_src)
561 0 : return nullptr;
562 :
563 : /* Skip past the XML header to the parts we care about. */
564 49 : auto pos = svg_src->find ("<!-- Generated by graphviz");
565 49 : if (pos == svg_src->npos)
566 0 : return nullptr;
567 :
568 49 : auto substring = std::string (*svg_src, pos);
569 49 : return std::make_unique<xml::raw> (std::move (substring));
570 49 : }
571 :
572 : } // namespace dot
573 :
574 : #if CHECKING_P
575 :
576 : namespace selftest {
577 :
578 : static void
579 4 : test_ids ()
580 : {
581 4 : ASSERT_TRUE (dot::id::is_identifier_p ("foo"));
582 4 : ASSERT_FALSE (dot::id::is_identifier_p ("hello world"));
583 4 : ASSERT_TRUE (dot::id::is_identifier_p ("foo42"));
584 4 : ASSERT_FALSE (dot::id::is_identifier_p ("42"));
585 4 : ASSERT_TRUE (dot::id::is_identifier_p ("_"));
586 4 : }
587 :
588 : static void
589 4 : test_trivial_graph ()
590 : {
591 4 : dot::graph g;
592 : // node "a"
593 4 : {
594 4 : g.add_stmt (std::make_unique<dot::node_stmt> (dot::id ("a")));
595 : }
596 : // node "b"
597 4 : {
598 4 : auto n = std::make_unique<dot::node_stmt> (dot::id ("b"));
599 4 : n->m_attrs.add (dot::id ("label"), dot::id ("This is node b"));
600 4 : n->m_attrs.add (dot::id ("color"), dot::id ("green"));
601 4 : g.add_stmt (std::move (n));
602 4 : }
603 : // an edge between them
604 4 : {
605 12 : auto e = std::make_unique<dot::edge_stmt> (dot::id ("a"),
606 12 : dot::id ("b"));
607 4 : e->m_attrs.add (dot::id ("label"), dot::id ("I'm an edge"));
608 4 : g.add_stmt (std::move (e));
609 4 : }
610 4 : pretty_printer pp;
611 4 : dot::writer w (pp);
612 4 : g.print (w);
613 4 : ASSERT_STREQ
614 : (pp_formatted_text (&pp),
615 : ("digraph {\n"
616 : " a;\n"
617 : " b [label=\"This is node b\"; color=green];\n"
618 : " a -> b [label=\"I'm an edge\"];\n"
619 : "}\n"));
620 4 : }
621 :
622 : /* Recreating the HTML record example from
623 : https://graphviz.org/doc/info/shapes.html#html */
624 :
625 : static void
626 4 : test_layout_example ()
627 : {
628 4 : dot::graph g (dot::id ("structs"));
629 :
630 : // "node [shape=plaintext]\n"
631 4 : {
632 4 : auto attr_stmt
633 4 : = std::make_unique<dot::attr_stmt> (dot::attr_stmt::kind::node);
634 4 : attr_stmt->m_attrs.add (dot::id ("shape"), dot::id ("plaintext"));
635 4 : g.add_stmt (std::move (attr_stmt));
636 4 : }
637 :
638 : // struct1
639 4 : {
640 4 : auto n = std::make_unique<dot::node_stmt> (dot::id ("struct1"));
641 :
642 4 : xml::element table ("TABLE", false);
643 4 : xml::printer xp (table);
644 4 : xp.set_attr ("BORDER", "0");
645 4 : xp.set_attr ("CELLBORDER", "1");
646 4 : xp.set_attr ("CELLSPACING", "0");
647 :
648 4 : xp.push_tag ("TR", true);
649 :
650 4 : xp.push_tag ("TD", false);
651 4 : xp.add_text ("left");
652 4 : xp.pop_tag ("TD");
653 :
654 4 : xp.push_tag ("TD", false);
655 4 : xp.set_attr ("PORT", "f1");
656 4 : xp.add_text ("mid dle");
657 4 : xp.pop_tag ("TD");
658 :
659 4 : xp.push_tag ("TD", false);
660 4 : xp.set_attr ("PORT", "f2");
661 4 : xp.add_text ("right");
662 4 : xp.pop_tag ("TD");
663 :
664 4 : n->set_label (table);
665 4 : g.add_stmt (std::move (n));
666 4 : }
667 :
668 : // struct2
669 4 : {
670 4 : auto n = std::make_unique<dot::node_stmt> (dot::id ("struct2"));
671 4 : xml::element table ("TABLE", false);
672 4 : xml::printer xp (table);
673 4 : xp.set_attr ("BORDER", "0");
674 4 : xp.set_attr ("CELLBORDER", "1");
675 4 : xp.set_attr ("CELLSPACING", "0");
676 :
677 4 : xp.push_tag ("TR", true);
678 :
679 4 : xp.push_tag ("TD", false);
680 4 : xp.set_attr ("PORT", "f0");
681 4 : xp.add_text ("one");
682 4 : xp.pop_tag ("TD");
683 :
684 4 : xp.push_tag ("TD", false);
685 4 : xp.add_text ("two");
686 4 : xp.pop_tag ("TD");
687 :
688 4 : n->set_label (table);
689 4 : g.add_stmt (std::move (n));
690 4 : }
691 :
692 : // struct3
693 4 : {
694 4 : auto n = std::make_unique<dot::node_stmt> (dot::id ("struct3"));
695 4 : xml::element table ("TABLE", false);
696 4 : xml::printer xp (table);
697 4 : xp.set_attr ("BORDER", "0");
698 4 : xp.set_attr ("CELLBORDER", "1");
699 4 : xp.set_attr ("CELLSPACING", "0");
700 4 : xp.set_attr ("CELLPADDING", "4");
701 :
702 4 : xp.push_tag ("TR", false);
703 :
704 4 : xp.push_tag ("TD", true);
705 4 : xp.set_attr ("ROWSPAN", "3");
706 4 : xp.add_text ("hello");
707 4 : xp.append (std::make_unique<xml::element> ("BR", false));
708 4 : xp.add_text ("world");
709 4 : xp.pop_tag ("TD");
710 :
711 4 : xp.push_tag ("TD", true);
712 4 : xp.set_attr ("COLSPAN", "3");
713 4 : xp.add_text ("b");
714 4 : xp.pop_tag ("TD");
715 :
716 4 : xp.push_tag ("TD", true);
717 4 : xp.set_attr ("ROWSPAN", "3");
718 4 : xp.add_text ("g");
719 4 : xp.pop_tag ("TD");
720 :
721 4 : xp.push_tag ("TD", true);
722 4 : xp.set_attr ("ROWSPAN", "3");
723 4 : xp.add_text ("h");
724 4 : xp.pop_tag ("TD");
725 :
726 4 : xp.pop_tag ("TR");
727 :
728 4 : xp.push_tag ("TR", false);
729 :
730 4 : xp.push_tag ("TD", true);
731 4 : xp.add_text ("c");
732 4 : xp.pop_tag ("TD");
733 :
734 4 : xp.push_tag ("TD", true);
735 4 : xp.set_attr ("PORT", "here");
736 4 : xp.add_text ("d");
737 4 : xp.pop_tag ("TD");
738 :
739 4 : xp.push_tag ("TD", true);
740 4 : xp.add_text ("e");
741 4 : xp.pop_tag ("TD");
742 :
743 4 : xp.pop_tag ("TR");
744 :
745 4 : xp.push_tag ("TR", false);
746 :
747 4 : xp.push_tag ("TD", true);
748 4 : xp.set_attr ("COLSPAN", "3");
749 4 : xp.add_text ("f");
750 4 : xp.pop_tag ("TD");
751 :
752 4 : n->set_label (table);
753 4 : g.add_stmt (std::move (n));
754 4 : }
755 :
756 4 : g.m_stmt_list.add_edge
757 8 : (dot::node_id (dot::id ("struct1"),
758 12 : dot::port (dot::id ("f1"))),
759 8 : dot::node_id (dot::id ("struct2"),
760 12 : dot::port (dot::id ("f0"))));
761 4 : g.m_stmt_list.add_edge
762 8 : (dot::node_id (dot::id ("struct1"),
763 12 : dot::port (dot::id ("f2"))),
764 8 : dot::node_id (dot::id ("struct3"),
765 12 : dot::port (dot::id ("here"))));
766 :
767 4 : pretty_printer pp;
768 4 : dot::writer w (pp);
769 4 : g.print (w);
770 :
771 : /* There are some whitespace differences with the example in the
772 : GraphViz docs. */
773 4 : ASSERT_STREQ
774 : (pp_formatted_text (&pp),
775 : ("digraph structs {\n"
776 : " node [shape=plaintext];\n" // added semicolon
777 : " struct1 [label=<<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n"
778 : " <TR><TD>left</TD><TD PORT=\"f1\">mid dle</TD><TD PORT=\"f2\">right</TD></TR>\n"
779 : "</TABLE>\n"
780 : ">];\n"
781 : " struct2 [label=<<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n"
782 : " <TR><TD PORT=\"f0\">one</TD><TD>two</TD></TR>\n"
783 : "</TABLE>\n"
784 : ">];\n"
785 : " struct3 [label=<<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"4\">\n"
786 : " <TR>\n"
787 : " <TD ROWSPAN=\"3\">hello<BR/>world</TD>\n"
788 : " <TD COLSPAN=\"3\">b</TD>\n"
789 : " <TD ROWSPAN=\"3\">g</TD>\n"
790 : " <TD ROWSPAN=\"3\">h</TD>\n"
791 : " </TR>\n"
792 : " <TR>\n"
793 : " <TD>c</TD>\n"
794 : " <TD PORT=\"here\">d</TD>\n"
795 : " <TD>e</TD>\n"
796 : " </TR>\n"
797 : " <TR>\n"
798 : " <TD COLSPAN=\"3\">f</TD>\n"
799 : " </TR>\n"
800 : "</TABLE>\n"
801 : ">];\n"
802 : " struct1:f1 -> struct2:f0;\n"
803 : " struct1:f2 -> struct3:here;\n"
804 : "}\n"));
805 4 : }
806 :
807 : /* Run all of the selftests within this file. */
808 :
809 : void
810 4 : graphviz_cc_tests ()
811 : {
812 4 : test_ids ();
813 4 : test_trivial_graph ();
814 4 : test_layout_example ();
815 4 : }
816 :
817 : } // namespace selftest
818 :
819 : #endif /* CHECKING_P */
|