Vcsn  2.6
Be Rational
dot.hh
Go to the documentation of this file.
1 #pragma once
2 
3 #include <algorithm>
4 #include <iostream>
5 #include <sstream>
6 
7 #include <vcsn/algos/accessible.hh> // useful_states
9 #include <vcsn/dyn/automaton.hh>
10 #include <vcsn/dyn/fwd.hh>
12 #include <vcsn/misc/iostream.hh>
13 #include <vcsn/misc/set.hh>
15 
16 namespace vcsn
17 {
18 
19  namespace detail
20  {
21  /*-------------------------.
22  | dot(automaton, stream). |
23  `-------------------------*/
24 
28  template <Automaton Aut>
29  class dot_impl: public printer<Aut>
30  {
31  private:
33  using typename super_t::automaton_t;
34  using typename super_t::state_t;
35  using typename super_t::polynomial_t;
36  using typename super_t::transition_t;
37  using typename super_t::weightset_t;
38  using typename super_t::weight_t;
39 
40  using super_t::aut_;
41  using super_t::finals_;
42  using super_t::initials_;
43  using super_t::os_;
44  using super_t::ps_;
45  using super_t::ws_;
46 
47  using super_t::super_t;
48 
49  // Dot, by default, uses the X11 color naming scheme, whose "gray"
50  // is really light (it looks almost blue in some cases).
51  const char* gray = "color = DimGray";
52 
53  public:
54  dot_impl(const automaton_t& aut, std::ostream& out, format fmt,
55  bool mathjax)
56  : super_t(aut, out)
57  , format_(fmt)
58  {
59 #if defined __GNUC__ && ! defined __clang__
60  // GCC 4.9 and 5.0 warnings: see
61  // <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65324>.
62 # pragma GCC diagnostic push
63 # pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
64 #endif
66 #if defined __GNUC__ && ! defined __clang__
67 # pragma GCC diagnostic pop
68 #endif
69  bos_.push(out);
70  dot2tex_ = fmt == format::latex && !mathjax;
71  }
72 
74  std::ostream& operator()()
75  {
77  print_states_();
80  return os_;
81  }
82 
83  private:
84  config::config_value get_style(const std::string& style_name)
85  {
86  auto conf = get_config()["dot"]["styles"];
87  auto style = conf[style_name];
88  while (style.is_valid("inherits"))
89  {
90  auto parent = style["inherits"].str();
91  //std::cerr << "\n\nmergin parent: " << parent << std::endl;
92  style.remove("inherits");
93  style.merge(conf[parent]);
94  }
95 
96  get_config()["dot"]["styles"][style_name] = style;
97  return style;
98  }
99 
102  {
103  bos_ <<
104  "digraph\n"
105  "{\n"
106  " vcsn_context = \"";
107  enable_();
108  aut_->context().print_set(bos_, format::sname);
109  disable_();
110  bos_ << "\"\n"
111  " rankdir = LR\n"
112  " edge [";
113 
114  if (dot2tex_)
115  bos_ << "texmode = math, lblstyle = auto";
116  else
117  {
118  auto style_name = get_config()["dot"]["default-style"].str();
119  auto conf = get_style(style_name)["edge"];
120  auto keys = conf.keys();
121  bos_ << keys[0] << " = " << conf[keys[0]].str();
122  for (size_t i = 1; i < keys.size(); i++)
123  {
124  bos_ << ", " << keys[i] << " = " << conf[keys[i]].str();
125  }
126  }
127  bos_ << "]\n" << std::flush;
128 
129  if (dot2tex_)
130  bos_ <<
131  " d2toptions = \"--format tikz --tikzedgelabels"
132  " --graphstyle=automaton --crop --nominsize --autosize\"\n"
133  " d2tdocpreamble = \""
134  " \\usepackage{amssymb}"
135  " \\usetikzlibrary{arrows.meta, automata, bending}"
136  " \\tikzstyle{automaton}=[shorten >=1pt, pos=.4,"
137  " >={Stealth[bend,round]}, initial text=]"
138  " \\tikzstyle{named}=[rectangle, rounded corners]"
139  " \\tikzstyle{initial}=[initial by arrow]"
140  " \\tikzstyle{accepting}=[accepting by arrow]"
141  " \"\n";
142  }
143 
146  {
147  bos_ << '}';
148  }
149 
155  bool print_(const std::string& sep,
156  const std::string& kind, const weight_t& w)
157  {
158  if (ws_.is_zero(w))
159  return false;
160  else
161  {
162  bos_ << sep << kind;
163  if (ws_.show_one() || !ws_.is_one(w))
164  {
165  bos_ << ", " << kind << " text={";
166  ws_.print(w, bos_, format_) << '}';
167  }
168  return true;
169  }
170  }
171 
173  void
175  {
176  aut_->print_state(s, bos_);
177  bool has_attributes = false;
178  if (dot2tex_)
179  {
180  has_attributes = true;
181  bos_ << " [";
182  std::string style;
183  std::string sep;
184  std::string close;
185  // I hate this piece of code. There must be means to be
186  // better looking...
187  if (aut_->state_has_name(s))
188  {
189  bos_ << "label = \"";
190  enable_();
191  aut_->print_state_name(s, bos_, format_);
192  disable_();
193  static bool debug = getenv("VCSN_DEBUG");
194  if (debug)
195  bos_ << " (" << s << ')';
196  bos_ << "\", style = \"named";
197  sep = ", ";
198  close = "\"";
199  }
200  else
201  sep = "style = \"state, ";
202  if (print_(sep, "initial", aut_->get_initial_weight(s)))
203  {
204  sep = ", ";
205  close = "\"";
206  }
207  if (print_(sep, "accepting", aut_->get_final_weight(s)))
208  close = "\"";
209  bos_ << close;
210  }
211  else
212  {
213  // Dot format.
214  if (aut_->state_has_name(s))
215  {
216  has_attributes = true;
217  bos_ << " [label = \"";
218  if (format_ == format::latex)
219  bos_ << '$';
220  enable_();
221  aut_->print_state_name(s, bos_, format_);
222  disable_();
223  if (format_ == format::latex)
224  bos_ << '$';
225  static bool debug = getenv("VCSN_DEBUG");
226  if (debug)
227  bos_ << " (" << s << ')';
228  bos_ << "\", shape = box";
229  if (format_ == format::latex)
230  {
231  // Approximate a fixed width based on raw text
232  // output.
233  std::ostringstream oss;
234  aut_->print_state_name(s, oss, format::text);
235  float len = oss.str().size();
236  float width = 0.5f + 0.1f * (len / 2);
237  bos_ << ", fixedsize = true"
238  << ", width = " << width;
239  }
240  }
241  if (aut_->is_lazy(s))
242  {
243  if (has_attributes)
244  bos_ << ", ";
245  else
246  {
247  bos_ << " [";
248  has_attributes = true;
249  }
250  bos_ << "style = dashed";
251  }
252  }
253  if (!has(useful_, s))
254  {
255  if (has_attributes)
256  bos_ << ", ";
257  else
258  {
259  bos_ << " [";
260  has_attributes = true;
261  }
262  bos_ << gray;
263  }
264  if (has_attributes)
265  bos_ << ']';
266  }
267 
270  {
271  if (!dot2tex_)
272  {
273  // Output the pre-initial and post-final states.
274  if (!initial_transitions(aut_).empty()
275  || !final_transitions(aut_).empty())
276  {
277  bos_ <<
278  " {\n"
279  " node [shape = point, width = 0]\n";
280  for (auto s : initials_())
281  {
282  bos_ << " I";
283  aut_->print_state(s, bos_);
284  bos_ << '\n';
285  }
286  for (auto s : finals_())
287  {
288  bos_ << " F";
289  aut_->print_state(s, bos_);
290  bos_ << '\n';
291  }
292  bos_ << " }\n";
293  }
294  }
295 
296  // Output all the states to make "print | read" idempotent.
297  //
298  // Put the useless ones in gray. This does not work:
299  //
300  // { 0 1 2 }
301  // { node [color = gray] 2 }
302  //
303  // because 2 was already "declared", and dot does not associate
304  // "color = gray" to it.
305  //
306  // Set the width to something nicer than the default and shape
307  // to rounded. Useless for circle, but useful for shape =
308  // box, and simpler to set it once for all.
309  if (!aut_->states().empty())
310  {
311  bos_ << " {\n"
312  << " node [";
313 
314  if (dot2tex_)
315  bos_ << "texmode = math, style = state";
316  else
317  {
318  auto style_name = get_config()["dot"]["default-style"].str();
319  auto conf = get_style(style_name)["node"];
320  auto keys = conf.keys();
321  bos_ << keys[0] << " = " << conf[keys[0]].str();
322  for (size_t i = 1; i < keys.size(); i++)
323  {
324  bos_ << ", " << keys[i] << " = " << conf[keys[i]].str();
325  }
326  }
327  bos_ << "]\n";
328  for (auto s : aut_->states())
329  {
330  bos_ << " ";
331  print_state_(s);
332  bos_ << '\n';
333  }
334  bos_ << " }\n";
335  }
336  }
337 
339  void print_transitions_(const state_t src, const state_t dst,
340  const polynomial_t& entry)
341  {
342  bos_ << " ";
343  if (src == aut_->pre())
344  {
345  bos_ << 'I';
346  aut_->print_state(dst, bos_);
347  }
348  else
349  aut_->print_state(src, bos_);
350  bos_ << " -> ";
351  if (dst == aut_->post())
352  {
353  bos_ << 'F';
354  aut_->print_state(src, bos_);
355  }
356  else
357  aut_->print_state(dst, bos_);
358 
359  auto e = to_string(ps_, entry, format_, ", ");
360  bool useless = !has(useful_, src) || !has(useful_, dst);
361  if (!e.empty() || useless)
362  {
363  bos_ << " [";
364  const char* sep = "";
365  if (!e.empty())
366  {
367  bos_ << "label = \"";
368  enable_();
369  bos_ << e;
370  disable_();
371  bos_ << "\"";
372  sep = ", ";
373  }
374  if (useless)
375  bos_ << sep << gray;
376  bos_ << ']';
377  }
378  bos_ << '\n';
379  }
380 
383  {
384  // For each src state, the destinations, sorted.
385  auto dsts = std::map<state_t, polynomial_t>{};
386  for (auto src : aut_->all_states())
387  if (!aut_->is_lazy(src)
388  && (!dot2tex_ || src != aut_->pre()))
389  {
390  dsts.clear();
391  for (auto t: all_out(aut_, src))
392  if (!dot2tex_ || aut_->dst_of(t) != aut_->post())
393  // Bypass weight_of(set), because we know that the weight is
394  // nonzero, and that there is only one weight per letter.
395  ps_.new_weight(dsts[aut_->dst_of(t)],
396  aut_->label_of(t), aut_->weight_of(t));
397  for (const auto& p: dsts)
398  print_transitions_(src, p.first, p.second);
399  }
400  }
401 
403  void enable_()
404  {
405  boost::iostreams::flush(bos_);
406  bos_.component<detail::backslashify_output_filter>(0)->enable();
407  }
408 
410  void disable_()
411  {
412  boost::iostreams::flush(bos_);
413  bos_.component<detail::backslashify_output_filter>(0)->disable();
414  }
415 
417  detail::io::filtering_ostream bos_;
421  bool dot2tex_ = false;
423  std::unordered_set<state_t_of<Aut>> useful_ = useful_states(aut_, false);
424  };
425  }
426 
433  template <Automaton Aut>
434  std::ostream&
435  dot(const Aut& aut, std::ostream& out = std::cout, format fmt = {},
436  bool mathjax = false)
437  {
438  // Cannot use auto here.
439  detail::dot_impl<Aut> dot{aut, out, fmt, mathjax};
440  return dot();
441  }
442 }
std::ostream & dot(const Aut &aut, std::ostream &out=std::cout, format fmt={}, bool mathjax=false)
Print an automaton in Graphviz&#39;s Dot format.
Definition: dot.hh:435
void print_transitions_(const state_t src, const state_t dst, const polynomial_t &entry)
Print the transitions between state src and state dst.
Definition: dot.hh:339
dot_impl(const automaton_t &aut, std::ostream &out, format fmt, bool mathjax)
Definition: dot.hh:54
const polynomialset_t ps_
Short-hand to the polynomialset used to print the entries.
Definition: printer.hh:141
std::unordered_set< state_t_of< Aut > > useful_
Useful states, without evaluating the lazy states.
Definition: dot.hh:423
std::string to_string(direction d)
Conversion to string.
Definition: direction.cc:7
bool print_(const std::string &sep, const std::string &kind, const weight_t &w)
Print a TikZ attribute.
Definition: dot.hh:155
void print_epilogue_()
Finish the dot graph.
Definition: dot.hh:145
typename polynomialset_t::value_t polynomial_t
Definition: printer.hh:59
void disable_()
Disable the escaping of backslashes.
Definition: dot.hh:410
The class returned by [] operators.
states_t finals_()
The list of final states, sorted.
Definition: printer.hh:123
const char * gray
Definition: dot.hh:51
format format_
Format for labels and weights.
Definition: dot.hh:419
An input/output format for valuesets.
Definition: format.hh:13
Factor common bits in automaton formatting.
Definition: printer.hh:37
void print_states_()
Print the states.
Definition: dot.hh:269
detail::config & get_config()
Get the configuration singleton.
ATTRIBUTE_PURE bool has(const boost::container::flat_set< Key, Compare, Allocator > &s, const Key &e)
Whether e is member of s.
Definition: setalpha.hh:26
config::config_value get_style(const std::string &style_name)
Definition: dot.hh:84
const weightset_t & ws_
Short-hand to the weightset.
Definition: printer.hh:139
states_t< Aut > useful_states(const Aut &a, bool strict=true)
The set of useful states, including possibly pre() and post().
Definition: accessible.hh:79
void print_state_(state_t s)
Pretty-print state s.
Definition: dot.hh:174
std::ostream & operator()()
Print the automaton on the stream.
Definition: dot.hh:74
Format an automaton into Dot.
Definition: dot.hh:29
bool dot2tex_
Whether we need dot2tex formatting.
Definition: dot.hh:421
auto final_transitions(const Aut &aut) -> decltype(aut->all_in(aut->post()))
Indexes of transitions from (visible) final states.
Definition: automaton.hh:178
auto out(const Aut &aut, state_t_of< Aut > s)
Indexes of visible transitions leaving state s.
Definition: automaton.hh:86
detail::io::filtering_ostream bos_
The output stream, with a backslashify filter.
Definition: dot.hh:417
states_t initials_()
The list of initial states, sorted.
Definition: printer.hh:113
Print for LaTeX.
Definition: format.hh:22
automaton_t aut_
The automaton we have to output.
Definition: printer.hh:133
Definition: a-star.hh:8
state_t_of< automaton_t > state_t
Definition: printer.hh:50
void print_prologue_()
Start the dot graph.
Definition: dot.hh:101
weightset_t_of< automaton_t > weightset_t
Definition: printer.hh:56
Print as plain (ASCII) text, escaped.
Definition: format.hh:28
void enable_()
Enable the escaping of backslashes.
Definition: dot.hh:403
auto all_out(const Aut &aut, state_t_of< Aut > s)
Indexes of transitions leaving state s.
Definition: automaton.hh:67
std::vector< std::string > keys() const
void print_transitions_()
Print all the transitions, sorted by src state, then dst state.
Definition: dot.hh:382
transition_t_of< automaton_t > transition_t
Definition: printer.hh:55
weight_t_of< automaton_t > weight_t
Definition: printer.hh:57
std::ostream & os_
Output stream.
Definition: printer.hh:135
Print as a parsable type string.
Definition: format.hh:26
auto initial_transitions(const Aut &aut) -> decltype(aut->all_out(aut->pre()))
Indexes of transitions to (visible) initial states.
Definition: automaton.hh:167