diff --git a/NEWS b/NEWS index 920313354..20fa6254b 100644 --- a/NEWS +++ b/NEWS @@ -69,6 +69,10 @@ New in spot 1.1.4a (not relased) default. See the spot-x(7) man page for documentation about the related options: sat-minimize, sat-states, sat-acc, state-based. + - ltlfilt, genltl, and randltl now have a --latex option to output + formulas in a way that its easier to embed in a LaTeX document. + Each operator is output as a command such as \U, \F, etc. + doc/tl/spotltl.sty gives one possible definition for each macro. * New functions and classes in the library: @@ -131,6 +135,8 @@ New in spot 1.1.4a (not relased) a couple of specializations for classes where is can be know without exploration. + - to_latex_string(): Output a formula using LaTeX syntax. + * Noteworthy internal changes: - When minimize_obligation() is not given the formula associated diff --git a/doc/tl/Makefile.am b/doc/tl/Makefile.am index 7eb2590eb..854f88bee 100644 --- a/doc/tl/Makefile.am +++ b/doc/tl/Makefile.am @@ -28,7 +28,7 @@ LATEXMK = BIBINPUTS='$(srcdir)' latexmk -pdf -ps- -dvi- -pvc- \ -e '$$pdflatex="pdflatex %O \"\\def\\SpotVersion{$(VERSION)}\\input{%S}\""' dist_pdf_DATA = $(srcdir)/tl.pdf -EXTRA_DIST = tl.tex tl.bib +EXTRA_DIST = tl.tex tl.bib spotltl.sty # latexmk produces its output in the current directory, and may not # update its timestamp when no rebuild was necessary. This can cause diff --git a/doc/tl/spotltl.sty b/doc/tl/spotltl.sty new file mode 100644 index 000000000..ead5536db --- /dev/null +++ b/doc/tl/spotltl.sty @@ -0,0 +1,49 @@ +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{spotltl}[2013/08/28 Temporal Logic Operators for LTL and PSL] +% NOTE: if you use amsmath.sty, make sure you include it before spotltl.sty +% otherwise it will try to redefine some symbols from txfonts. +\RequirePackage{txfonts} + +\newcommand{\ttrue}{\top} +\newcommand{\ffalse}{\bot} +\newcommand{\eword}{\varepsilon} % empty word, denoted by [*0] in PSL + +% These three are not declared as operator +\newcommand{\F}{\mathsf{F}} % eventually +\newcommand{\G}{\mathsf{G}} % always +\newcommand{\X}{\mathsf{X}} % next +% The \mathbin tell TeX to adjust spacing for binary operators +\newcommand{\M}{\mathbin{\mathsf{M}}} % strong release +\newcommand{\R}{\mathbin{\mathsf{R}}} % release +\newcommand{\U}{\mathbin{\mathsf{U}}} % until +\newcommand{\W}{\mathbin{\mathsf{W}}} % weak until + +% additional logical operators to complete \land, \lor, and \lnot. +\newcommand{\limplies}{\rightarrow} +\newcommand{\liff}{\leftrightarrow} +\newcommand{\lxor}{\oplus} + +% Star-like PSL operators +\newcommand{\SereStar}[1]{^{\star#1}} +\newcommand{\SereEqual}[1]{^{=#1}} +\newcommand{\SereGoto}[1]{^{\to#1}} +\newcommand{\SerePlus}{^+} +\newcommand{\SereFusion}{\mathbin{\mathsf{:}}} +\newcommand{\SereConcat}{\mathbin{\mathsf{;}}} +\newcommand{\SereOr}{\cup} +\newcommand{\SereAnd}{\cap} +\newcommand{\SereAndNLM}{\mathbin{\mathsf{\&}}} + +% Triggers and Seq operators, with their variant. The *M variants of +% these ope are internal "Marked" operators for Spot that should +% normally not be output unless the formula is used to label a state +% in an automaton (but we do not use LaTeX there). + +\newcommand{\seq}{\Diamondright} +%\newcommand{\seqM}{\seq} +\newcommand{\seqX}{\DiamondRight} +%\newcommand{\seqXM}{\seqX} +\newcommand{\triggers}{\boxright} +\newcommand{\triggersX}{\boxRight} + + diff --git a/src/bin/common_output.cc b/src/bin/common_output.cc index c967361bb..8fb84f89c 100644 --- a/src/bin/common_output.cc +++ b/src/bin/common_output.cc @@ -1,6 +1,6 @@ // -*- coding: utf-8 -*- -// Copyright (C) 2012 Laboratoire de Recherche et Développement de -// l'Epita (LRDE). +// Copyright (C) 2012, 2013 Laboratoire de Recherche et Développement +// de l'Epita (LRDE). // // This file is part of Spot, a model checking library. // @@ -27,6 +27,7 @@ #define OPT_SPOT 1 #define OPT_WRING 2 +#define OPT_LATEX 3 output_format_t output_format = spot_output; bool full_parenth = false; @@ -40,6 +41,7 @@ static const argp_option options[] = { "lbt", 'l', 0, 0, "output in LBT's syntax", -20 }, { "wring", OPT_WRING, 0, 0, "output in Wring's syntax", -20 }, { "utf8", '8', 0, 0, "output using UTF-8 characters", -20 }, + { "latex", OPT_LATEX, 0, 0, "output using LaTeX macros", -20 }, { 0, 0, 0, 0, 0, 0 } }; @@ -63,6 +65,9 @@ parse_opt_output(int key, char*, struct argp_state*) case 's': output_format = spin_output; break; + case OPT_LATEX: + output_format = latex_output; + break; case OPT_SPOT: output_format = spot_output; break; @@ -119,6 +124,9 @@ output_formula(const spot::ltl::formula* f, const char* filename, int linenum) case utf8_output: spot::ltl::to_utf8_string(f, std::cout, full_parenth); break; + case latex_output: + spot::ltl::to_latex_string(f, std::cout, full_parenth); + break; } // Make sure we abort if we can't write to std::cout anymore // (like disk full or broken pipe with SIGPIPE ignored). diff --git a/src/bin/common_output.hh b/src/bin/common_output.hh index 78af51a69..cc27027ca 100644 --- a/src/bin/common_output.hh +++ b/src/bin/common_output.hh @@ -1,6 +1,6 @@ // -*- coding: utf-8 -*- -// Copyright (C) 2012 Laboratoire de Recherche et Développement de -// l'Epita (LRDE). +// Copyright (C) 2012, 2013 Laboratoire de Recherche et Développement +// de l'Epita (LRDE). // // This file is part of Spot, a model checking library. // @@ -26,7 +26,7 @@ #include "ltlast/formula.hh" enum output_format_t { spot_output, spin_output, utf8_output, - lbt_output, wring_output }; + lbt_output, wring_output, latex_output }; extern output_format_t output_format; extern bool full_parenth; diff --git a/src/ltltest/Makefile.am b/src/ltltest/Makefile.am index f034d4f83..fdeafe168 100644 --- a/src/ltltest/Makefile.am +++ b/src/ltltest/Makefile.am @@ -95,6 +95,7 @@ TESTS = \ kind.test \ remove_x.test \ ltlfilt.test \ + latex.test \ lbt.test \ lenient.test \ isop.test \ diff --git a/src/ltltest/latex.test b/src/ltltest/latex.test new file mode 100755 index 000000000..8a8954216 --- /dev/null +++ b/src/ltltest/latex.test @@ -0,0 +1,62 @@ +#!/bin/sh +# -*- coding: utf-8 -*- +# Copyright (C) 2013 Laboratoire de Recherche et Développement +# de l'Epita (LRDE). +# +# This file is part of Spot, a model checking library. +# +# Spot is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Spot is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +# License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +. ./defs +set -e + +# Skip this test if we don't find a sufficiently complete LaTeX +# installation + +(latexmk --version) || exit 77 +(pdflatex --version) || exit 77 +(kpsewhich txfonts.sty) || exit 77 +(kpsewhich amsmath.sty) || exit 77 + +cat >input < FGX(!b & !c) | (b ^ a) +a U b W c R (d & e) M f +{a;b[=2];((c:d*) && f*);e[*2..]}<>-> {((a | [*0])*;b[+]) & (c1[->2])}[]-> h +{a;b;c} []=> {d*;e} <>=> !f +!{a;b*;c}! -> d +G(uglyname->Fuglierlongname42) +EOF + +( +cat <<\EOF +\documentclass{article} +\usepackage{amsmath} +\usepackage{spotltl} +\begin{document} +\begin{align} +EOF +( ../../bin/ltlfilt --latex input; + ../../bin/genltl --go-theta=1..3 --latex ) | sed 's/$/\\\\/' +cat <<\EOF +\text{done} +\end{align} +\end{document} +EOF +) > output.tex + +TEXINPUTS=$srcdir/../../../doc/tl: latexmk -f -silent --pdf output.tex + +# latexmk will have a non-zero exit status on failure, so we will +# detect that. However the file output.pdf really ought to be +# controled by eye... diff --git a/src/ltlvisit/tostring.cc b/src/ltlvisit/tostring.cc index 5992ae35e..f0da9c113 100644 --- a/src/ltlvisit/tostring.cc +++ b/src/ltlvisit/tostring.cc @@ -49,12 +49,12 @@ namespace spot KR, KW, KM, - KEConcat, - KEConcatNext, - KEConcatMarked, - KEConcatMarkedNext, - KUConcat, - KUConcatNext, + KSeq, + KSeqNext, + KSeqMarked, + KSeqMarkedNext, + KTriggers, + KTriggersNext, KNot, KX, KF, @@ -66,6 +66,13 @@ namespace spot KAndNLM, KConcat, KFusion, + KOpenSERE, + KCloseSERE, + KCloseBunop, + KStarBunop, + KPlusBunop, + KEqualBunop, + KGotoBunop, }; const char* spot_kw[] = { @@ -96,6 +103,13 @@ namespace spot " & ", ";", ":", + "{", + "}", + "]", + "[*", + "[+]", + "[=", + "[->", }; const char* spin_kw[] = { @@ -126,6 +140,13 @@ namespace spot " & ", // not supported ";", // not supported ":", // not supported + "{", // not supported + "}", // not supported + "]", // not supported + "[*", // not supported + "[+]", // not supported + "[=", // not supported + "[->", // not supported }; const char* wring_kw[] = { @@ -156,6 +177,13 @@ namespace spot " & ", // not supported ";", // not supported ":", // not supported + "{", // not supported + "}", // not supported + "]", // not supported + "[*", // not supported + "[+]", // not supported + "[=", // not supported + "[->", // not supported }; const char* utf8_kw[] = { @@ -186,6 +214,50 @@ namespace spot " & ", ";", ":", + "{", + "}", + "]", + "[*", + "[+]", + "[=", + "[->", + }; + + const char* latex_kw[] = { + "\\ffalse", + "\\ttrue", + "\\eword", + " \\lxor ", + " \\limplies ", + " \\liff ", + " \\U ", + " \\R ", + " \\W ", + " \\M ", + "\\seq ", + "\\seqX ", + "\\seqM ", + "\\seqXM ", + "\\triggers ", + "\\triggersX ", + "\\lnot ", + "\\X ", + "\\F ", + "\\G ", + " \\lor ", + " \\SereOr ", + " \\land ", + " \\SereAnd ", + " \\SereAndNLM ", + " \\SereConcat ", + " \\SereFusion ", + "\\{", + "\\}", + "}", + "\\SereStar{", + "\\SerePlus{}", + "\\SereEqual{", + "\\SereGoto{", }; static bool @@ -258,17 +330,23 @@ namespace spot void openp() const { - os_ << (in_ratexp_ ? "{" : "("); + if (in_ratexp_) + emit(KOpenSERE); + else + os_ << "("; } void closep() const { - os_ << (in_ratexp_ ? "}" : ")"); + if (in_ratexp_) + emit(KCloseSERE); + else + os_ << ")"; } std::ostream& - emit(int symbol) + emit(int symbol) const { return os_ << kw_[symbol]; } @@ -292,7 +370,28 @@ namespace spot } else { - os_ << str; + if (kw_ == latex_kw) + { + size_t s = str.size(); + while (str[s - 1] >= '0' && str[s - 1] <= '9') + { + --s; + assert(s != 0); // bare words cannot start with letters + } + if (s > 1) + os_ << "\\mathit{"; + os_ << str.substr(0, s); + if (s > 1) + os_ << "}"; + if (s != str.size()) + os_ << "_{" + << str.substr(s) + << "}"; + } + else + { + os_ << str; + } } if (kw_ == wring_kw) os_ << "=1"; @@ -336,8 +435,8 @@ namespace spot case binop::UConcat: case binop::EConcat: case binop::EConcatMarked: - os_ << "{"; in_ratexp_ = true; + openp(); top_level_ = true; { const multop* m = is_multop(bo->first(), multop::Concat); @@ -384,26 +483,26 @@ namespace spot emit(KM); break; case binop::UConcat: - os_ << "}"; - emit(onelast ? KUConcatNext : KUConcat); + closep(); + emit(onelast ? KTriggersNext : KTriggers); in_ratexp_ = false; - top_level_ = top_level; + top_level_ = false; break; case binop::EConcat: + emit(KCloseSERE); if (bo->second() == constant::true_instance()) { - os_ << "}!"; + os_ << "!"; in_ratexp_ = false; goto second_done; } - os_ << "}"; - emit(onelast ? KEConcatNext : KEConcat); + emit(onelast ? KSeqNext : KSeq); in_ratexp_ = false; top_level_ = false; break; case binop::EConcatMarked: os_ << "}"; - emit(onelast ? KEConcatMarkedNext : KEConcatMarked); + emit(onelast ? KSeqMarkedNext : KSeqMarked); in_ratexp_ = false; top_level_ = false; break; @@ -471,16 +570,16 @@ namespace spot case Star: if (min == 1 && max == bunop::unbounded) { - os_ << "[+]"; + emit(KPlusBunop); return; } - os_ << "[*"; + emit(KStarBunop); break; case Equal: - os_ << "[="; + emit(KEqualBunop); break; case Goto: - os_ << "[->"; + emit(KGotoBunop); default_min = 1; default_max = 1; break; @@ -517,7 +616,7 @@ namespace spot os_ << max; } } - os_ << "]"; + emit(KCloseBunop); } void @@ -667,12 +766,15 @@ namespace spot // in which case it's b[=1]. if (i + 2 < max && mo->nth(i) == mo->nth(i + 2)) { - os_ << "[=1]"; + emit(KEqualBunop); + os_ << "1"; + emit(KCloseBunop); i += 2; } else { - os_ << "[->]"; + emit(KGotoBunop); + emit(KCloseBunop); ++i; } continue; @@ -685,7 +787,7 @@ namespace spot if (b1 == b2) { emit_bunop_child(b1); - os_ << "[="; + emit(KEqualBunop); unsigned min = s->min(); os_ << min; unsigned max = s->max(); @@ -695,7 +797,7 @@ namespace spot if (max != bunop::unbounded) os_ << max; } - os_ << "]"; + emit(KCloseBunop); ++i; continue; } @@ -847,5 +949,24 @@ namespace spot return os.str(); } + std::ostream& + to_latex_string(const formula* f, std::ostream& os, + bool full_parent, bool ratexp) + { + to_string_visitor v(os, full_parent, ratexp, latex_kw); + f->accept(v); + return os; + } + + std::string + to_latex_string(const formula* f, + bool full_parent, bool ratexp) + { + std::ostringstream os; + to_latex_string(f, os, full_parent, ratexp); + return os.str(); + } + + } } diff --git a/src/ltlvisit/tostring.hh b/src/ltlvisit/tostring.hh index db35b89af..baab4a7fb 100644 --- a/src/ltlvisit/tostring.hh +++ b/src/ltlvisit/tostring.hh @@ -101,6 +101,27 @@ namespace spot SPOT_API std::string to_wring_string(const formula* f); + /// \brief Output a formula as an LaTeX string which is parsable unless + /// the formula contains automaton operators (used in ELTL formulae). + /// \param f The formula to translate. + /// \param os The stream where it should be output. + /// \param full_parent Whether or not the string should by fully + /// parenthesized. + /// \param ratexp Whether we are printing a SERE. + SPOT_API std::ostream& + to_latex_string(const formula* f, std::ostream& os, + bool full_parent = false, bool ratexp = false); + + /// \brief Output a formula as a LaTeX string which is parsable + /// unless the formula contains automaton operators (used in ELTL formulae). + /// \param f The formula to translate. + /// \param full_parent Whether or not the string should by fully + /// parenthesized. + /// \param ratexp Whether we are printing a SERE. + SPOT_API std::string + to_latex_string(const formula* f, + bool full_parent = false, bool ratexp = false); + /// @} } }