Branch data Line data Source code
1 : : /* Helper code for graphviz output.
2 : : Copyright (C) 2019-2025 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 : 49 : dot::writer::writer (pretty_printer &pp)
34 : 49 : : m_pp (pp),
35 : 49 : m_indent (0)
36 : : {
37 : 49 : }
38 : :
39 : : /* Print the current indent to the underlying pp. */
40 : :
41 : : void
42 : 5330 : dot::writer::write_indent ()
43 : : {
44 : 50722 : for (int i = 0; i < m_indent * 4; ++i)
45 : 45392 : pp_space (get_pp ());
46 : 5330 : }
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 : 1419 : graphviz_out::print (const char *fmt, ...)
58 : : {
59 : 1419 : va_list ap;
60 : :
61 : 1419 : va_start (ap, fmt);
62 : 1419 : text_info text (fmt, &ap, errno);
63 : 1419 : pp_format (get_pp (), &text);
64 : 1419 : pp_output_formatted_text (get_pp ());
65 : 1419 : va_end (ap);
66 : 1419 : }
67 : :
68 : : /* Formatted print of FMT. The text is indented by the current
69 : : indent, and a newline is added. */
70 : :
71 : : void
72 : 4318 : graphviz_out::println (const char *fmt, ...)
73 : : {
74 : 4318 : va_list ap;
75 : :
76 : 4318 : write_indent ();
77 : :
78 : 4318 : va_start (ap, fmt);
79 : 4318 : text_info text (fmt, &ap, errno);
80 : 4318 : pp_format (get_pp (), &text);
81 : 4318 : pp_output_formatted_text (get_pp ());
82 : 4318 : va_end (ap);
83 : :
84 : 4318 : pp_newline (get_pp ());
85 : 4318 : }
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 : 565 : graphviz_out::begin_tr ()
92 : : {
93 : 565 : pp_string (get_pp (), "<TR>");
94 : 565 : pp_write_text_to_stream (get_pp ());
95 : 565 : }
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 : 557 : graphviz_out::end_tr ()
102 : : {
103 : 557 : pp_string (get_pp (), "</TR>");
104 : 557 : pp_write_text_to_stream (get_pp ());
105 : 557 : }
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 : 597 : graphviz_out::begin_td ()
112 : : {
113 : 597 : pp_string (get_pp (), "<TD ALIGN=\"LEFT\">");
114 : 597 : pp_write_text_to_stream (get_pp ());
115 : 597 : }
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 : 597 : graphviz_out::end_td ()
122 : : {
123 : 597 : pp_string (get_pp (), "</TD>");
124 : 597 : pp_write_text_to_stream (get_pp ());
125 : 597 : }
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 : 755 : graphviz_out::begin_trtd ()
132 : : {
133 : 755 : pp_string (get_pp (), "<TR><TD ALIGN=\"LEFT\">");
134 : 755 : pp_write_text_to_stream (get_pp ());
135 : 755 : }
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 : 763 : graphviz_out::end_tdtr ()
142 : : {
143 : 763 : pp_string (get_pp (), "</TD></TR>");
144 : 763 : pp_write_text_to_stream (get_pp ());
145 : 763 : }
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 : 126 : id::id (std::string str)
167 : 126 : : m_str (std::move (str)),
168 : 126 : m_kind (is_identifier_p (m_str.c_str ())
169 : 126 : ? kind::identifier
170 : 126 : : kind::quoted)
171 : : {
172 : 126 : }
173 : :
174 : 13 : id::id (const xml::node &n)
175 : 13 : : m_kind (kind::html)
176 : : {
177 : 13 : pretty_printer pp;
178 : 13 : n.write_as_xml (&pp, 0, true);
179 : 13 : m_str = pp_formatted_text (&pp);
180 : 13 : }
181 : :
182 : : void
183 : 128 : id::print (writer &w) const
184 : : {
185 : 128 : switch (m_kind)
186 : : {
187 : 0 : default:
188 : 0 : gcc_unreachable ();
189 : :
190 : 107 : case kind::identifier:
191 : 107 : w.write_string (m_str.c_str ());
192 : 107 : break;
193 : :
194 : 8 : case kind::quoted:
195 : 8 : w.write_character ('"');
196 : 108 : for (auto ch : m_str)
197 : 100 : if (ch == '"')
198 : 0 : w.write_string ("\\\"");
199 : : else
200 : 100 : w.write_character (ch);
201 : 8 : w.write_character ('"');
202 : 8 : break;
203 : :
204 : 13 : case kind::html:
205 : 13 : w.write_character ('<');
206 : 13 : w.write_string (m_str.c_str ());
207 : 13 : w.write_character ('>');
208 : 13 : break;
209 : :
210 : : }
211 : 128 : }
212 : :
213 : : bool
214 : 146 : id::is_identifier_p (const char *str)
215 : : {
216 : 146 : const char initial_ch = *str;
217 : 146 : if (initial_ch != '_' && !ISALPHA (initial_ch))
218 : : return false;
219 : 662 : for (const char *iter = str + 1; *iter; ++iter)
220 : : {
221 : 532 : const char iter_ch = *iter;
222 : 532 : if (iter_ch != '_' && !ISALNUM (iter_ch))
223 : : return false;
224 : : }
225 : : return true;
226 : : }
227 : :
228 : : // struct kv_pair
229 : :
230 : : void
231 : 31 : kv_pair::print (writer &w) const
232 : : {
233 : 31 : m_key.print (w);
234 : 31 : w.write_character ('=');
235 : 31 : m_value.print (w);
236 : 31 : }
237 : :
238 : : // struct attr_list
239 : :
240 : : void
241 : 38 : attr_list::print (writer &w) const
242 : : {
243 : 38 : if (m_kvs.empty ())
244 : : return;
245 : 26 : w.write_string (" [");
246 : 57 : for (auto iter = m_kvs.begin (); iter != m_kvs.end (); ++iter)
247 : : {
248 : 31 : if (iter != m_kvs.begin ())
249 : 5 : w.write_string ("; ");
250 : 31 : iter->print (w);
251 : : }
252 : 26 : w.write_string ("]");
253 : : }
254 : :
255 : : // struct stmt_list
256 : :
257 : : void
258 : 10 : stmt_list::print (writer &w) const
259 : : {
260 : 49 : for (auto &stmt : m_stmts)
261 : : {
262 : 39 : w.write_indent ();
263 : 39 : stmt->print (w);
264 : 39 : w.write_string (";");
265 : 39 : w.write_newline ();
266 : : }
267 : 10 : }
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 : 0 : stmt_list::add_attr (id key, id value)
279 : : {
280 : 0 : add_stmt
281 : 0 : (std::make_unique <kv_stmt> (kv_pair (std::move (key),
282 : 0 : std::move (value))));
283 : 0 : }
284 : :
285 : : // struct graph
286 : :
287 : : void
288 : 9 : graph::print (writer &w) const
289 : : {
290 : 9 : w.write_indent ();
291 : 9 : w.write_string ("digraph ");
292 : 9 : if (m_id)
293 : : {
294 : 4 : m_id->print (w);
295 : 4 : w.write_character (' ');
296 : : }
297 : 9 : w.write_string ("{");
298 : 9 : w.write_newline ();
299 : :
300 : 9 : w.indent ();
301 : 9 : m_stmt_list.print (w);
302 : 9 : w.outdent ();
303 : :
304 : 9 : w.write_indent ();
305 : 9 : w.write_string ("}");
306 : 9 : w.write_newline ();
307 : 9 : }
308 : :
309 : : // struct stmt_with_attr_list : public stmt
310 : :
311 : : void
312 : 12 : stmt_with_attr_list::set_label (dot::id value)
313 : : {
314 : 12 : m_attrs.add (dot::id ("label"), std::move (value));
315 : 12 : }
316 : :
317 : : // struct node_stmt : public stmt_with_attr_list
318 : :
319 : : void
320 : 21 : node_stmt::print (writer &w) const
321 : : {
322 : 21 : m_id.print (w);
323 : 21 : m_attrs.print (w);
324 : 21 : }
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 : 0 : kv_stmt::print (writer &w) const
352 : : {
353 : 0 : m_kv.print (w);
354 : 0 : }
355 : :
356 : : // struct node_id
357 : :
358 : : void
359 : 24 : node_id::print (writer &w) const
360 : : {
361 : 24 : m_id.print (w);
362 : 24 : if (m_port)
363 : 16 : m_port->print (w);
364 : 24 : }
365 : :
366 : : // struct port
367 : :
368 : : void
369 : 16 : port::print (writer &w) const
370 : : {
371 : 16 : if (m_id)
372 : : {
373 : 16 : w.write_character (':');
374 : 16 : m_id->print (w);
375 : : }
376 : 16 : if (m_compass_pt)
377 : : {
378 : 0 : w.write_character (':');
379 : 0 : switch (*m_compass_pt)
380 : : {
381 : 0 : default:
382 : 0 : gcc_unreachable ();
383 : 0 : case compass_pt::n:
384 : 0 : w.write_string ("n");
385 : 0 : break;
386 : 0 : case compass_pt::ne:
387 : 0 : w.write_string ("ne");
388 : 0 : break;
389 : 0 : case compass_pt::e:
390 : 0 : w.write_string ("e");
391 : 0 : break;
392 : 0 : case compass_pt::se:
393 : 0 : w.write_string ("se");
394 : 0 : break;
395 : 0 : case compass_pt::s:
396 : 0 : w.write_string ("s");
397 : 0 : break;
398 : 0 : case compass_pt::sw:
399 : 0 : w.write_string ("sw");
400 : 0 : break;
401 : 0 : case compass_pt::w:
402 : 0 : w.write_string ("w");
403 : 0 : break;
404 : 0 : case compass_pt::nw:
405 : 0 : w.write_string ("nw");
406 : 0 : break;
407 : 0 : case compass_pt::c:
408 : 0 : w.write_string ("c");
409 : 0 : break;
410 : : }
411 : : }
412 : 16 : }
413 : :
414 : : // struct edge_stmt : public stmt_with_attr_list
415 : :
416 : : void
417 : 12 : edge_stmt::print (writer &w) const
418 : : {
419 : 36 : for (auto iter = m_node_ids.begin (); iter != m_node_ids.end (); ++iter)
420 : : {
421 : 24 : if (iter != m_node_ids.begin ())
422 : 12 : w.write_string (" -> ");
423 : 24 : iter->print (w);
424 : : }
425 : 12 : m_attrs.print (w);
426 : 12 : }
427 : :
428 : : // struct subgraph : public stmt
429 : :
430 : : void
431 : 1 : subgraph::print (writer &w) const
432 : : {
433 : 1 : w.write_newline ();
434 : 1 : w.write_indent ();
435 : 1 : w.write_string ("subgraph ");
436 : 1 : m_id.print (w);
437 : 1 : w.write_string (" {");
438 : 1 : w.write_newline ();
439 : :
440 : 1 : w.indent ();
441 : 1 : m_stmt_list.print (w);
442 : 1 : w.outdent ();
443 : 1 : w.write_newline ();
444 : :
445 : 1 : w.write_indent ();
446 : 1 : w.write_string ("}"); // newline and semicolon added by stmt_list
447 : 1 : }
448 : :
449 : : /* Convert G to graphviz source, attempt to invoke "dot -Tsvg" on it
450 : : as a subprocess, and get the SVG source from stdout, or nullptr
451 : : if there was a problem. */
452 : :
453 : : static std::unique_ptr<std::string>
454 : 1 : make_svg_document_buffer_from_graph (const graph &g)
455 : : {
456 : : /* Ideally there would be a way of doing this without
457 : : invoking dot as a subprocess. */
458 : :
459 : 1 : std::vector<std::string> args;
460 : 1 : args.push_back ("dot");
461 : 1 : args.push_back ("-Tsvg");
462 : :
463 : 1 : pex p (0, "dot", nullptr);
464 : :
465 : 1 : {
466 : 1 : auto pipe_stdin = p.input_file (true, nullptr);
467 : 1 : gcc_assert (pipe_stdin.m_file);
468 : 1 : pretty_printer pp;
469 : 1 : pp.set_output_stream (pipe_stdin.m_file);
470 : 1 : writer w (pp);
471 : 1 : g.print (w);
472 : 1 : pp_flush (&pp);
473 : 1 : }
474 : :
475 : 1 : int err = 0;
476 : 1 : const char * errmsg
477 : 1 : = p.run (PEX_SEARCH,
478 : : "dot", args, nullptr, nullptr, &err);
479 : 1 : auto pipe_stdout = p.read_output ();
480 : 1 : auto content = pipe_stdout.read_all ();
481 : :
482 : 1 : if (errmsg)
483 : 0 : return nullptr;
484 : 1 : if (err)
485 : 0 : return nullptr;
486 : :
487 : 1 : std::string result;
488 : 1 : result.reserve (content->size () + 1);
489 : 6322 : for (auto &iter : *content)
490 : 6321 : result.push_back (iter);
491 : 1 : return std::make_unique<std::string> (std::move (result));
492 : 1 : }
493 : :
494 : : /* Convert G to graphviz source, attempt to invoke "dot -Tsvg" on it
495 : : as a subprocess, and get the SVG source from stdout, and extract
496 : : the "svg" subtree as an xml::raw node.
497 : :
498 : : Note that this
499 : : (a) invokes "dot" as a subprocess
500 : : (b) assumes that we trust the output from "dot".
501 : :
502 : : Return nullptr if there was a problem. */
503 : :
504 : : std::unique_ptr<xml::node>
505 : 1 : make_svg_from_graph (const graph &g)
506 : : {
507 : 1 : auto svg_src = make_svg_document_buffer_from_graph (g);
508 : 1 : if (!svg_src)
509 : 0 : return nullptr;
510 : :
511 : : /* Skip past the XML header to the parts we care about. */
512 : 1 : auto pos = svg_src->find ("<!-- Generated by graphviz");
513 : 1 : if (pos == svg_src->npos)
514 : 0 : return nullptr;
515 : :
516 : 1 : auto substring = std::string (*svg_src, pos);
517 : 1 : return std::make_unique<xml::raw> (std::move (substring));
518 : 1 : }
519 : :
520 : : } // namespace dot
521 : :
522 : : #if CHECKING_P
523 : :
524 : : namespace selftest {
525 : :
526 : : static void
527 : 4 : test_ids ()
528 : : {
529 : 4 : ASSERT_TRUE (dot::id::is_identifier_p ("foo"));
530 : 4 : ASSERT_FALSE (dot::id::is_identifier_p ("hello world"));
531 : 4 : ASSERT_TRUE (dot::id::is_identifier_p ("foo42"));
532 : 4 : ASSERT_FALSE (dot::id::is_identifier_p ("42"));
533 : 4 : ASSERT_TRUE (dot::id::is_identifier_p ("_"));
534 : 4 : }
535 : :
536 : : static void
537 : 4 : test_trivial_graph ()
538 : : {
539 : 4 : dot::graph g;
540 : : // node "a"
541 : 4 : {
542 : 4 : g.add_stmt (std::make_unique<dot::node_stmt> (dot::id ("a")));
543 : : }
544 : : // node "b"
545 : 4 : {
546 : 4 : auto n = std::make_unique<dot::node_stmt> (dot::id ("b"));
547 : 4 : n->m_attrs.add (dot::id ("label"), dot::id ("This is node b"));
548 : 4 : n->m_attrs.add (dot::id ("color"), dot::id ("green"));
549 : 4 : g.add_stmt (std::move (n));
550 : 4 : }
551 : : // an edge between them
552 : 4 : {
553 : 12 : auto e = std::make_unique<dot::edge_stmt> (dot::id ("a"),
554 : 12 : dot::id ("b"));
555 : 4 : e->m_attrs.add (dot::id ("label"), dot::id ("I'm an edge"));
556 : 4 : g.add_stmt (std::move (e));
557 : 4 : }
558 : 4 : pretty_printer pp;
559 : 4 : dot::writer w (pp);
560 : 4 : g.print (w);
561 : 4 : ASSERT_STREQ
562 : : (pp_formatted_text (&pp),
563 : : ("digraph {\n"
564 : : " a;\n"
565 : : " b [label=\"This is node b\"; color=green];\n"
566 : : " a -> b [label=\"I'm an edge\"];\n"
567 : : "}\n"));
568 : 4 : }
569 : :
570 : : /* Recreating the HTML record example from
571 : : https://graphviz.org/doc/info/shapes.html#html */
572 : :
573 : : static void
574 : 4 : test_layout_example ()
575 : : {
576 : 4 : dot::graph g (dot::id ("structs"));
577 : :
578 : : // "node [shape=plaintext]\n"
579 : 4 : {
580 : 4 : auto attr_stmt
581 : 4 : = std::make_unique<dot::attr_stmt> (dot::attr_stmt::kind::node);
582 : 4 : attr_stmt->m_attrs.add (dot::id ("shape"), dot::id ("plaintext"));
583 : 4 : g.add_stmt (std::move (attr_stmt));
584 : 4 : }
585 : :
586 : : // struct1
587 : 4 : {
588 : 4 : auto n = std::make_unique<dot::node_stmt> (dot::id ("struct1"));
589 : :
590 : 4 : xml::element table ("TABLE", false);
591 : 4 : xml::printer xp (table);
592 : 4 : xp.set_attr ("BORDER", "0");
593 : 4 : xp.set_attr ("CELLBORDER", "1");
594 : 4 : xp.set_attr ("CELLSPACING", "0");
595 : :
596 : 4 : xp.push_tag ("TR", true);
597 : :
598 : 4 : xp.push_tag ("TD", false);
599 : 4 : xp.add_text ("left");
600 : 4 : xp.pop_tag ("TD");
601 : :
602 : 4 : xp.push_tag ("TD", false);
603 : 4 : xp.set_attr ("PORT", "f1");
604 : 4 : xp.add_text ("mid dle");
605 : 4 : xp.pop_tag ("TD");
606 : :
607 : 4 : xp.push_tag ("TD", false);
608 : 4 : xp.set_attr ("PORT", "f2");
609 : 4 : xp.add_text ("right");
610 : 4 : xp.pop_tag ("TD");
611 : :
612 : 4 : n->set_label (table);
613 : 4 : g.add_stmt (std::move (n));
614 : 4 : }
615 : :
616 : : // struct2
617 : 4 : {
618 : 4 : auto n = std::make_unique<dot::node_stmt> (dot::id ("struct2"));
619 : 4 : xml::element table ("TABLE", false);
620 : 4 : xml::printer xp (table);
621 : 4 : xp.set_attr ("BORDER", "0");
622 : 4 : xp.set_attr ("CELLBORDER", "1");
623 : 4 : xp.set_attr ("CELLSPACING", "0");
624 : :
625 : 4 : xp.push_tag ("TR", true);
626 : :
627 : 4 : xp.push_tag ("TD", false);
628 : 4 : xp.set_attr ("PORT", "f0");
629 : 4 : xp.add_text ("one");
630 : 4 : xp.pop_tag ("TD");
631 : :
632 : 4 : xp.push_tag ("TD", false);
633 : 4 : xp.add_text ("two");
634 : 4 : xp.pop_tag ("TD");
635 : :
636 : 4 : n->set_label (table);
637 : 4 : g.add_stmt (std::move (n));
638 : 4 : }
639 : :
640 : : // struct3
641 : 4 : {
642 : 4 : auto n = std::make_unique<dot::node_stmt> (dot::id ("struct3"));
643 : 4 : xml::element table ("TABLE", false);
644 : 4 : xml::printer xp (table);
645 : 4 : xp.set_attr ("BORDER", "0");
646 : 4 : xp.set_attr ("CELLBORDER", "1");
647 : 4 : xp.set_attr ("CELLSPACING", "0");
648 : 4 : xp.set_attr ("CELLPADDING", "4");
649 : :
650 : 4 : xp.push_tag ("TR", false);
651 : :
652 : 4 : xp.push_tag ("TD", true);
653 : 4 : xp.set_attr ("ROWSPAN", "3");
654 : 4 : xp.add_text ("hello");
655 : 4 : xp.append (std::make_unique<xml::element> ("BR", false));
656 : 4 : xp.add_text ("world");
657 : 4 : xp.pop_tag ("TD");
658 : :
659 : 4 : xp.push_tag ("TD", true);
660 : 4 : xp.set_attr ("COLSPAN", "3");
661 : 4 : xp.add_text ("b");
662 : 4 : xp.pop_tag ("TD");
663 : :
664 : 4 : xp.push_tag ("TD", true);
665 : 4 : xp.set_attr ("ROWSPAN", "3");
666 : 4 : xp.add_text ("g");
667 : 4 : xp.pop_tag ("TD");
668 : :
669 : 4 : xp.push_tag ("TD", true);
670 : 4 : xp.set_attr ("ROWSPAN", "3");
671 : 4 : xp.add_text ("h");
672 : 4 : xp.pop_tag ("TD");
673 : :
674 : 4 : xp.pop_tag ("TR");
675 : :
676 : 4 : xp.push_tag ("TR", false);
677 : :
678 : 4 : xp.push_tag ("TD", true);
679 : 4 : xp.add_text ("c");
680 : 4 : xp.pop_tag ("TD");
681 : :
682 : 4 : xp.push_tag ("TD", true);
683 : 4 : xp.set_attr ("PORT", "here");
684 : 4 : xp.add_text ("d");
685 : 4 : xp.pop_tag ("TD");
686 : :
687 : 4 : xp.push_tag ("TD", true);
688 : 4 : xp.add_text ("e");
689 : 4 : xp.pop_tag ("TD");
690 : :
691 : 4 : xp.pop_tag ("TR");
692 : :
693 : 4 : xp.push_tag ("TR", false);
694 : :
695 : 4 : xp.push_tag ("TD", true);
696 : 4 : xp.set_attr ("COLSPAN", "3");
697 : 4 : xp.add_text ("f");
698 : 4 : xp.pop_tag ("TD");
699 : :
700 : 4 : n->set_label (table);
701 : 4 : g.add_stmt (std::move (n));
702 : 4 : }
703 : :
704 : 4 : g.m_stmt_list.add_edge
705 : 8 : (dot::node_id (dot::id ("struct1"),
706 : 12 : dot::port (dot::id ("f1"))),
707 : 8 : dot::node_id (dot::id ("struct2"),
708 : 12 : dot::port (dot::id ("f0"))));
709 : 4 : g.m_stmt_list.add_edge
710 : 8 : (dot::node_id (dot::id ("struct1"),
711 : 12 : dot::port (dot::id ("f2"))),
712 : 8 : dot::node_id (dot::id ("struct3"),
713 : 12 : dot::port (dot::id ("here"))));
714 : :
715 : 4 : pretty_printer pp;
716 : 4 : dot::writer w (pp);
717 : 4 : g.print (w);
718 : :
719 : : /* There are some whitespace differences with the example in the
720 : : GraphViz docs. */
721 : 4 : ASSERT_STREQ
722 : : (pp_formatted_text (&pp),
723 : : ("digraph structs {\n"
724 : : " node [shape=plaintext];\n" // added semicolon
725 : : " struct1 [label=<<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n"
726 : : " <TR><TD>left</TD><TD PORT=\"f1\">mid dle</TD><TD PORT=\"f2\">right</TD></TR>\n"
727 : : "</TABLE>\n"
728 : : ">];\n"
729 : : " struct2 [label=<<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n"
730 : : " <TR><TD PORT=\"f0\">one</TD><TD>two</TD></TR>\n"
731 : : "</TABLE>\n"
732 : : ">];\n"
733 : : " struct3 [label=<<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"4\">\n"
734 : : " <TR>\n"
735 : : " <TD ROWSPAN=\"3\">hello<BR/>world</TD>\n"
736 : : " <TD COLSPAN=\"3\">b</TD>\n"
737 : : " <TD ROWSPAN=\"3\">g</TD>\n"
738 : : " <TD ROWSPAN=\"3\">h</TD>\n"
739 : : " </TR>\n"
740 : : " <TR>\n"
741 : : " <TD>c</TD>\n"
742 : : " <TD PORT=\"here\">d</TD>\n"
743 : : " <TD>e</TD>\n"
744 : : " </TR>\n"
745 : : " <TR>\n"
746 : : " <TD COLSPAN=\"3\">f</TD>\n"
747 : : " </TR>\n"
748 : : "</TABLE>\n"
749 : : ">];\n"
750 : : " struct1:f1 -> struct2:f0;\n"
751 : : " struct1:f2 -> struct3:here;\n"
752 : : "}\n"));
753 : 4 : }
754 : :
755 : : /* Run all of the selftests within this file. */
756 : :
757 : : void
758 : 4 : graphviz_cc_tests ()
759 : : {
760 : 4 : test_ids ();
761 : 4 : test_trivial_graph ();
762 : 4 : test_layout_example ();
763 : 4 : }
764 : :
765 : : } // namespace selftest
766 : :
767 : : #endif /* CHECKING_P */
|