dot: add x option for dot2tex

* spot/twa/acc.cc, spot/twa/acc.hh: Add a LaTeX output for acceptance
conditions.
* spot/twaalgos/dot.cc: Implement the 'x' option and refactor the code
a bit to limit duplication.
* tests/core/dot2tex.test: New test case (requires dot2tex).
* tests/Makefile.am: Add dot2tex.test.
* tests/core/alternating.test, tests/core/readsave.test,
tests/python/automata-io.ipynb: Adjust expected output.
* NEWS, doc/org/oaut.org: Mention the new option.
This commit is contained in:
Alexandre Duret-Lutz 2017-08-31 18:56:53 +02:00
parent b242122ce8
commit fbb9e4374e
10 changed files with 365 additions and 182 deletions

View file

@ -63,20 +63,45 @@ namespace spot
os << v;
}
template<bool html>
enum code_output {HTML, TEXT, LATEX};
template<enum code_output style>
static void
print_code(std::ostream& os,
const acc_cond::acc_code& code, unsigned pos,
std::function<void(std::ostream&, int)> set_printer)
{
const char* op = " | ";
const char* op_ = style == LATEX ? " \\lor " : " | ";
auto& w = code[pos];
const char* negated = "";
const char* negated_pre = "";
const char* negated_post = "";
auto set_neg = [&]() {
if (style == LATEX)
{
negated_pre = "\\overline{";
negated_post = "}";
}
else
{
negated_pre = "!";
}
};
bool top = pos == code.size() - 1;
switch (w.sub.op)
{
case acc_cond::acc_op::And:
op = html ? " &amp; " : " & ";
switch (style)
{
case HTML:
op_ = " &amp; ";
break;
case TEXT:
op_ = " & ";
break;
case LATEX:
op_ = " \\land ";
break;
}
SPOT_FALLTHROUGH;
case acc_cond::acc_op::Or:
{
@ -90,8 +115,8 @@ namespace spot
if (first)
first = false;
else
os << op;
print_code<html>(os, code, pos, set_printer);
os << op_;
print_code<style>(os, code, pos, set_printer);
pos -= code[pos].sub.size;
}
if (!top)
@ -99,14 +124,17 @@ namespace spot
}
break;
case acc_cond::acc_op::InfNeg:
negated = "!";
set_neg();
SPOT_FALLTHROUGH;
case acc_cond::acc_op::Inf:
{
auto a = code[pos - 1].mark.id;
if (a == 0U)
{
os << 't';
if (style == LATEX)
os << "\\mathsf{t}";
else
os << 't';
}
else
{
@ -115,16 +143,32 @@ namespace spot
top = code[pos - 1].mark.count() == 1;
unsigned level = 0;
const char* and_ = "";
const char* and_next_ = []() {
// The lack of surrounding space in HTML and
// TEXT is on purpose: we want to
// distinguish those grouped "Inf"s from
// other terms that are ANDed together.
switch (style)
{
case HTML:
return "&amp;";
case TEXT:
return "&";
case LATEX:
return " \\land ";
}
}();
if (!top)
os << '(';
const char* inf_ = (style == LATEX) ? "\\mathsf{Inf}(" : "Inf(";
while (a)
{
if (a & 1)
{
os << and_ << "Inf(" << negated;
os << and_ << inf_ << negated_pre;
set_printer(os, level);
os << ')';
and_ = html ? "&amp;" : "&";
os << negated_post << ')';
and_ = and_next_;
}
a >>= 1;
++level;
@ -135,14 +179,17 @@ namespace spot
}
break;
case acc_cond::acc_op::FinNeg:
negated = "!";
set_neg();
SPOT_FALLTHROUGH;
case acc_cond::acc_op::Fin:
{
auto a = code[pos - 1].mark.id;
if (a == 0U)
{
os << 'f';
if (style == LATEX)
os << "\\mathsf{f}";
else
os << 'f';
}
else
{
@ -153,14 +200,19 @@ namespace spot
const char* or_ = "";
if (!top)
os << '(';
const char* fin_ = (style == LATEX) ? "\\mathsf{Fin}(" : "Fin(";
while (a)
{
if (a & 1)
{
os << or_ << "Fin(" << negated;
os << or_ << fin_ << negated_pre;
set_printer(os, level);
os << ')';
or_ = "|";
os << negated_post << ')';
// The lack of surrounding space in HTML and
// TEXT is on purpose: we want to distinguish
// those grouped "Fin"s from other terms that
// are ORed together.
or_ = style == LATEX ? " \\lor " : "|";
}
a >>= 1;
++level;
@ -1407,7 +1459,7 @@ namespace spot
if (empty())
os << 't';
else
print_code<true>(os, *this, size() - 1,
print_code<HTML>(os, *this, size() - 1,
set_printer ? set_printer : default_set_printer);
return os;
}
@ -1420,7 +1472,20 @@ namespace spot
if (empty())
os << 't';
else
print_code<false>(os, *this, size() - 1,
print_code<TEXT>(os, *this, size() - 1,
set_printer ? set_printer : default_set_printer);
return os;
}
std::ostream&
acc_cond::acc_code::to_latex(std::ostream& os,
std::function<void(std::ostream&, int)>
set_printer) const
{
if (empty())
os << "\\mathsf{t}";
else
print_code<LATEX>(os, *this, size() - 1,
set_printer ? set_printer : default_set_printer);
return os;
}

View file

@ -892,6 +892,13 @@ namespace spot
std::function<void(std::ostream&, int)>
set_printer = nullptr) const;
// Print the acceptance as Latex. The set_printer function can
// be used to implement customized output for set numbers.
std::ostream&
to_latex(std::ostream& os,
std::function<void(std::ostream&, int)>
set_printer = nullptr) const;
/// \brief Construct an acc_code from a string.
///

View file

@ -23,6 +23,7 @@
#include <ostream>
#include <sstream>
#include <stdexcept>
#include <spot/tl/print.hh>
#include <spot/twa/twagraph.hh>
#include <spot/twaalgos/dot.hh>
#include <spot/twa/bddprint.hh>
@ -100,6 +101,10 @@ namespace spot
bool opt_numbered_edges_ = false;
bool opt_orig_show_ = false;
bool max_states_given_ = false; // related to max_states_
bool opt_latex_ = false;
const char* nl_ = "\\n";
const char* label_pre_ = "label=\"";
char label_post_ = '"';
const_twa_graph_ptr aut_;
std::string opt_font_;
@ -265,6 +270,9 @@ namespace spot
case 't':
opt_force_acc_trans_ = true;
break;
case 'x':
opt_latex_ = true;
break;
case 'y':
opt_shared_univ_dest_ = false;
break;
@ -272,6 +280,23 @@ namespace spot
throw std::runtime_error
(std::string("unknown option for print_dot(): ") + c);
}
if (opt_html_labels_)
{
nl_ = "<br/>";
label_pre_ = "label=<";
label_post_ = '>';
}
if (opt_latex_)
{
if (opt_latex_ && opt_html_labels_)
throw std::runtime_error
(std::string("print_dot(): options 'r' and 'R' "
"are incompatible with 'x'"));
nl_ = "\\\\{}";
label_pre_ = "texlbl=\"";
label_post_ = '"';
}
}
dotty_output(std::ostream& os, const char* options)
@ -306,7 +331,12 @@ namespace spot
output_set(acc_cond::mark_t a) const
{
if (!opt_all_bullets)
os_ << '{';
{
if (opt_latex_)
os_ << "\\{";
else
os_ << '{';
}
const char* space = "";
for (auto v: a.sets())
{
@ -316,7 +346,12 @@ namespace spot
space = ",";
}
if (!opt_all_bullets)
os_ << '}';
{
if (opt_latex_)
os_ << "\\}";
else
os_ << '}';
}
}
const char*
@ -369,8 +404,30 @@ namespace spot
os_ << '}';
}
std::string
state_label(unsigned s) const
std::ostream&
escape_for_output(std::ostream& os, const std::string& s) const
{
if (opt_html_labels_)
return escape_html(os, s);
if (opt_latex_)
return escape_latex(os, s);
return escape_str(os, s);
}
std::ostream&
format_label(std::ostream& os, bdd label) const
{
formula f = bdd_to_formula(label, aut_->get_dict());
if (opt_latex_)
{
print_sclatex_psl(os << '$', f) << '$';
return os;
}
return escape_for_output(os, str_psl(f));
}
std::ostream&
format_state_label(std::ostream& os, unsigned s) const
{
bdd label = bddfalse;
for (auto& t: aut_->out(s))
@ -380,8 +437,8 @@ namespace spot
}
if (label == bddfalse
&& incomplete_ && incomplete_->find(s) != incomplete_->end())
return "...";
return bdd_format_formula(aut_->get_dict(), label);
return os << "...";
return format_label(os, label);
}
std::string string_dst(int dst, int color_num = -1)
@ -420,26 +477,27 @@ namespace spot
}
}
void print_acceptance_for_human()
std::string get_acceptance_for_human()
{
const char* nl = opt_html_labels_ ? "<br/>" : "\\n";
std::ostringstream os;
if (aut_->acc().is_generalized_buchi())
{
if (aut_->acc().is_all())
os_ << nl << "[all]";
os << "all";
else if (aut_->acc().is_buchi())
os_ << nl << "[Büchi]";
os << "Büchi";
else
os_ << nl << "[gen. Büchi " << aut_->num_sets() << ']';
os << "gen. Büchi " << aut_->num_sets();
}
else if (aut_->acc().is_generalized_co_buchi())
{
if (aut_->acc().is_none())
os_ << nl << "[none]";
os << "none";
else if (aut_->acc().is_co_buchi())
os_ << nl << "[co-Büchi]";
os << "co-Büchi";
else
os_ << nl << "[gen. co-Büchi " << aut_->num_sets() << ']';
os << "gen. co-Büchi " << aut_->num_sets();
}
else
{
@ -447,7 +505,7 @@ namespace spot
assert(r != 0);
if (r > 0)
{
os_ << nl << "[Rabin " << r << ']';
os << "Rabin " << r;
}
else
{
@ -455,14 +513,14 @@ namespace spot
assert(r != 0);
if (r > 0)
{
os_ << nl << "[Streett " << r << ']';
os << "Streett " << r;
}
else
{
std::vector<unsigned> pairs;
if (aut_->acc().is_generalized_rabin(pairs))
{
os_ << nl << "[gen. Rabin " << pairs.size() << ']';
os << "gen. Rabin " << pairs.size();
}
else
{
@ -470,10 +528,10 @@ namespace spot
bool odd = false;
if (aut_->acc().is_parity(max, odd))
{
os_ << nl << "[parity "
<< (max ? "max " : "min ")
<< (odd ? "odd " : "even ")
<< aut_->num_sets() << ']';
os << "parity "
<< (max ? "max " : "min ")
<< (odd ? "odd " : "even ")
<< aut_->num_sets();
}
else
{
@ -484,14 +542,23 @@ namespace spot
bool s_like = aut_->acc().is_streett_like(s_pairs);
unsigned ssz = s_pairs.size();
if (r_like && (!s_like || (rsz <= ssz)))
os_ << nl << "[Rabin-like " << rsz << ']';
else if (s_like && (!r_like || (ssz < rsz)))
os_ << nl << "[Streett-like " << ssz << ']';
os << "Rabin-like " << rsz;
else if (s_like && (!r_like || (ssz < rsz)))
os << "Streett-like " << ssz;
}
}
}
}
}
return os.str();
}
void print_acceptance_for_human()
{
std::string accstr = get_acceptance_for_human();
if (accstr.empty())
return;
os_ << nl_ << '[' << accstr << ']';
}
void
@ -503,56 +570,39 @@ namespace spot
if (opt_bullet && aut_->num_sets() <= MAX_BULLET)
opt_all_bullets = true;
os_ << "digraph G {\n";
if (opt_latex_)
os_ << " d2tgraphstyle=\"every node/.style={align=center}\"\n";
if (!opt_vertical_)
os_ << " rankdir=LR\n";
if (name_ || opt_show_acc_)
{
if (!opt_html_labels_)
os_ << " " << label_pre_;
if (name_)
escape_for_output(os_, *name_);
if (opt_show_acc_)
{
os_ << " label=\"";
if (name_)
if (!dcircles_)
{
escape_str(os_, *name_);
if (opt_show_acc_)
os_ << "\\n";
if (name_)
os_ << nl_;
auto& acc = aut_->get_acceptance();
if (opt_html_labels_)
acc.to_html(os_, [this](std::ostream& os, int v)
{
this->output_html_set_aux(os, v);
});
else if (opt_latex_)
acc.to_latex(os_ << '$') << '$';
else
acc.to_text(os_, [this](std::ostream& os, int v)
{
this->output_set(os, v);
});
}
if (opt_show_acc_)
{
if (!dcircles_)
{
aut_->get_acceptance().to_text
(os_, [this](std::ostream& os, int v)
{
this->output_set(os, v);
});
}
print_acceptance_for_human();
}
os_ << "\"\n";
}
else
{
os_ << " label=<";
if (name_)
{
escape_html(os_, *name_);
if (opt_show_acc_)
os_ << "<br/>";
}
if (opt_show_acc_)
{
if (!dcircles_)
{
aut_->get_acceptance().to_html
(os_, [this](std::ostream& os, int v)
{
this->output_html_set_aux(os, v);
});
}
print_acceptance_for_human();
}
os_ << ">\n";
print_acceptance_for_human();
}
os_ << label_post_ << '\n';
os_ << " labelloc=\"t\"\n";
}
switch (opt_shape_)
@ -607,6 +657,16 @@ namespace spot
void
process_state(unsigned s)
{
os_ << " " << s << " [" << label_pre_;
if (sn_ && s < sn_->size() && !(*sn_)[s].empty())
escape_for_output(os_, (*sn_)[s]);
else if (sprod_)
os_ << (*sprod_)[s].first << ',' << (*sprod_)[s].second;
else
os_ << s;
if (orig_ && s < orig_->size())
os_ << " (" << (*orig_)[s] << ')';
if (mark_states_ && !dcircles_)
{
acc_cond::mark_t acc = 0U;
@ -616,64 +676,23 @@ namespace spot
break;
}
bool has_name = sn_ && s < sn_->size() && !(*sn_)[s].empty();
os_ << " " << s << " [label=";
if (!opt_html_labels_)
if (acc)
{
os_ << '"';
if (has_name)
escape_str(os_, (*sn_)[s]);
else if (sprod_)
os_ << (*sprod_)[s].first << ',' << (*sprod_)[s].second;
os_ << nl_;
if (opt_html_labels_)
output_html_set(acc);
else
os_ << s;
if (orig_ && s < orig_->size())
os_ << " (" << (*orig_)[s] << ')';
if (acc)
{
os_ << "\\n";
output_set(acc);
}
if (opt_state_labels_)
escape_str(os_ << "\\n", state_label(s));
os_ << '"';
}
else
{
os_ << '<';
if (has_name)
escape_html(os_, (*sn_)[s]);
else if (sprod_)
os_ << (*sprod_)[s].first << ',' << (*sprod_)[s].second;
else
os_ << s;
if (orig_ && s < orig_->size())
os_ << " (" << (*orig_)[s] << ')';
if (acc)
{
os_ << "<br/>";
output_html_set(acc);
}
if (opt_state_labels_)
escape_html(os_ << "<br/>", state_label(s));
os_ << '>';
output_set(acc);
}
if (opt_state_labels_)
format_state_label(os_ << nl_, s);
os_ << label_post_;
}
else
{
os_ << " " << s << " [label=\"";
if (sn_ && s < sn_->size() && !(*sn_)[s].empty())
escape_str(os_, (*sn_)[s]);
else if (sprod_)
os_ << (*sprod_)[s].first << ',' << (*sprod_)[s].second;
else
os_ << s;
if (orig_ && s < orig_->size())
os_ << " (" << (*orig_)[s] << ')';
if (opt_state_labels_)
escape_str(os_ << "\\n", state_label(s));
os_ << '"';
format_state_label(os_ << nl_, s);
os_ << label_post_;
// Use state_acc_sets(), not state_is_accepting() because
// on co-Büchi automata we want to mark the rejecting
// states.
@ -713,35 +732,20 @@ namespace spot
if (iter != highlight_edges_->end())
os_ << '.' << iter->second % palette_mod;
}
std::string label;
os_ << " [" << label_pre_;
if (!opt_state_labels_)
label = bdd_format_formula(aut_->get_dict(), t.cond);
if (!opt_html_labels_)
{
os_ << " [label=\"";
escape_str(os_, label);
if (!mark_states_)
if (auto a = t.acc)
{
if (!opt_state_labels_)
os_ << "\\n";
output_set(a);
}
os_ << '"';
}
else
{
os_ << " [label=<";
escape_html(os_, label);
if (!mark_states_)
if (auto a = t.acc)
{
if (!opt_state_labels_)
os_ << "<br/>";
output_html_set(a);
}
os_ << '>';
}
format_label(os_, t.cond);
if (!mark_states_)
if (auto a = t.acc)
{
if (!opt_state_labels_)
os_ << nl_;
if (opt_html_labels_)
output_html_set(a);
else
output_set(a);
}
os_ << label_post_;
if (opt_ordered_edges_ || opt_numbered_edges_)
{
os_ << ", taillabel=\"";
@ -886,7 +890,7 @@ namespace spot
{
// Reset the label, otherwise the graph label would
// be inherited by the cluster.
os_ << " label=\"\"\n";
os_ << (opt_latex_ ? " texlbl=\"\"\n" : " label=\"\"\n");
}
for (auto s: si->states_of(i))
{