// -*- 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();
}
}
}