Introduce [=min..max] operator.

* src/ltlast/bunop.hh: Declare bunop::Equal
* src/ltlast/bunop.cc: Handle it.
* src/ltlparse/ltlparse.yy,
src/ltlparse/ltlscan.ll: Add rules for [=min..max].
* src/tgbaalgos/ltl2tgba_fm.cc: Handle bunop::Equal in
the translation.
* src/ltltest/equals.test: Test trivial identities
for [=min..max].
* src/tgbatest/ltl2tgba.test: Add new formulae to test.
This commit is contained in:
Alexandre Duret-Lutz 2010-10-14 18:51:34 +02:00
parent d7781bc4d6
commit 8d4a413a37
7 changed files with 236 additions and 91 deletions

View file

@ -24,6 +24,8 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include "constant.hh" #include "constant.hh"
#include "unop.hh"
#include "ltlvisit/kind.hh"
namespace spot namespace spot
{ {
@ -107,6 +109,8 @@ namespace spot
{ {
switch (op_) switch (op_)
{ {
case Equal:
return "Equal";
case Star: case Star:
return "Star"; return "Star";
} }
@ -120,14 +124,22 @@ namespace spot
{ {
std::ostringstream out; std::ostringstream out;
// Syntactic sugaring switch (op_)
if (min_ == 1 && max_ == unbounded)
{ {
out << "[+]"; case Star:
return out.str(); // Syntactic sugaring
if (min_ == 1 && max_ == unbounded)
{
out << "[+]";
return out.str();
}
out << "[*";
break;
case Equal:
out << "[=";
break;
} }
out << "[*";
if (min_ != 0 || max_ != unbounded) if (min_ != 0 || max_ != unbounded)
{ {
out << min_; out << min_;
@ -151,73 +163,115 @@ namespace spot
// Some trivial simplifications. // Some trivial simplifications.
// - [*0][*min..max] = [*0] switch (op)
if (child == constant::empty_word_instance())
return child;
// - 0[*0..max] = [*0]
// - 0[*min..max] = 0 if min > 0
if (child == constant::false_instance())
{ {
if (min == 0) case Equal:
return constant::empty_word_instance(); {
else // - 0[=0..max] = [*]
return child; // - 0[=min..max] = 0 if min > 0
} if (child == constant::false_instance())
{
if (min == 0)
{
max = -1U;
op = Star;
child = constant::true_instance();
break;
}
else
return child;
}
// - 1[=0] = [*0]
// - 1[=min..max] = 1[*min..max]
if (child == constant::true_instance())
{
if (max == 0)
return constant::empty_word_instance();
else
{
op = Star;
break;
}
}
// - Exp[=0] = (!Exp)[*]
if (max == 0)
return bunop::instance(bunop::Star,
unop::instance(unop::Not, child));
break;
}
case Star:
{
// - [*0][*min..max] = [*0]
if (child == constant::empty_word_instance())
return child;
// - Exp[*0..0] = [*0] // - 0[*0..max] = [*0]
if (max == 0) // - 0[*min..max] = 0 if min > 0
{ if (child == constant::false_instance())
child->destroy(); {
return constant::empty_word_instance(); if (min == 0)
} return constant::empty_word_instance();
else
return child;
}
// - Exp[*i..j][*min..max] = Exp[*i(min)..j(max)] if i*(min+1)<=j(min)+1. // - Exp[*0] = [*0]
bunop* s = dynamic_cast<bunop*>(child); if (max == 0)
if (s) {
{ child->destroy();
unsigned i = s->min(); return constant::empty_word_instance();
unsigned j = s->max(); }
// Exp has to be true between i*min and j*min // - Exp[*i..j][*min..max] = Exp[*i(min)..j(max)]
// then between i*(min+1) and j*(min+1) // if i*(min+1)<=j(min)+1.
// ... bunop* s = dynamic_cast<bunop*>(child);
// finally between i*max and j*max if (s)
// {
// We can merge these intervals into [i*min..j*max] iff the unsigned i = s->min();
// first are adjacent or overlap, i.e. iff unsigned j = s->max();
// i*(min+1) <= j*min+1.
// (Because i<=j, this entails that the other intervals also
// overlap).
formula* exp = s->child(); // Exp has to be true between i*min and j*min
if (j == unbounded) // then between i*(min+1) and j*(min+1)
{ // ...
min *= i; // finally between i*max and j*max
max = unbounded; //
// We can merge these intervals into [i*min..j*max] iff the
// first are adjacent or overlap, i.e. iff
// i*(min+1) <= j*min+1.
// (Because i<=j, this entails that the other intervals also
// overlap).
// Exp[*min..max] formula* exp = s->child();
exp->clone(); if (j == unbounded)
child->destroy(); {
child = exp; min *= i;
} max = unbounded;
else
{ // Exp[*min..max]
if (i * (min + 1) <= (j * min) + 1) exp->clone();
{ child->destroy();
min *= i; child = exp;
if (max != unbounded) }
{ else
if (j == unbounded) {
max = unbounded; if (i * (min + 1) <= (j * min) + 1)
else {
max *= j; min *= i;
} if (max != unbounded)
exp->clone(); {
child->destroy(); if (j == unbounded)
child = exp; max = unbounded;
} else
} max *= j;
}
exp->clone();
child->destroy();
child = exp;
}
}
}
break;
}
} }
pair p(pairo(op, child), pairu(min, max)); pair p(pairo(op, child), pairu(min, max));

View file

@ -37,7 +37,7 @@ namespace spot
class bunop : public ref_formula class bunop : public ref_formula
{ {
public: public:
enum type { Star }; enum type { Star, Equal };
static const unsigned unbounded = -1U; static const unsigned unbounded = -1U;
@ -49,7 +49,13 @@ namespace spot
/// - 0[*0..max] = [*0] /// - 0[*0..max] = [*0]
/// - 0[*min..max] = 0 if min > 0 /// - 0[*min..max] = 0 if min > 0
/// - [*0][*min..max] = [*0] /// - [*0][*min..max] = [*0]
/// - Exp[*0] = [*0]
/// - Exp[*i..j][*k..l] = Exp[*ik..jl] if i*(k+1)<=jk+1. /// - Exp[*i..j][*k..l] = Exp[*ik..jl] if i*(k+1)<=jk+1.
/// - 0[=0..max] = 1[*]
/// - 0[=min..max] = 0 if min > 0
/// - 1[=0] = [*0]
/// - 1[=min..max] = 1[*min..max] if max > 0
/// - Exp[=0] = (!Exp)[*]
/// ///
/// These rewriting rules imply that it is not possible to build /// These rewriting rules imply that it is not possible to build
/// an LTL formula object that is SYNTACTICALLY equal to one of /// an LTL formula object that is SYNTACTICALLY equal to one of

View file

@ -35,6 +35,7 @@
#include <string> #include <string>
#include "public.hh" #include "public.hh"
#include "ltlast/allnodes.hh" #include "ltlast/allnodes.hh"
#include "ltlvisit/kind.hh"
struct minmax_t { unsigned min, max; }; struct minmax_t { unsigned min, max; };
} }
@ -92,9 +93,10 @@ using namespace spot::ltl;
%token OP_X "next operator" OP_NOT "not operator" OP_STAR "star operator" %token OP_X "next operator" OP_NOT "not operator" OP_STAR "star operator"
%token OP_PLUS "plus operator" %token OP_PLUS "plus operator"
%token OP_STAR_OPEN "opening bracket for star operator" %token OP_STAR_OPEN "opening bracket for star operator"
%token OP_STAR_CLOSE "closing bracket for star operator" %token OP_EQUAL_OPEN "opening bracket for equal operator"
%token <num> OP_STAR_NUM "number for star operator" %token OP_SQBKT_CLOSE "closing bracket"
%token OP_STAR_SEP "separator for star operator" %token <num> OP_SQBKT_NUM "number for square bracket operator"
%token OP_SQBKT_SEP "separator for square bracket operator"
%token OP_UCONCAT "universal concat operator" %token OP_UCONCAT "universal concat operator"
%token OP_ECONCAT "existential concat operator" %token OP_ECONCAT "existential concat operator"
%token OP_UCONCAT_NONO "universal non-overlapping concat operator" %token OP_UCONCAT_NONO "universal non-overlapping concat operator"
@ -124,7 +126,7 @@ using namespace spot::ltl;
%nonassoc OP_X %nonassoc OP_X
/* High priority regex operator. */ /* High priority regex operator. */
%nonassoc OP_STAR OP_STAR_OPEN OP_PLUS %nonassoc OP_STAR OP_STAR_OPEN OP_PLUS OP_EQUAL_OPEN
/* Not has the most important priority after Wring's `=0' and `=1'. */ /* Not has the most important priority after Wring's `=0' and `=1'. */
%nonassoc OP_NOT %nonassoc OP_NOT
@ -133,7 +135,7 @@ using namespace spot::ltl;
%type <ltl> subformula booleanatom rationalexp %type <ltl> subformula booleanatom rationalexp
%type <ltl> bracedrationalexp parenthesedsubformula %type <ltl> bracedrationalexp parenthesedsubformula
%type <minmax> starargs %type <minmax> starargs equalargs sqbracketargs
%destructor { delete $$; } <str> %destructor { delete $$; } <str>
%destructor { $$->destroy(); } <ltl> %destructor { $$->destroy(); } <ltl>
@ -187,24 +189,27 @@ enderror: error END_OF_INPUT
} }
OP_STAR_SEP_opt: | OP_STAR_SEP OP_SQBKT_SEP_opt: | OP_SQBKT_SEP
error_opt: | error error_opt: | error
sqbracketargs: OP_SQBKT_NUM OP_SQBKT_SEP OP_SQBKT_NUM OP_SQBKT_CLOSE
{ $$.min = $1; $$.max = $3; }
| OP_SQBKT_NUM OP_SQBKT_SEP OP_SQBKT_CLOSE
{ $$.min = $1; $$.max = bunop::unbounded; }
| OP_SQBKT_SEP OP_SQBKT_NUM OP_SQBKT_CLOSE
{ $$.min = 0U; $$.max = $2; }
| OP_SQBKT_SEP_opt OP_SQBKT_CLOSE
{ $$.min = 0U; $$.max = bunop::unbounded; }
| OP_SQBKT_NUM OP_SQBKT_CLOSE
{ $$.min = $$.max = $1; }
starargs: OP_STAR starargs: OP_STAR
{ $$.min = 0U; $$.max = bunop::unbounded; } { $$.min = 0U; $$.max = bunop::unbounded; }
| OP_PLUS | OP_PLUS
{ $$.min = 1U; $$.max = bunop::unbounded; } { $$.min = 1U; $$.max = bunop::unbounded; }
| OP_STAR_OPEN OP_STAR_NUM OP_STAR_SEP OP_STAR_NUM OP_STAR_CLOSE | OP_STAR_OPEN sqbracketargs
{ $$.min = $2; $$.max = $4; } { $$ = $2; }
| OP_STAR_OPEN OP_STAR_NUM OP_STAR_SEP OP_STAR_CLOSE | OP_STAR_OPEN error OP_SQBKT_CLOSE
{ $$.min = $2; $$.max = bunop::unbounded; }
| OP_STAR_OPEN OP_STAR_SEP OP_STAR_NUM OP_STAR_CLOSE
{ $$.min = 0U; $$.max = $3; }
| OP_STAR_OPEN OP_STAR_SEP_opt OP_STAR_CLOSE
{ $$.min = 0U; $$.max = bunop::unbounded; }
| OP_STAR_OPEN OP_STAR_NUM OP_STAR_CLOSE
{ $$.min = $$.max = $2; }
| OP_STAR_OPEN error OP_STAR_CLOSE
{ error_list.push_back(parse_error(@$, { error_list.push_back(parse_error(@$,
"treating this star block as [*]")); "treating this star block as [*]"));
$$.min = 0U; $$.max = bunop::unbounded; } $$.min = 0U; $$.max = bunop::unbounded; }
@ -213,6 +218,17 @@ starargs: OP_STAR
"missing closing bracket for star")); "missing closing bracket for star"));
$$.min = $$.max = 0U; } $$.min = $$.max = 0U; }
equalargs: OP_EQUAL_OPEN sqbracketargs
{ $$ = $2; }
| OP_EQUAL_OPEN error OP_SQBKT_CLOSE
{ error_list.push_back(parse_error(@$,
"treating this star block as [*]"));
$$.min = 0U; $$.max = bunop::unbounded; }
| OP_EQUAL_OPEN error_opt END_OF_INPUT
{ error_list.push_back(parse_error(@$,
"missing closing bracket for star"));
$$.min = $$.max = 0U; }
/* The reason we use `constant::false_instance()' for error recovery /* The reason we use `constant::false_instance()' for error recovery
is that it isn't reference counted. (Hence it can't leak references.) */ is that it isn't reference counted. (Hence it can't leak references.) */
@ -323,6 +339,22 @@ rationalexp: booleanatom
| starargs | starargs
{ $$ = bunop::instance(bunop::Star, constant::true_instance(), { $$ = bunop::instance(bunop::Star, constant::true_instance(),
$1.min, $1.max); } $1.min, $1.max); }
| rationalexp equalargs
{
if ((kind_of($1) & Boolean_Kind) == Boolean_Kind)
{
$$ = bunop::instance(bunop::Equal, $1, $2.min, $2.max);
}
else
{
error_list.push_back(parse_error(@1,
"not a boolean expression: [=...] can only "
"be applied to a boolean expression"));
error_list.push_back(parse_error(@$,
"treating this block as false"));
$$ = constant::false_instance();
}
}
bracedrationalexp: BRACE_OPEN rationalexp BRACE_CLOSE bracedrationalexp: BRACE_OPEN rationalexp BRACE_CLOSE
{ $$ = $2; } { $$ = $2; }

View file

@ -59,7 +59,7 @@ flex_set_buffer(const char* buf, int start_tok)
%} %}
%s not_prop %s not_prop
%x star %x sqbracket
%% %%
@ -96,14 +96,15 @@ flex_set_buffer(const char* buf, int start_tok)
":" BEGIN(0); return token::OP_FUSION; ":" BEGIN(0); return token::OP_FUSION;
"*"|"[*]" BEGIN(0); return token::OP_STAR; "*"|"[*]" BEGIN(0); return token::OP_STAR;
"[+]" BEGIN(0); return token::OP_PLUS; "[+]" BEGIN(0); return token::OP_PLUS;
"[*" BEGIN(star); return token::OP_STAR_OPEN; "[*" BEGIN(sqbracket); return token::OP_STAR_OPEN;
<star>"]" BEGIN(0); return token::OP_STAR_CLOSE; "[=" BEGIN(sqbracket); return token::OP_EQUAL_OPEN;
<star>[0-9]+ { <sqbracket>"]" BEGIN(0); return token::OP_SQBKT_CLOSE;
<sqbracket>[0-9]+ {
unsigned num = 0; unsigned num = 0;
try { try {
num = boost::lexical_cast<unsigned>(yytext); num = boost::lexical_cast<unsigned>(yytext);
yylval->num = num; yylval->num = num;
return token::OP_STAR_NUM; return token::OP_SQBKT_NUM;
} }
catch (boost::bad_lexical_cast &) catch (boost::bad_lexical_cast &)
{ {
@ -114,7 +115,7 @@ flex_set_buffer(const char* buf, int start_tok)
yylloc->step(); yylloc->step();
} }
} }
<star>","|".." return token::OP_STAR_SEP; <sqbracket>","|".." return token::OP_SQBKT_SEP;
/* & and | come from Spin. && and || from LTL2BA. /* & and | come from Spin. && and || from LTL2BA.

View file

@ -139,3 +139,14 @@ run 0 ../equals '{a[+][*1..3]}' '{a[+]}'
run 0 ../equals '{a[*1..3][+]}' '{a[+]}' run 0 ../equals '{a[*1..3][+]}' '{a[+]}'
run 0 ../equals '{[*2][+]}' '{[*2][+]}' run 0 ../equals '{[*2][+]}' '{[*2][+]}'
run 0 ../equals '{[+][*2]}' '{[*2..]}' run 0 ../equals '{[+][*2]}' '{[*2..]}'
run 0 ../equals '{0[=2]}' '0'
run 0 ../equals '{0[=2..]}' '0'
run 0 ../equals '{0[=1..10]}' '0'
run 0 ../equals '{0[=0]}' '{[*]}'
run 0 ../equals '{0[=0..10]}' '{*}'
run 0 ../equals '{0[=0..]}' '{*}'
run 0 ../equals '{1[=0]}' '{[*0]}'
run 0 ../equals '{1[=1..2]}' '{[*1..2]}'
run 0 ../equals '{1[=..4]}' '{1[*..4]}'
run 0 ../equals '{b[=0]}' '{(!b)[*]}'

View file

@ -233,6 +233,9 @@ namespace spot
std::ostream& std::ostream&
trace_ltl_bdd(const translate_dict& d, bdd f) trace_ltl_bdd(const translate_dict& d, bdd f)
{ {
std::cerr << "Displaying BDD ";
bdd_print_set(std::cerr, d.dict, f) << ":" << std::endl;
minato_isop isop(f); minato_isop isop(f);
bdd cube; bdd cube;
while ((cube = isop.next()) != bddfalse) while ((cube = isop.next()) != bddfalse)
@ -318,7 +321,7 @@ namespace spot
bdd next_to_concat() bdd next_to_concat()
{ {
if (!to_concat_) if (!to_concat_)
to_concat_ = constant::empty_word_instance(); return bddtrue;
int x = dict_.register_next_variable(to_concat_); int x = dict_.register_next_variable(to_concat_);
return bdd_ithvar(x); return bdd_ithvar(x);
} }
@ -391,6 +394,9 @@ namespace spot
formula* f; formula* f;
unsigned min = bo->min(); unsigned min = bo->min();
unsigned max = bo->max(); unsigned max = bo->max();
assert(max > 0);
unsigned min2 = (min == 0) ? 0 : (min - 1); unsigned min2 = (min == 0) ? 0 : (min - 1);
unsigned max2 = unsigned max2 =
(max == bunop::unbounded) ? bunop::unbounded : (max - 1); (max == bunop::unbounded) ? bunop::unbounded : (max - 1);
@ -407,7 +413,36 @@ namespace spot
res_ = recurse(bo->child(), f); res_ = recurse(bo->child(), f);
if (min == 0) if (min == 0)
res_ |= now_to_concat(); res_ |= now_to_concat();
return; return;
case bunop::Equal:
{
// b[=min..max] == (!b;b[=min..max]) | (b;b[=min-1..max-1])
// b[=0..max] == [*0] | (!b;b[=0..max]) | (b;b[=0..max-1])
// Note: b[=0] == (!b)[*] is a trivial identity, so it will
// never occur here.
formula* f1 = // !b;b[min..max]
multop::instance(multop::Concat,
unop::instance(unop::Not,
bo->child()->clone()),
bo->clone());
formula* f2 = // b;b[=min-1..max-1]
multop::instance(multop::Concat,
bo->child()->clone(),
bunop::instance(bunop::Equal,
bo->child()->clone(),
min2, max2));
f = multop::instance(multop::Or, f1, f2);
res_ = recurse_and_concat(f);
f->destroy();
if (min == 0)
res_ |= now_to_concat();
return;
}
} }
/* Unreachable code. */ /* Unreachable code. */
assert(0); assert(0);

View file

@ -87,6 +87,12 @@ check_psl '{{[+];a;[+]} && {[+];b;[+]}}<>->c'
# Example from "Beyond Hardware Verification" by Glazberg, Moulin, Orni, # Example from "Beyond Hardware Verification" by Glazberg, Moulin, Orni,
# Ruah, Zarpas (2007). # Ruah, Zarpas (2007).
check_psl '{[*];req;ack}|=>{start;busy[*];done}' check_psl '{[*];req;ack}|=>{start;busy[*];done}'
# Examples from "Property-by-Example Guide: a Handbook of PSL Examples"
# by Ben David and Orni (2005)/
check_psl '{end[=3]}(false)' # 2.27.A
check_psl '{[*]; {read[=3]} && {write[=2]}} |=>
{(!read && !write)[*]; ready}' # 3.5.A
# Make sure 'a U (b U c)' has 3 states and 6 transitions, # Make sure 'a U (b U c)' has 3 states and 6 transitions,
# before and after degeneralization. # before and after degeneralization.