tl: add support for X[n], F[n:m] and G[n:m]

* NEWS, doc/tl/tl.tex, doc/tl/tl.bib: Document these new operators.
* spot/parsetl/parsetl.yy, spot/parsetl/scantl.ll: Parse those.
* spot/tl/formula.cc, spot/tl/formula.hh: Add constructors.
* spot/gen/formulas.cc: Use it.
* tests/core/sugar.test: New file.
* tests/Makefile.am: Add it.
This commit is contained in:
Alexandre Duret-Lutz 2018-07-05 17:29:25 +02:00
parent 2616ea7c80
commit e7aa334a71
10 changed files with 364 additions and 12 deletions

View file

@ -261,10 +261,7 @@ namespace spot
X_n(formula p, int n)
{
assert(n >= 0);
formula res = p;
while (n--)
res = X_(res);
return res;
return formula::X(n, p);
}
static formula

View file

@ -206,6 +206,7 @@ using namespace spot;
%token OP_W "weak until operator" OP_M "strong release operator"
%token OP_F "sometimes operator" OP_G "always operator"
%token OP_X "next operator" OP_NOT "not operator"
%token OP_XREP "X[.] operator" OP_FREP "F[.] operator" OP_GREP "G[.] operator"
%token OP_STAR "star operator" OP_BSTAR "bracket star operator"
%token OP_BFSTAR "bracket fusion-star operator"
%token OP_PLUS "plus operator"
@ -250,8 +251,8 @@ using namespace spot;
/* LTL operators. */
%right OP_U OP_R OP_M OP_W
%nonassoc OP_F OP_G
%nonassoc OP_X
%nonassoc OP_F OP_G OP_FREP OP_GREP
%nonassoc OP_X OP_XREP
/* High priority regex operator. */
%nonassoc OP_BSTAR OP_STAR_OPEN OP_PLUS
@ -822,14 +823,60 @@ subformula: booleanatom
{ $$ = fnode::unop(op::F, $2); }
| OP_F error
{ missing_right_op($$, @1, "sometimes operator"); }
| OP_FREP OP_SQBKT_NUM OP_SQBKT_CLOSE subformula %prec OP_FREP
{ $$ = fnode::nested_unop_range(op::X, op::Or, $2, $2, $4);
error_list.emplace_back(@1 + @3,
"F[n:m] expects two parameters");
}
| OP_FREP OP_SQBKT_NUM OP_SQBKT_SEP OP_SQBKT_NUM OP_SQBKT_CLOSE
subformula %prec OP_FREP
{ $$ = fnode::nested_unop_range(op::X, op::Or, $2, $4, $6); }
| OP_FREP OP_SQBKT_NUM OP_SQBKT_SEP OP_SQBKT_NUM OP_SQBKT_CLOSE
error
{ missing_right_op($$, @1 + @5, "F[.] operator"); }
| OP_FREP error_opt END_OF_INPUT
{ error_list.emplace_back(@$, "missing closing bracket for F[.]");
$$ = fnode::ff(); }
| OP_FREP error OP_SQBKT_CLOSE subformula %prec OP_FREP
{ error_list.emplace_back(@1 + @3,
"treating this F[.] as a simple F");
$$ = fnode::unop(op::F, $4); }
| OP_G subformula
{ $$ = fnode::unop(op::G, $2); }
| OP_G error
{ missing_right_op($$, @1, "always operator"); }
| OP_GREP OP_SQBKT_NUM OP_SQBKT_SEP OP_SQBKT_NUM OP_SQBKT_CLOSE
subformula %prec OP_GREP
{ $$ = fnode::nested_unop_range(op::X, op::And, $2, $4, $6); }
| OP_GREP OP_SQBKT_NUM OP_SQBKT_CLOSE subformula %prec OP_GREP
{ $$ = fnode::nested_unop_range(op::X, op::And, $2, $2, $4);
error_list.emplace_back(@1 + @3,
"G[n:m] expects two parameters");
}
| OP_GREP OP_SQBKT_NUM OP_SQBKT_SEP OP_SQBKT_NUM OP_SQBKT_CLOSE
error
{ missing_right_op($$, @1 + @5, "G[.] operator"); }
| OP_GREP error_opt END_OF_INPUT
{ error_list.emplace_back(@$, "missing closing bracket for G[.]");
$$ = fnode::ff(); }
| OP_GREP error OP_SQBKT_CLOSE subformula %prec OP_GREP
{ error_list.emplace_back(@1 + @3,
"treating this G[.] as a simple G");
$$ = fnode::unop(op::F, $4); }
| OP_X subformula
{ $$ = fnode::unop(op::X, $2); }
| OP_X error
{ missing_right_op($$, @1, "next operator"); }
| OP_XREP OP_SQBKT_NUM OP_SQBKT_CLOSE subformula %prec OP_XREP
{ $$ = fnode::nested_unop_range(op::X, op::Or, $2, $2, $4); }
| OP_XREP OP_SQBKT_NUM OP_SQBKT_CLOSE error
{ missing_right_op($$, @1 + @3, "X[.] operator"); }
| OP_XREP error OP_SQBKT_CLOSE subformula %prec OP_XREP
{ error_list.emplace_back(@$, "treating this X[.] as a simple X");
$$ = fnode::unop(op::X, $4); }
| OP_XREP error_opt END_OF_INPUT
{ error_list.emplace_back(@$, "missing closing bracket for X[.]");
$$ = fnode::ff(); }
| OP_NOT subformula
{ $$ = fnode::unop(op::Not, $2); }
| OP_NOT error

View file

@ -273,7 +273,21 @@ eol2 (\n\r)+|(\r\n)+
{ARROWL}|{DARROWL} BEGIN(0); return token::OP_IMPLIES;
{ARROWLR}|{DARROWLR} BEGIN(0); return token::OP_EQUIV;
/* <>, [], and () are used in Spin. */
/* TSLF-like syntactic sugar:
X[3]f = XXXf
F[2..4]f = XX(f | X(f | Xf))
G[2..4]f = XX(f & X(f & Xf))
We also have to deal with the Spin-like notation for G.
X[]f = XGf
*/
"X"[ \t\n]*"[]" yyless(1); return token::OP_X;
"F"[ \t\n]*"[]" yyless(1); return token::OP_F;
"G"[ \t\n]*"[]" yyless(1); return token::OP_G;
"X"[ \t\n]*"[" BEGIN(sqbracket); return token::OP_XREP;
"F"[ \t\n]*"[" BEGIN(sqbracket); return token::OP_FREP;
"G"[ \t\n]*"[" BEGIN(sqbracket); return token::OP_GREP;
/* <> (DIAMOND) and [] (BOX), are used in Spin.
() (CIRCLE) is not, but would make sense. */
"F"|{DIAMOND} BEGIN(0); return token::OP_F;
"G"|{BOX} BEGIN(0); return token::OP_G;
"U" BEGIN(0); return token::OP_U;

View file

@ -1,6 +1,6 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2015, 2016, 2017 Laboratoire de Recherche et Développement de
// l'Epita (LRDE).
// Copyright (C) 2015-2018 Laboratoire de Recherche et Développement
// de l'Epita (LRDE).
//
// This file is part of Spot, a model checking library.
//
@ -1621,6 +1621,22 @@ namespace spot
}
}
const fnode*
fnode::nested_unop_range(op uo, op bo, unsigned min, unsigned max,
const fnode* f)
{
const fnode* res = f;
if (max < min)
std::swap(min, max);
for (unsigned i = min; i < max; ++i)
{
const fnode* a = f->clone();
res = fnode::multop(bo, {a, fnode::unop(uo, res)});
}
for (unsigned i = 0; i < min; ++i)
res = fnode::unop(uo, res);
return res;
}
std::ostream& fnode::dump(std::ostream& os) const
{

View file

@ -1,6 +1,6 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2015, 2016, 2017 Laboratoire de Recherche et Développement de
// l'Epita (LRDE).
// Copyright (C) 2015-2018 Laboratoire de Recherche et Développement
// de l'Epita (LRDE).
//
// This file is part of Spot, a model checking library.
//
@ -155,6 +155,10 @@ namespace spot
static const fnode* bunop(op o, const fnode* f,
uint8_t min, uint8_t max = unbounded());
/// \see formula::nested_unop_range
static const fnode* nested_unop_range(op uo, op bo, unsigned min,
unsigned max, const fnode* f);
/// \see formula::kind
op kind() const
{
@ -887,11 +891,39 @@ namespace spot
SPOT_DEF_UNOP(X);
/// @}
/// \brief Construct an X[n]
///
/// X[3]f = XXXf
static formula X(unsigned level, const formula& f)
{
return nested_unop_range(op::X, op::Or /* unused */, level, level, f);
}
/// \brief Construct an F
/// @{
SPOT_DEF_UNOP(F);
/// @}
/// \brief Construct F[n:m]
///
/// F[2:3] = XX(a | Xa)
///
/// This syntax is from TSLF; the operator is called next_e![n..m] in PSL.
static formula F(unsigned min_level, unsigned max_level, const formula& f)
{
return nested_unop_range(op::X, op::Or, min_level, max_level, f);
}
/// \brief Construct G[n:m]
///
/// G[2:3] = XX(a & Xa)
///
/// This syntax is from TSLF; the operator is called next_a![n..m] in PSL.
static formula G(unsigned min_level, unsigned max_level, const formula& f)
{
return nested_unop_range(op::X, op::And, min_level, max_level, f);
}
/// \brief Construct a G
/// @{
SPOT_DEF_UNOP(G);
@ -1173,6 +1205,20 @@ namespace spot
/// @}
#undef SPOT_DEF_BUNOP
/// \brief Nested operator construction (syntactic sugar).
///
/// Build between min and max nested uo, and chose between the
/// different numbers with bo.
///
/// For instance nested_unup_range(op::X, op::Or, 2, 4, a) returns
/// XX(a | X(a | Xa)).
static const formula nested_unop_range(op uo, op bo, unsigned min,
unsigned max, formula f)
{
return formula(fnode::nested_unop_range(uo, bo, min, max,
f.ptr_->clone()));
}
/// \brief Create a SERE equivalent to b[->min..max]
///
/// The operator does not exist: it is handled as sugar by the parser