// -*- coding: utf-8 -*- // Copyright (C) 2008, 2010, 2012, 2013, 2014, 2015 Laboratoire de // Recherche et Développement de l'Epita (LRDE) // Copyright (C) 2003, 2004 Laboratoire d'Informatique de Paris 6 (LIP6), // département Systèmes Répartis Coopératifs (SRC), Université Pierre // et Marie Curie. // // 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 . #include #include #include #include #include #include "ltlast/allnodes.hh" #include "ltlast/visitor.hh" #include "lunabbrev.hh" #include "wmunabbrev.hh" #include "tostring.hh" namespace spot { namespace ltl { namespace { enum keyword { KFalse = 0, KTrue = 1, KEmptyWord = 2, KXor, KImplies, KEquiv, KU, KR, KW, KM, KSeq, KSeqNext, KSeqMarked, KSeqMarkedNext, KTriggers, KTriggersNext, KNot, KX, KF, KG, KOr, KOrRat, KAnd, KAndRat, KAndNLM, KConcat, KFusion, KOpenSERE, KCloseSERE, KCloseBunop, KStarBunop, KPlusBunop, KFStarBunop, KFPlusBunop, KEqualBunop, KGotoBunop, }; const char* spot_kw[] = { "0", "1", "[*0]", " xor ", " -> ", " <-> ", " U ", " R ", " W ", " M ", "<>-> ", "<>=> ", "<>+> ", "<>=+> ", "[]-> ", "[]=> ", "!", "X", "F", "G", " | ", " | ", " & ", " && ", " & ", ";", ":", "{", "}", "]", "[*", "[+]", "[:*", "[:+]", "[=", "[->", }; const char* spin_kw[] = { "false", // 0 doesn't work from the command line "true", // 1 doesn't work from the command line "[*0]", // not supported " xor ", // rewritten " -> ", // rewritten, although supported " <-> ", // rewritten, although supported " U ", " V ", " W ", // rewritten " M ", // rewritten "<>-> ", // not supported "<>=> ", // not supported "<>+> ", // not supported "<>=+> ", // not supported "[]-> ", // not supported "[]=> ", // not supported "!", "X", "<>", "[]", " || ", " || ", " && ", " && ", // not supported " & ", // not supported ";", // not supported ":", // not supported "{", // not supported "}", // not supported "]", // not supported "[*", // not supported "[+]", // not supported "[:*", // not supported "[:+]", // not supported "[=", // not supported "[->", // not supported }; const char* wring_kw[] = { "FALSE", "TRUE", "[*0]", // not supported " ^ ", " -> ", " <-> ", " U ", " R ", " W ", // rewritten " M ", // rewritten "<>-> ", // not supported "<>=> ", // not supported "<>+> ", // not supported "<>=+> ", // not supported "[]-> ", // not supported "[]=> ", // not supported "!", "X", "F", "G", " + ", " | ", // not supported " * ", " && ", // not supported " & ", // not supported ";", // not supported ":", // not supported "{", // not supported "}", // not supported "]", // not supported "[*", // not supported "[+]", // not supported "[:*", // not supported "[:+]", // not supported "[=", // not supported "[->", // not supported }; const char* utf8_kw[] = { "0", "1", "[*0]", "⊕", " → ", " ↔ ", " U ", " R ", " W ", " M ", "◇→ ", "◇⇒ ", "◇→̃ ", "◇⇒̃ ", "□→ ", "□⇒ ", "¬", "○", "◇", "□", "∨", " | ", "∧", " ∩ ", " & ", ";", ":", "{", "}", "]", "[*", "[+]", "[:*", "[:+]", "[=", "[->", }; 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{}", "\\SereFStar{", "\\SereFPlus{}", "\\SereEqual{", "\\SereGoto{", }; const char* sclatex_kw[] = { "\\bot", "\\top", "\\varepsilon", " \\oplus ", " \\rightarrow ", " \\leftrightarrow ", " \\mathbin{\\mathsf{U}} ", " \\mathbin{\\mathsf{R}} ", " \\mathbin{\\mathsf{W}} ", " \\mathbin{\\mathsf{M}} ", ("\\mathrel{\\Diamond\\kern-1.7pt\\raise.4pt" "\\hbox{$\\mathord{\\rightarrow}$}} "), ("\\mathrel{\\Diamond\\kern-1.7pt\\raise.4pt" "\\hbox{$\\mathord{\\Rightarrow}$}} "), "\\seqM ", "\\seqXM ", ("\\mathrel{\\Box\\kern-1.7pt\\raise.4pt" "\\hbox{$\\mathord{\\rightarrow}$}} "), ("\\mathrel{\\Box\\kern-1.7pt\\raise.4pt" "\\hbox{$\\mathord{\\Rightarrow}$}} "), "\\lnot ", "\\mathsf{X} ", "\\mathsf{F} ", "\\mathsf{G} ", " \\lor ", " \\cup ", " \\land ", " \\cap ", " \\mathbin{\\mathsf{\\&}} ", " \\mathbin{\\mathsf{;}} ", " \\mathbin{\\mathsf{:}} ", "\\{", "\\}", "}", "^{\\star", "^+", "^{\\mathsf{:}\\star", "^{\\mathsf{:}+}", "^{=", "^{\\to", }; static bool is_bare_word(const char* str) { // Bare words cannot be empty, start with the letter of a // unary operator, or be the name of an existing constant or // operator. Also they should start with an letter. if (!*str || *str == 'F' || *str == 'G' || *str == 'X' || !(isalpha(*str) || *str == '_' || *str == '.') || ((*str == 'U' || *str == 'W' || *str == 'M' || *str == 'R') && str[1] == 0) || !strcasecmp(str, "true") || !strcasecmp(str, "false")) return false; // The remaining of the word must be alphanumeric. while (*++str) if (!(isalnum(*str) || *str == '_' || *str == '.')) return false; return true; } // If the formula has the form (!b)[*], return b. static const formula* strip_star_not(const formula* f) { if (const bunop* s = is_Star(f)) if (const unop* n = is_Not(s->child())) return n->child(); return 0; } // If the formula as position i in multop mo has the form // (!b)[*];b with b being a Boolean formula, return b. static const formula* match_goto(const multop *mo, unsigned i) { assert(i + 1 < mo->size()); const formula* b = strip_star_not(mo->nth(i)); if (!b || !b->is_boolean()) return 0; if (mo->nth(i + 1) == b) return b; return 0; } class to_string_visitor: public visitor { public: to_string_visitor(std::ostream& os, bool full_parent = false, bool ratexp = false, const char** kw = spot_kw) : os_(os), top_level_(true), full_parent_(full_parent), in_ratexp_(ratexp), kw_(kw) { } virtual ~to_string_visitor() { } void openp() const { if (in_ratexp_) emit(KOpenSERE); else os_ << '('; } void closep() const { if (in_ratexp_) emit(KCloseSERE); else os_ << ')'; } std::ostream& emit(int symbol) const { return os_ << kw_[symbol]; } void visit(const atomic_prop* ap) { std::string str = ap->name(); if (full_parent_) os_ << '('; if (!is_bare_word(str.c_str())) { // Spin 6 supports atomic propositions such as (a == 0) // as long as they are enclosed in parentheses. if (kw_ != spin_kw) os_ << '"' << str << '"'; else if (!full_parent_) os_ << '(' << str << ')'; else os_ << str; } else { if (kw_ == latex_kw || kw_ == sclatex_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"; if (full_parent_) os_ << ')'; } void visit(const constant* c) { if (full_parent_) openp(); switch (c->val()) { case constant::False: emit(KFalse); break; case constant::True: emit(KTrue); break; case constant::EmptyWord: emit(KEmptyWord); break; } if (full_parent_) closep(); } void visit(const binop* bo) { bool top_level = top_level_; top_level_ = false; if (!top_level) openp(); bool onelast = false; switch (bo->op()) { case binop::UConcat: case binop::EConcat: case binop::EConcatMarked: in_ratexp_ = true; openp(); top_level_ = true; { const multop* m = is_multop(bo->first(), multop::Concat); if (m) { unsigned s = m->size(); if (m->nth(s - 1) == constant::true_instance()) { const formula* tmp = m->all_but(s - 1); tmp->accept(*this); tmp->destroy(); onelast = true; break; } } } // fall through default: bo->first()->accept(*this); break; } switch (bo->op()) { case binop::Xor: emit(KXor); break; case binop::Implies: emit(KImplies); break; case binop::Equiv: emit(KEquiv); break; case binop::U: emit(KU); break; case binop::R: emit(KR); break; case binop::W: emit(KW); break; case binop::M: emit(KM); break; case binop::UConcat: closep(); emit(onelast ? KTriggersNext : KTriggers); in_ratexp_ = false; top_level_ = false; break; case binop::EConcat: emit(KCloseSERE); if (bo->second() == constant::true_instance()) { os_ << '!'; in_ratexp_ = false; goto second_done; } emit(onelast ? KSeqNext : KSeq); in_ratexp_ = false; top_level_ = false; break; case binop::EConcatMarked: os_ << '}'; emit(onelast ? KSeqMarkedNext : KSeqMarked); in_ratexp_ = false; top_level_ = false; break; } bo->second()->accept(*this); second_done: if (!top_level) closep(); } void emit_bunop_child(const formula* b) { // b[*] is OK, no need to print {b}[*]. However want braces // for {!b}[*], the only unary operator that can be nested // with [*] or any other BUnOp like [->i..j] or [=i..j]. formula::opkind ck = b->kind(); bool need_parent = (full_parent_ || ck == formula::UnOp || ck == formula::BinOp || ck == formula::MultOp); if (need_parent) openp(); b->accept(*this); if (need_parent) closep(); } void visit(const bunop* bo) { const formula* c = bo->child(); enum { Star, FStar, Goto } sugar = Star; unsigned default_min = 0; unsigned default_max = bunop::unbounded; bunop::type op = bo->op(); // Abbreviate "1[*]" as "[*]". if (c != constant::true_instance() || op != bunop::Star) { switch (op) { case bunop::Star: // Is this a Goto? if (const multop* mo = is_Concat(c)) { unsigned s = mo->size(); if (s == 2) if (const formula* b = match_goto(mo, 0)) { c = b; sugar = Goto; } } break; case bunop::FStar: sugar = FStar; break; } emit_bunop_child(c); } unsigned min = bo->min(); unsigned max = bo->max(); switch (sugar) { case Star: if (min == 1 && max == bunop::unbounded) { emit(KPlusBunop); return; } emit(KStarBunop); break; case FStar: if (min == 1 && max == bunop::unbounded) { emit(KFPlusBunop); return; } emit(KFStarBunop); break; case Goto: emit(KGotoBunop); default_min = 1; default_max = 1; break; } // Beware that the default parameters of the Goto operator are // not the same as Star or Equal: // // [->] = [->1..1] // [->..] = [->1..unbounded] // [*] = [*0..unbounded] // [*..] = [*0..unbounded] // [=] = [=0..unbounded] // [=..] = [=0..unbounded] // // Strictly speaking [=] is not specified by PSL, and anyway we // automatically rewrite Exp[=0..unbounded] as // Exp[*0..unbounded], so we should never have to print [=] // here. // // Also // [*..] = [*0..unbounded] if (min != default_min || max != default_max) { // Always print the min_, even when it is equal to // default_min, this way we avoid ambiguities (like // when reading [*..3] near [->..2]) os_ << min; if (min != max) { os_ << ".."; if (max != bunop::unbounded) os_ << max; } } emit(KCloseBunop); } void visit(const unop* uo) { top_level_ = false; // The parser treats F0, F1, G0, G1, X0, and X1 as atomic // propositions. So make sure we output F(0), G(1), etc. bool need_parent = (uo->child()->kind() == formula::Constant); bool top_level = top_level_; bool overline = false; if (full_parent_) { need_parent = false; // These will be printed by each subformula if (!top_level) openp(); } switch (uo->op()) { case unop::Not: need_parent = false; // If we negate a single letter in UTF-8, use a // combining overline. if (!full_parent_ && kw_ == utf8_kw) if (const ltl::atomic_prop* ap = is_atomic_prop(uo->child())) if (ap->name().size() == 1 && is_bare_word(ap->name().c_str())) { overline = true; break; } // If we negate an atomic proposition for Wring, // output prop=0. if (kw_ == wring_kw) if (const ltl::atomic_prop* ap = is_atomic_prop(uo->child())) if (is_bare_word(ap->name().c_str())) { os_ << ap->name() << "=0"; goto skiprec; } emit(KNot); break; case unop::X: emit(KX); break; case unop::F: emit(KF); break; case unop::G: emit(KG); break; case unop::Closure: os_ << '{'; in_ratexp_ = true; top_level_ = true; break; case unop::NegClosure: emit(KNot); os_ << '{'; in_ratexp_ = true; top_level_ = true; break; case unop::NegClosureMarked: emit(KNot); os_ << (kw_ == utf8_kw ? "̃{": "+{"); in_ratexp_ = true; top_level_ = true; break; } if (need_parent) openp(); uo->child()->accept(*this); if (need_parent) closep(); switch (uo->op()) { case unop::Closure: case unop::NegClosure: os_ << '}'; in_ratexp_ = false; top_level_ = false; break; default: break; } skiprec: if (full_parent_ && !top_level) closep(); else if (overline) // The following string contains only the character U+0305 // (a combining overline). It looks better than U+0304 (a // combining overbar). os_ << "̅"; } void resugar_concat(const multop* mo) { unsigned max = mo->size(); for (unsigned i = 0; i < max; ++i) { if (i > 0) emit(KConcat); if (i + 1 < max) { // Try to match (!b)[*];b const formula* b = match_goto(mo, i); if (b) { emit_bunop_child(b); // Wait... maybe we are looking at (!b)[*];b;(!b)[*] // in which case it's b[=1]. if (i + 2 < max && mo->nth(i) == mo->nth(i + 2)) { emit(KEqualBunop); os_ << '1'; emit(KCloseBunop); i += 2; } else { emit(KGotoBunop); emit(KCloseBunop); ++i; } continue; } // Try to match ((!b)[*];b)[*i..j];(!b)[*] if (const bunop* s = is_Star(mo->nth(i))) if (const formula* b2 = strip_star_not(mo->nth(i + 1))) if (const multop* sc = is_Concat(s->child())) if (const formula* b1 = match_goto(sc, 0)) if (b1 == b2) { emit_bunop_child(b1); emit(KEqualBunop); unsigned min = s->min(); os_ << min; unsigned max = s->max(); if (max != min) { os_ << ".."; if (max != bunop::unbounded) os_ << max; } emit(KCloseBunop); ++i; continue; } } mo->nth(i)->accept(*this); } } void visit(const multop* mo) { bool top_level = top_level_; top_level_ = false; if (!top_level) openp(); multop::type op = mo->op(); // Handle the concatenation separately, because we want to // "resugar" some patterns. if (op == multop::Concat) { resugar_concat(mo); if (!top_level) closep(); return; } mo->nth(0)->accept(*this); keyword k = KFalse; // Initialize to something to please GCC. switch (op) { case multop::Or: k = KOr; break; case multop::OrRat: k = KOrRat; break; case multop::And: k = in_ratexp_ ? KAndRat : KAnd; break; case multop::AndRat: k = KAndRat; break; case multop::AndNLM: k = KAndNLM; break; case multop::Concat: // Handled by resugar_concat. SPOT_UNREACHABLE(); break; case multop::Fusion: k = KFusion; break; } assert(k != KFalse); unsigned max = mo->size(); for (unsigned n = 1; n < max; ++n) { emit(k); mo->nth(n)->accept(*this); } if (!top_level) closep(); } protected: std::ostream& os_; bool top_level_; bool full_parent_; bool in_ratexp_; const char** kw_; }; } // anonymous std::ostream& to_string(const formula* f, std::ostream& os, bool full_parent, bool ratexp) { to_string_visitor v(os, full_parent, ratexp, spot_kw); f->accept(v); return os; } std::string to_string(const formula* f, bool full_parent, bool ratexp) { std::ostringstream os; to_string(f, os, full_parent, ratexp); return os.str(); } std::ostream& to_utf8_string(const formula* f, std::ostream& os, bool full_parent, bool ratexp) { to_string_visitor v(os, full_parent, ratexp, utf8_kw); f->accept(v); return os; } std::string to_utf8_string(const formula* f, bool full_parent, bool ratexp) { std::ostringstream os; to_utf8_string(f, os, full_parent, ratexp); return os.str(); } std::ostream& to_spin_string(const formula* f, std::ostream& os, bool full_parent) { // Remove xor, ->, and <-> first. const formula* fu = unabbreviate_logic(f); // Also remove W and M. f = unabbreviate_wm(fu); fu->destroy(); to_string_visitor v(os, full_parent, false, spin_kw); f->accept(v); f->destroy(); return os; } std::string to_spin_string(const formula* f, bool full_parent) { std::ostringstream os; to_spin_string(f, os, full_parent); return os.str(); } std::ostream& to_wring_string(const formula* f, std::ostream& os) { // Remove W and M. f = unabbreviate_wm(f); to_string_visitor v(os, true, false, wring_kw); f->accept(v); f->destroy(); return os; } std::string to_wring_string(const formula* f) { std::ostringstream os; to_wring_string(f, os); 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(); } std::ostream& to_sclatex_string(const formula* f, std::ostream& os, bool full_parent, bool ratexp) { to_string_visitor v(os, full_parent, ratexp, sclatex_kw); f->accept(v); return os; } std::string to_sclatex_string(const formula* f, bool full_parent, bool ratexp) { std::ostringstream os; to_sclatex_string(f, os, full_parent, ratexp); return os.str(); } } }