* spot/tl/formula.hh, spot/tl/formula.cc: Update the properties and track them. * tests/core/kind.test: Augment the test case. * doc/tl/tl.tex, doc/spot.bib, NEWS: Document these new classes.
2173 lines
68 KiB
C++
2173 lines
68 KiB
C++
// -*- coding: utf-8 -*-
|
|
// Copyright (C) by the Spot authors, see the AUTHORS file for details.
|
|
//
|
|
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
#include "config.h"
|
|
#include <spot/misc/common.hh>
|
|
#include <spot/tl/formula.hh>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <map>
|
|
#include <set>
|
|
#include <cstring>
|
|
#include <algorithm>
|
|
#include <spot/misc/bareword.hh>
|
|
#include <spot/tl/print.hh>
|
|
|
|
#ifndef HAVE_STRVERSCMP
|
|
// If the libc does not have this, a version is compiled in lib/.
|
|
extern "C" int strverscmp(const char *s1, const char *s2);
|
|
#endif
|
|
|
|
namespace spot
|
|
{
|
|
namespace
|
|
{
|
|
typedef std::vector<const fnode*> vec;
|
|
|
|
[[noreturn]] static void
|
|
report_repetition_overflow(unsigned val)
|
|
{
|
|
std::ostringstream s;
|
|
s << val << " exceeds maximum supported repetition ("
|
|
<< (fnode::unbounded()-1) << ')';
|
|
throw std::overflow_error(s.str());
|
|
}
|
|
|
|
[[noreturn]] static void
|
|
report_swapped_minmax(unsigned min, unsigned max)
|
|
{
|
|
std::ostringstream s;
|
|
s << "range " << min << ".." << max << " looks reversed (min>max)";
|
|
throw std::overflow_error(s.str());
|
|
}
|
|
|
|
// Compare two formulas, by looking at their operators and
|
|
// children. This does not use id for the top-level operator,
|
|
// because it is used to decide whether to reuse an equal existing
|
|
// formula.
|
|
struct formula_cmp
|
|
{
|
|
bool operator()(const fnode* l, const fnode* r) const
|
|
{
|
|
op opl = l->kind();
|
|
op opr = r->kind();
|
|
if (opl != opr)
|
|
return opl < opr;
|
|
|
|
if (SPOT_UNLIKELY(opl == op::Star || opl == op::FStar))
|
|
{
|
|
{
|
|
auto minl = l->min();
|
|
auto minr = r->min();
|
|
if (minl != minr)
|
|
return minl < minr;
|
|
}
|
|
{
|
|
auto maxl = l->max();
|
|
auto maxr = r->max();
|
|
if (maxl != maxr)
|
|
return maxl < maxr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto szl = l->size();
|
|
auto szr = r->size();
|
|
if (szl != szr)
|
|
return szl < szr;
|
|
}
|
|
|
|
auto el = l->end();
|
|
auto ir = r->begin();
|
|
for (auto il = l->begin(); il != el; ++il, ++ir)
|
|
if (*il != *ir)
|
|
return (*il)->id() < (*ir)->id();
|
|
|
|
return false;
|
|
}
|
|
};
|
|
|
|
struct maps_t final
|
|
{
|
|
std::map<std::string, const fnode*> name2ap;
|
|
std::map<size_t, std::string> ap2name;
|
|
|
|
std::set<const fnode*, formula_cmp> uniq;
|
|
};
|
|
static maps_t m;
|
|
|
|
static void
|
|
gather_bool(vec& v, op o)
|
|
{
|
|
// Gather all boolean terms.
|
|
vec b;
|
|
vec::iterator i = v.begin();
|
|
while (i != v.end())
|
|
{
|
|
if ((*i)->is_boolean())
|
|
{
|
|
b.emplace_back(*i);
|
|
i = v.erase(i);
|
|
}
|
|
else
|
|
{
|
|
++i;
|
|
}
|
|
}
|
|
// - AndNLM(Exps1...,Bool1,Exps2...,Bool2,Exps3...) =
|
|
// AndNLM(And(Bool1,Bool2),Exps1...,Exps2...,Exps3...)
|
|
// - AndRat(Exps1...,Bool1,Exps2...,Bool2,Exps3...) =
|
|
// AndRat(And(Bool1,Bool2),Exps1...,Exps2...,Exps3...)
|
|
// - OrRat(Exps1...,Bool1,Exps2...,Bool2,Exps3...) =
|
|
// OrRat(Or(Bool1,Bool2),Exps1...,Exps2...,Exps3...)
|
|
if (!b.empty())
|
|
v.insert(v.begin(), fnode::multop(o, std::move(b)));
|
|
}
|
|
}
|
|
|
|
const fnode* fnode::unique(fnode* f)
|
|
{
|
|
auto ires = m.uniq.emplace(f);
|
|
if (!ires.second)
|
|
{
|
|
//(*ires.first)->dump(std::cerr << "UNI: ") << '\n';
|
|
for (auto c: *f)
|
|
c->destroy();
|
|
f->~fnode();
|
|
::operator delete(f);
|
|
return (*ires.first)->clone();
|
|
}
|
|
//f->dump(std::cerr << "INS: ") << '\n';
|
|
return f;
|
|
}
|
|
|
|
void
|
|
fnode::destroy_aux() const
|
|
{
|
|
if (SPOT_UNLIKELY(is(op::ap)))
|
|
{
|
|
auto i = m.ap2name.find(id());
|
|
auto n = m.name2ap.erase(i->second);
|
|
assert(n == 1);
|
|
(void)n;
|
|
m.ap2name.erase(i);
|
|
}
|
|
else
|
|
{
|
|
auto n = m.uniq.erase(this);
|
|
assert(n == 1);
|
|
(void)n;
|
|
for (auto c: *this)
|
|
c->destroy();
|
|
}
|
|
this->~fnode();
|
|
::operator delete(const_cast<fnode*>(this));
|
|
}
|
|
|
|
void
|
|
fnode::report_non_existing_child()
|
|
{
|
|
throw std::runtime_error("access to non-existing child");
|
|
}
|
|
|
|
void
|
|
fnode::report_too_many_children()
|
|
{
|
|
throw std::runtime_error("too many children for formula");
|
|
}
|
|
|
|
void
|
|
fnode::report_get_child_of_expecting_single_child_node()
|
|
{
|
|
throw std::invalid_argument
|
|
("get_child_of() expecting single-child node");
|
|
}
|
|
|
|
void
|
|
fnode::report_min_invalid_arg()
|
|
{
|
|
throw std::invalid_argument
|
|
("min() only works on Star and FStar nodes");
|
|
}
|
|
|
|
void
|
|
fnode::report_max_invalid_arg()
|
|
{
|
|
throw std::invalid_argument
|
|
("min() only works on Star and FStar nodes");
|
|
}
|
|
|
|
void
|
|
formula::report_ap_invalid_arg()
|
|
{
|
|
throw std::invalid_argument("atomic propositions cannot be "
|
|
"constructed from arbitrary formulas");
|
|
}
|
|
|
|
std::string fnode::kindstr() const
|
|
{
|
|
switch (op_)
|
|
{
|
|
#define C(x) \
|
|
case op::x: \
|
|
return #x; \
|
|
break
|
|
C(ff);
|
|
C(tt);
|
|
C(eword);
|
|
C(ap);
|
|
C(Not);
|
|
C(X);
|
|
C(F);
|
|
C(G);
|
|
C(Closure);
|
|
C(NegClosure);
|
|
C(NegClosureMarked);
|
|
C(Xor);
|
|
C(Implies);
|
|
C(Equiv);
|
|
C(U);
|
|
C(R);
|
|
C(W);
|
|
C(M);
|
|
C(EConcat);
|
|
C(EConcatMarked);
|
|
C(UConcat);
|
|
C(Or);
|
|
C(OrRat);
|
|
C(And);
|
|
C(AndRat);
|
|
C(AndNLM);
|
|
C(Concat);
|
|
C(Fusion);
|
|
C(Star);
|
|
C(FStar);
|
|
C(first_match);
|
|
C(strong_X);
|
|
#undef C
|
|
}
|
|
SPOT_UNREACHABLE();
|
|
}
|
|
|
|
const fnode*
|
|
fnode::multop(op o, vec v)
|
|
{
|
|
// Inline children of same kind.
|
|
//
|
|
// When we construct a formula such as Multop(Op,X,Multop(Op,Y,Z))
|
|
// we will want to inline it as Multop(Op,X,Y,Z).
|
|
//
|
|
// At the same time, it's possible that vec contains some null
|
|
// pointers we should remove. We can do it in the same loop.
|
|
//
|
|
// It is simpler to construct a separate vector to do that, but that's
|
|
// only needed if we have nested multops or null pointers.
|
|
if (std::find_if(v.begin(), v.end(),
|
|
[o](const fnode* f) { return f == nullptr || f->is(o); })
|
|
!= v.end())
|
|
{
|
|
vec inlined;
|
|
for (const fnode* f: v)
|
|
{
|
|
if (f == nullptr)
|
|
continue;
|
|
if (f->is(o))
|
|
{
|
|
unsigned ps = f->size();
|
|
for (unsigned n = 0; n < ps; ++n)
|
|
inlined.emplace_back(f->nth(n)->clone());
|
|
f->destroy();
|
|
}
|
|
else
|
|
{
|
|
inlined.emplace_back(f);
|
|
}
|
|
}
|
|
v.swap(inlined);
|
|
}
|
|
if (o != op::Concat && o != op::Fusion)
|
|
std::sort(v.begin(), v.end(), formula_ptr_less_than_bool_first());
|
|
|
|
unsigned orig_size = v.size();
|
|
|
|
const fnode* neutral; // neutral element
|
|
const fnode* neutral2; // second neutral element (if any)
|
|
const fnode* abs; // absorbent element
|
|
const fnode* abs2; // second absorbent element (if any)
|
|
const fnode* weak_abs; // almost absorbent element (if any)
|
|
// The notion of "almost absorbent" captures situation where the
|
|
// present of the element can be simplified in itself or another
|
|
// element depending on a condition on the rest of the formula.
|
|
switch (o)
|
|
{
|
|
case op::And:
|
|
neutral = tt();
|
|
neutral2 = nullptr;
|
|
abs = ff();
|
|
abs2 = nullptr;
|
|
weak_abs = nullptr;
|
|
break;
|
|
case op::AndRat:
|
|
neutral = one_star();
|
|
{
|
|
// If this AndRat contains an operand that does not accept
|
|
// the empty word, and that is not [+], then any [+] can be
|
|
// removed.
|
|
bool one_non_eword_non_plus =
|
|
std::find_if(v.begin(), v.end(),
|
|
[o = one_plus()](const fnode* f) {
|
|
return !f->accepts_eword() && f != o;
|
|
}) != v.end();
|
|
neutral2 = one_non_eword_non_plus ? one_plus() : nullptr;
|
|
}
|
|
abs = ff();
|
|
abs2 = nullptr;
|
|
weak_abs = eword();
|
|
gather_bool(v, op::And);
|
|
break;
|
|
case op::AndNLM:
|
|
neutral = eword();
|
|
neutral2 = nullptr;
|
|
abs = ff();
|
|
abs2 = nullptr;
|
|
weak_abs = tt();
|
|
gather_bool(v, op::And);
|
|
break;
|
|
case op::Or:
|
|
neutral = ff();
|
|
neutral2 = nullptr;
|
|
abs = tt();
|
|
abs2 = nullptr;
|
|
weak_abs = nullptr;
|
|
break;
|
|
case op::OrRat:
|
|
neutral = ff();
|
|
neutral2 = nullptr;
|
|
abs = one_star();
|
|
abs2 = nullptr;
|
|
weak_abs = one_plus();
|
|
gather_bool(v, op::Or);
|
|
break;
|
|
case op::Concat:
|
|
neutral = eword();
|
|
neutral2 = nullptr;
|
|
abs = ff();
|
|
abs2 = nullptr;
|
|
weak_abs = nullptr;
|
|
|
|
// - Concat(Exps1...,FExps2...,1[*],FExps3...,Exps4) =
|
|
// Concat(Exps1...,1[*],Exps4)
|
|
// If FExps2... and FExps3 all accept [*0].
|
|
{
|
|
vec::iterator i = v.begin();
|
|
const fnode* os = one_star();
|
|
while (i != v.end())
|
|
{
|
|
while (i != v.end() && !(*i)->accepts_eword())
|
|
++i;
|
|
if (i == v.end())
|
|
break;
|
|
vec::iterator b = i;
|
|
// b is the first expressions that accepts [*0].
|
|
// let's find more, and locate the position of
|
|
// 1[*] at the same time.
|
|
bool os_seen = false;
|
|
do
|
|
{
|
|
os_seen |= (*i == os);
|
|
++i;
|
|
}
|
|
while (i != v.end() && (*i)->accepts_eword());
|
|
|
|
if (os_seen) // [b..i) is a range that contains [*].
|
|
{
|
|
// Place [*] at the start of the range, and erase
|
|
// all other formulae.
|
|
(*b)->destroy();
|
|
*b++ = os->clone();
|
|
for (vec::iterator c = b; c < i; ++c)
|
|
(*c)->destroy();
|
|
i = v.erase(b, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
case op::Fusion:
|
|
neutral = tt();
|
|
neutral2 = nullptr;
|
|
abs = ff();
|
|
abs2 = eword();
|
|
weak_abs = nullptr;
|
|
|
|
// Make a first pass to group adjacent Boolean formulae.
|
|
// - Fusion(Exps1...,BoolExp1...BoolExpN,Exps2,Exps3...) =
|
|
// Fusion(Exps1...,And(BoolExp1...BoolExpN),Exps2,Exps3...)
|
|
{
|
|
vec::iterator i = v.begin();
|
|
while (i != v.end())
|
|
{
|
|
if ((*i)->is_boolean())
|
|
{
|
|
vec::iterator first = i;
|
|
++i;
|
|
if (i == v.end())
|
|
break;
|
|
if (!(*i)->is_boolean())
|
|
{
|
|
++i;
|
|
continue;
|
|
}
|
|
do
|
|
++i;
|
|
while (i != v.end() && (*i)->is_boolean());
|
|
// We have at least two adjacent Boolean formulae.
|
|
// Replace the first one by the conjunction of all.
|
|
// FIXME: Investigate the removal of the temporary
|
|
// vector, by allowing range to be passed to
|
|
// multop() instead of entire containers. We
|
|
// should then able to do
|
|
// *first = multop(op::And, first, i);
|
|
// i = v.erase(first + 1, i);
|
|
vec b;
|
|
b.insert(b.begin(), first, i);
|
|
i = v.erase(first + 1, i);
|
|
*first = multop(op::And, b);
|
|
}
|
|
else
|
|
{
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
neutral = nullptr;
|
|
neutral2 = nullptr;
|
|
abs = nullptr;
|
|
abs2 = nullptr;
|
|
weak_abs = nullptr;
|
|
break;
|
|
}
|
|
|
|
// Remove duplicates (except for Concat and Fusion). We can't use
|
|
// std::unique(), because we must destroy() any formula we drop.
|
|
// Also ignore neutral elements and handle absorbent elements.
|
|
{
|
|
const fnode* last = nullptr;
|
|
vec::iterator i = v.begin();
|
|
bool weak_abs_seen = false;
|
|
while (i != v.end())
|
|
{
|
|
if ((*i == neutral) || (*i == neutral2) || (*i == last))
|
|
{
|
|
(*i)->destroy();
|
|
i = v.erase(i);
|
|
}
|
|
else if (*i == abs || *i == abs2)
|
|
{
|
|
for (i = v.begin(); i != v.end(); ++i)
|
|
(*i)->destroy();
|
|
assert(abs);
|
|
return abs->clone();
|
|
}
|
|
else
|
|
{
|
|
weak_abs_seen |= (*i == weak_abs);
|
|
if (o != op::Concat && o != op::Fusion)
|
|
// Don't remove duplicates
|
|
last = *i;
|
|
++i;
|
|
}
|
|
}
|
|
|
|
if (weak_abs_seen)
|
|
{
|
|
if (o == op::AndRat)
|
|
{
|
|
// We have a* && [*0] && c = 0
|
|
// and a* && [*0] && c* = [*0]
|
|
// So if [*0] has been seen, check if alls term
|
|
// recognize the empty word.
|
|
bool acc_eword = true;
|
|
for (i = v.begin(); i != v.end(); ++i)
|
|
{
|
|
acc_eword &= (*i)->accepts_eword();
|
|
(*i)->destroy();
|
|
}
|
|
if (acc_eword)
|
|
return weak_abs;
|
|
else
|
|
return abs;
|
|
}
|
|
else if (o == op::AndNLM)
|
|
{
|
|
// Similarly, a* & 1 & (c;d) = c;d
|
|
// a* & 1 & c* = 1
|
|
vec tmp;
|
|
for (auto i: v)
|
|
{
|
|
if (i == weak_abs)
|
|
continue;
|
|
if (i->accepts_eword())
|
|
{
|
|
i->destroy();
|
|
continue;
|
|
}
|
|
tmp.emplace_back(i);
|
|
}
|
|
if (tmp.empty())
|
|
tmp.emplace_back(weak_abs);
|
|
v.swap(tmp);
|
|
}
|
|
else if (o == op::OrRat)
|
|
{
|
|
// We have a[*] | [+] | c = [*]
|
|
// and a | [+] | c = [+]
|
|
// So if [+] has been seen, check if some term
|
|
// recognize the empty word.
|
|
bool acc_eword = false;
|
|
for (i = v.begin(); i != v.end(); ++i)
|
|
{
|
|
acc_eword |= (*i)->accepts_eword();
|
|
(*i)->destroy();
|
|
}
|
|
if (acc_eword)
|
|
return abs;
|
|
else
|
|
return weak_abs;
|
|
}
|
|
else
|
|
{
|
|
SPOT_UNREACHABLE();
|
|
}
|
|
}
|
|
else if (o == op::Concat || o == op::Fusion)
|
|
{
|
|
// Perform an extra loop to merge starable items.
|
|
// f;f -> f[*2]
|
|
// f;f[*i..j] -> f[*i+1..j+1]
|
|
// f[*i..j];f -> f[*i+1..j+1]
|
|
// f[*i..j];f[*k..l] -> f[*i+k..j+l]
|
|
// same for FStar:
|
|
// f:f -> f[:*2]
|
|
// f:f[*i..j] -> f[:*i+1..j+1]
|
|
// f[:*i..j];f -> f[:*i+1..j+1]
|
|
// f[:*i..j];f[:*k..l] -> f[:*i+k..j+l]
|
|
op bop = o == op::Concat ? op::Star : op::FStar;
|
|
i = v.begin();
|
|
while (i != v.end())
|
|
{
|
|
vec::iterator fpos = i;
|
|
const fnode* f;
|
|
unsigned min;
|
|
unsigned max;
|
|
bool changed = false;
|
|
if ((*i)->is(bop))
|
|
{
|
|
f = (*i)->nth(0);
|
|
min = (*i)->min();
|
|
max = (*i)->max();
|
|
}
|
|
else
|
|
{
|
|
f = *i;
|
|
min = max = 1;
|
|
}
|
|
|
|
++i;
|
|
while (i != v.end())
|
|
{
|
|
const fnode* f2;
|
|
unsigned min2;
|
|
unsigned max2;
|
|
if ((*i)->is(bop))
|
|
{
|
|
f2 = (*i)->nth(0);
|
|
if (f2 != f)
|
|
break;
|
|
min2 = (*i)->min();
|
|
max2 = (*i)->max();
|
|
}
|
|
else
|
|
{
|
|
f2 = *i;
|
|
if (f2 != f)
|
|
break;
|
|
min2 = max2 = 1;
|
|
}
|
|
if (min2 == unbounded())
|
|
{
|
|
min = unbounded();
|
|
}
|
|
else if (min != unbounded())
|
|
{
|
|
if (SPOT_UNLIKELY(min + min2 >= unbounded()))
|
|
break;
|
|
min += min2;
|
|
}
|
|
if (max2 == unbounded())
|
|
{
|
|
max = unbounded();
|
|
}
|
|
else if (max != unbounded())
|
|
{
|
|
if (SPOT_UNLIKELY(max + max2 >= unbounded()))
|
|
break;
|
|
max += max2;
|
|
}
|
|
(*i)->destroy();
|
|
i = v.erase(i);
|
|
changed = true;
|
|
}
|
|
if (changed)
|
|
{
|
|
const fnode* newfs = bunop(bop, f->clone(), min, max);
|
|
(*fpos)->destroy();
|
|
*fpos = newfs;
|
|
}
|
|
}
|
|
// also
|
|
// b[*i..j]:b -> b[*max(1,i),j]
|
|
// b:b[*i..j] -> b[*max(1,i),j]
|
|
// b[*i..j]:b[*k..l] -> b[*max(i,1)+max(j,1)-1,j+l-1]
|
|
if (o == op::Fusion && v.size() > 1)
|
|
{
|
|
i = v.begin();
|
|
while (i != v.end())
|
|
{
|
|
if (!(((*i)->is(op::Star) && (*i)->nth(0)->is_boolean())
|
|
|| (*i)->is_boolean()))
|
|
{
|
|
++i;
|
|
continue;
|
|
}
|
|
const fnode *b;
|
|
unsigned min;
|
|
unsigned max;
|
|
if ((*i)->is_boolean())
|
|
{
|
|
min = max = 1;
|
|
b = *i;
|
|
}
|
|
else
|
|
{
|
|
b = (*i)->nth(0);
|
|
min = (*i)->min();
|
|
max = (*i)->max();
|
|
}
|
|
vec::iterator prev = i;
|
|
++i;
|
|
bool changed = false;
|
|
while (i != v.end())
|
|
{
|
|
unsigned min2;
|
|
unsigned max2;
|
|
if ((*i)->is_boolean())
|
|
{
|
|
if (*i != b)
|
|
break;
|
|
min2 = max2 = 1;
|
|
}
|
|
else if ((*i)->is(op::Star) && (*i)->nth(0)->is_boolean())
|
|
{
|
|
if ((*i)->nth(0) != b)
|
|
break;
|
|
min2 = (*i)->min();
|
|
max2 = (*i)->max();
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
// Now we can merge prev and i.
|
|
min = min + (min == 0) + min2 + (min2 == 0) - 1;
|
|
assert(max != 0 && max2 != 0);
|
|
if (max2 == unbounded() || max == unbounded())
|
|
max = unbounded();
|
|
else if (max + max2 < unbounded())
|
|
max = max + max2 - 1;
|
|
else
|
|
break;
|
|
changed = true;
|
|
(*i)->destroy();
|
|
i = v.erase(i);
|
|
}
|
|
if (changed)
|
|
{
|
|
const fnode* newf =
|
|
fnode::bunop(op::Star, b->clone(), min, max);
|
|
(*prev)->destroy();
|
|
*prev = newf;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
vec::size_type s = v.size();
|
|
if (s == 0)
|
|
{
|
|
assert(neutral != nullptr);
|
|
return neutral->clone();
|
|
}
|
|
else if (s == 1)
|
|
{
|
|
// Simply replace Multop(Op,X) by X.
|
|
// Except we should never reduce the
|
|
// arguments of a Fusion operator to
|
|
// a list with a single formula that
|
|
// accepts [*0].
|
|
const fnode* res = v[0];
|
|
if (o != op::Fusion || orig_size == 1
|
|
|| !res->accepts_eword())
|
|
return res;
|
|
// If Fusion(f, ...) reduces to Fusion(f), emit Fusion(1,f).
|
|
// to ensure that [*0] is not accepted.
|
|
v.insert(v.begin(), tt());
|
|
}
|
|
|
|
auto mem = ::operator new(sizeof(fnode)
|
|
+ (v.size() - 1)*sizeof(*children));
|
|
return unique(new(mem) fnode(o, v.begin(), v.end()));
|
|
}
|
|
|
|
const fnode*
|
|
fnode::bunop(op o, const fnode* child, unsigned min, unsigned max)
|
|
{
|
|
if (SPOT_UNLIKELY(min >= unbounded()))
|
|
report_repetition_overflow(min);
|
|
// We are testing strict ">", because unbounded() is a legitimate
|
|
// input for max. We just cannot tell if it was really intented
|
|
// as "unbounded".
|
|
if (SPOT_UNLIKELY(max > unbounded()))
|
|
report_repetition_overflow(max);
|
|
if (SPOT_UNLIKELY(min > max))
|
|
report_swapped_minmax(min, max);
|
|
|
|
const fnode* neutral = nullptr;
|
|
switch (o)
|
|
{
|
|
case op::Star:
|
|
if (max == unbounded() && child == tt_)
|
|
{
|
|
// bypass normal construction: 1[*] and 1[+] are
|
|
// frequently used, so they are not reference counted.
|
|
if (min == 0)
|
|
return one_star();
|
|
if (min == 1)
|
|
return one_plus();
|
|
}
|
|
|
|
neutral = eword();
|
|
break;
|
|
case op::FStar:
|
|
neutral = tt();
|
|
break;
|
|
default:
|
|
SPOT_UNREACHABLE();
|
|
}
|
|
|
|
|
|
// common trivial simplifications
|
|
|
|
// - [*0][*min..max] = [*0]
|
|
// - [*0][:*0..max] = 1
|
|
// - [*0][:*min..max] = 0 if min > 0
|
|
if (child->is_eword())
|
|
switch (o)
|
|
{
|
|
case op::Star:
|
|
return neutral;
|
|
case op::FStar:
|
|
if (min == 0)
|
|
return neutral;
|
|
else
|
|
return ff();
|
|
default:
|
|
SPOT_UNREACHABLE();
|
|
}
|
|
|
|
// - 0[*0..max] = [*0]
|
|
// - 0[*min..max] = 0 if min > 0
|
|
// - b[:*0..max] = 1
|
|
// - b[:*min..max] = 0 if min > 0
|
|
if (child->is_ff()
|
|
|| (o == op::FStar && child->is_boolean()))
|
|
{
|
|
if (min == 0)
|
|
{
|
|
child->destroy();
|
|
return neutral;
|
|
}
|
|
return child;
|
|
}
|
|
|
|
// - Exp[*0] = [*0]
|
|
// - Exp[:*0] = 1
|
|
if (max == 0)
|
|
{
|
|
child->destroy();
|
|
return neutral;
|
|
}
|
|
|
|
// - Exp[*1] = Exp
|
|
// - Exp[:*1] = Exp if Exp does not accept [*0]
|
|
if (min == 1 && max == 1)
|
|
if (o == op::Star || !child->accepts_eword())
|
|
return child;
|
|
|
|
// - 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.
|
|
if (child->is(o))
|
|
{
|
|
unsigned i = child->min();
|
|
unsigned j = child->max();
|
|
|
|
// Exp has to be true between i*min and j*min
|
|
// then between i*(min+1) and j*(min+1)
|
|
// ...
|
|
// finally between i*max and j*max
|
|
//
|
|
// 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).
|
|
|
|
const fnode* exp = child->nth(0);
|
|
if (j == unbounded())
|
|
{
|
|
// cannot simplify if the bound overflows
|
|
if ((min * i) < unbounded())
|
|
{
|
|
min *= i;
|
|
max = unbounded();
|
|
|
|
// Exp[*min..max]
|
|
exp->clone();
|
|
child->destroy();
|
|
child = exp;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((i * (min + 1) <= (j * min) + 1)
|
|
// cannot simplify if the bound overflows
|
|
&& (min < unbounded()
|
|
&& (max == unbounded()
|
|
|| j == unbounded()
|
|
|| max * j < unbounded())))
|
|
{
|
|
min *= i;
|
|
if (max != unbounded())
|
|
{
|
|
if (j == unbounded())
|
|
max = unbounded();
|
|
else
|
|
max *= j;
|
|
}
|
|
exp->clone();
|
|
child->destroy();
|
|
child = exp;
|
|
}
|
|
}
|
|
}
|
|
|
|
return unique(new fnode(o, child, min, max));
|
|
}
|
|
|
|
const fnode*
|
|
fnode::unop(op o, const fnode* f)
|
|
{
|
|
// Some trivial simplifications.
|
|
switch (o)
|
|
{
|
|
case op::F:
|
|
case op::G:
|
|
{
|
|
// F and G are idempotent.
|
|
if (f->is(o))
|
|
return f;
|
|
|
|
// F(0) = G(0) = 0
|
|
// F(1) = G(1) = 1
|
|
if (f->is_ff() || f->is_tt())
|
|
return f;
|
|
|
|
assert(!f->is_eword());
|
|
}
|
|
break;
|
|
|
|
case op::Not:
|
|
{
|
|
// !1 = 0
|
|
if (f->is_tt())
|
|
return ff();
|
|
// !0 = 1
|
|
if (f->is_ff())
|
|
return tt();
|
|
// ![*0] = 1[+]
|
|
if (f->is_eword())
|
|
return one_plus();
|
|
|
|
auto fop = f->kind();
|
|
// "Not" is an involution.
|
|
if (fop == o)
|
|
{
|
|
auto c = f->nth(0)->clone();
|
|
f->destroy();
|
|
return c;
|
|
}
|
|
// !Closure(Exp) = NegClosure(Exp)
|
|
if (fop == op::Closure)
|
|
{
|
|
const fnode* c = unop(op::NegClosure, f->nth(0)->clone());
|
|
f->destroy();
|
|
return c;
|
|
}
|
|
// !NegClosure(Exp) = Closure(Exp)
|
|
if (fop == op::NegClosure || fop == op::NegClosureMarked)
|
|
{
|
|
const fnode* c = unop(op::Closure, f->nth(0)->clone());
|
|
f->destroy();
|
|
return c;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case op::X:
|
|
// X(1) = 1
|
|
if (f->is_tt())
|
|
return f;
|
|
// We do not have X(0)=0 because that
|
|
// is not true with finite semantics.
|
|
assert(!f->is_eword());
|
|
break;
|
|
case op::strong_X:
|
|
// X[!](0) = 0
|
|
if (f->is_ff())
|
|
return f;
|
|
// Note: with finite semantics X[!](1)≠1.
|
|
assert(!f->is_eword());
|
|
break;
|
|
|
|
case op::Closure:
|
|
// {0} = 0, {1} = 1, {b} = b
|
|
if (f->is_boolean())
|
|
return f;
|
|
// {[*0]} = 0
|
|
if (f->is_eword())
|
|
return ff();
|
|
break;
|
|
|
|
case op::NegClosure:
|
|
case op::NegClosureMarked:
|
|
// {1} = 0
|
|
if (f->is_tt())
|
|
return ff();
|
|
// {0} = 1, {[*0]} = 1
|
|
if (f->is_ff() || f->is_eword())
|
|
return tt();
|
|
// {b} = !b
|
|
if (f->is_boolean())
|
|
return unop(op::Not, f);
|
|
break;
|
|
case op::first_match:
|
|
// first_match(first_match(sere)) = first_match(sere);
|
|
// first_match(b) = b
|
|
if (f->is(o) || f->is_boolean())
|
|
return f;
|
|
// first_match(r*) = [*0]
|
|
if (f->accepts_eword())
|
|
{
|
|
f->destroy();
|
|
return eword();
|
|
}
|
|
break;
|
|
default:
|
|
SPOT_UNREACHABLE();
|
|
}
|
|
|
|
return unique(new fnode(o, {f}));
|
|
}
|
|
|
|
const fnode*
|
|
fnode::binop(op o, const fnode* first, const fnode* second)
|
|
{
|
|
// Sort the operands of commutative operators, so that for
|
|
// example the formula instance for 'a xor b' is the same as
|
|
// that for 'b xor a'.
|
|
|
|
// Trivial identities:
|
|
switch (o)
|
|
{
|
|
case op::Xor:
|
|
{
|
|
// Xor is commutative: sort operands.
|
|
formula_ptr_less_than_bool_first cmp;
|
|
if (cmp(second, first))
|
|
std::swap(second, first);
|
|
}
|
|
// - (1 ^ Exp) = !Exp
|
|
// - (0 ^ Exp) = Exp
|
|
if (first->is_tt())
|
|
return unop(op::Not, second);
|
|
if (first->is_ff())
|
|
return second;
|
|
if (first == second)
|
|
{
|
|
first->destroy();
|
|
second->destroy();
|
|
return ff();
|
|
}
|
|
// We expect constants to appear first, because they are
|
|
// instantiated first.
|
|
assert(!second->is_constant());
|
|
break;
|
|
case op::Equiv:
|
|
{
|
|
// Equiv is commutative: sort operands.
|
|
formula_ptr_less_than_bool_first cmp;
|
|
if (cmp(second, first))
|
|
std::swap(second, first);
|
|
}
|
|
// - (0 <=> Exp) = !Exp
|
|
// - (1 <=> Exp) = Exp
|
|
// - (Exp <=> Exp) = 1
|
|
if (first->is_ff())
|
|
return unop(op::Not, second);
|
|
if (first->is_tt())
|
|
return second;
|
|
if (first == second)
|
|
{
|
|
first->destroy();
|
|
second->destroy();
|
|
return tt();
|
|
}
|
|
// We expect constants to appear first, because they are
|
|
// instantiated first.
|
|
assert(!second->is_constant());
|
|
break;
|
|
case op::Implies:
|
|
// - (1 => Exp) = Exp
|
|
// - (0 => Exp) = 1
|
|
// - (Exp => 1) = 1
|
|
// - (Exp => 0) = !Exp
|
|
// - (Exp => Exp) = 1
|
|
if (first->is_tt())
|
|
return second;
|
|
if (first->is_ff())
|
|
{
|
|
second->destroy();
|
|
return tt();
|
|
}
|
|
if (second->is_tt())
|
|
{
|
|
first->destroy();
|
|
return second;
|
|
}
|
|
if (second->is_ff())
|
|
return unop(op::Not, first);
|
|
if (first == second)
|
|
{
|
|
first->destroy();
|
|
second->destroy();
|
|
return tt();
|
|
}
|
|
break;
|
|
case op::U:
|
|
// - (Exp U 1) = 1
|
|
// - (Exp U 0) = 0
|
|
// - (0 U Exp) = Exp
|
|
// - (Exp U Exp) = Exp
|
|
if (second->is_tt()
|
|
|| second->is_ff()
|
|
|| first->is_ff()
|
|
|| first == second)
|
|
{
|
|
first->destroy();
|
|
return second;
|
|
}
|
|
break;
|
|
case op::W:
|
|
// - (Exp W 1) = 1
|
|
// - (0 W Exp) = Exp
|
|
// - (1 W Exp) = 1
|
|
// - (Exp W Exp) = Exp
|
|
if (second->is_tt()
|
|
|| first->is_ff()
|
|
|| first == second)
|
|
{
|
|
first->destroy();
|
|
return second;
|
|
}
|
|
if (first->is_tt())
|
|
{
|
|
second->destroy();
|
|
return first;
|
|
}
|
|
break;
|
|
case op::R:
|
|
// - (Exp R 1) = 1
|
|
// - (Exp R 0) = 0
|
|
// - (1 R Exp) = Exp
|
|
// - (Exp R Exp) = Exp
|
|
if (second->is_tt()
|
|
|| second->is_ff()
|
|
|| first->is_tt()
|
|
|| first == second)
|
|
{
|
|
first->destroy();
|
|
return second;
|
|
}
|
|
break;
|
|
case op::M:
|
|
// - (Exp M 0) = 0
|
|
// - (1 M Exp) = Exp
|
|
// - (0 M Exp) = 0
|
|
// - (Exp M Exp) = Exp
|
|
if (second->is_ff()
|
|
|| first->is_tt()
|
|
|| first == second)
|
|
{
|
|
first->destroy();
|
|
return second;
|
|
}
|
|
if (first->is_ff())
|
|
{
|
|
second->destroy();
|
|
return first;
|
|
}
|
|
break;
|
|
case op::EConcat:
|
|
case op::EConcatMarked:
|
|
// - 0 <>-> Exp = 0
|
|
// - 1 <>-> Exp = Exp
|
|
// - [*0] <>-> Exp = 0
|
|
// - Exp <>-> 0 = 0
|
|
// - [*] <>-> 1 = 1
|
|
// - boolExp <>-> Exp = boolExp & Exp
|
|
if (first->is_tt())
|
|
return second;
|
|
if (first->is_ff()
|
|
|| first->is_eword())
|
|
{
|
|
second->destroy();
|
|
return ff();
|
|
}
|
|
if (second->is_ff() || (second->is_tt() && first == one_star()))
|
|
{
|
|
first->destroy();
|
|
return second;
|
|
}
|
|
if (first->is_boolean())
|
|
return multop(op::And, {first, second});
|
|
break;
|
|
case op::UConcat:
|
|
// - 0 []-> Exp = 1
|
|
// - 1 []-> Exp = Exp
|
|
// - [*0] []-> Exp = 1
|
|
// - Exp []-> 1 = 1
|
|
// - [*] []-> 0 = 0
|
|
// - boolExp []-> Exp = !boolExp | Exp
|
|
if (first->is_tt())
|
|
return second;
|
|
if (first->is_ff()
|
|
|| first->is_eword())
|
|
{
|
|
second->destroy();
|
|
return tt();
|
|
}
|
|
if (second->is_tt() || (second->is_ff() && first == one_star()))
|
|
{
|
|
first->destroy();
|
|
return second;
|
|
}
|
|
if (first->is_boolean())
|
|
return multop(op::Or, {unop(op::Not, first), second});
|
|
break;
|
|
default:
|
|
SPOT_UNREACHABLE();
|
|
}
|
|
|
|
auto mem = ::operator new(sizeof(fnode) + sizeof(*children));
|
|
return unique(new(mem) fnode(o, {first, second}));
|
|
}
|
|
|
|
const fnode*
|
|
fnode::ap(const std::string& name)
|
|
{
|
|
auto ires = m.name2ap.emplace(name, nullptr);
|
|
if (!ires.second)
|
|
return ires.first->second->clone();
|
|
// Name the formula before creating it, because the constructor
|
|
// will call ap_name().
|
|
//
|
|
// We plan to use next_id_ for identifier, but it is possible that
|
|
// next_id_ has already wrapped around 0 and that next_id_ is
|
|
// already the name of another atomic proposition. In that
|
|
// unlikely case, simply increment the id.
|
|
while (SPOT_UNLIKELY(!m.ap2name.emplace(next_id_, name).second))
|
|
bump_next_id();
|
|
// next_id_ is incremented by setup_props(), called by the
|
|
// constructor of fnode
|
|
return ires.first->second = new fnode(op::ap, {});
|
|
}
|
|
|
|
const std::string&
|
|
fnode::ap_name() const
|
|
{
|
|
if (op_ != op::ap)
|
|
throw std::runtime_error("ap_name() called on non-AP formula");
|
|
auto i = m.ap2name.find(id());
|
|
assert(i != m.ap2name.end());
|
|
return i->second;
|
|
}
|
|
|
|
size_t fnode::next_id_ = 0U;
|
|
|
|
size_t fnode::bump_next_id()
|
|
{
|
|
size_t id = next_id_++;
|
|
// If the counter of formulae ever loops, we want to skip the
|
|
// first three values, because they are permanently associated
|
|
// to constants, and it is convenient to have constants
|
|
// smaller than all other formulas.
|
|
if (SPOT_UNLIKELY(next_id_ == 0))
|
|
next_id_ = 3;
|
|
return id;
|
|
}
|
|
|
|
const fnode* fnode::ff_ = new fnode(op::ff, {}, true);
|
|
const fnode* fnode::tt_ = new fnode(op::tt, {}, true);
|
|
const fnode* fnode::ew_ = new fnode(op::eword, {}, true);
|
|
const fnode* fnode::one_star_ = nullptr; // Only built when necessary.
|
|
const fnode* fnode::one_plus_ = nullptr; // Only built when necessary.
|
|
|
|
void fnode::setup_props(op o)
|
|
{
|
|
id_ = bump_next_id();
|
|
|
|
switch (o)
|
|
{
|
|
case op::ff:
|
|
case op::tt:
|
|
is_.boolean = true;
|
|
is_.sugar_free_boolean = true;
|
|
is_.in_nenoform = true;
|
|
is_.syntactic_si = true; // for LTL (not PSL)
|
|
is_.sugar_free_ltl = true;
|
|
is_.ltl_formula = true;
|
|
is_.psl_formula = true;
|
|
is_.sere_formula = true;
|
|
is_.finite = true;
|
|
is_.eventual = true;
|
|
is_.universal = true;
|
|
is_.syntactic_safety = true;
|
|
is_.syntactic_guarantee = true;
|
|
is_.syntactic_obligation = true;
|
|
is_.syntactic_recurrence = true;
|
|
is_.syntactic_persistence = true;
|
|
is_.not_marked = true;
|
|
is_.accepting_eword = false;
|
|
is_.lbt_atomic_props = true;
|
|
is_.spin_atomic_props = true;
|
|
is_.delta1 = true;
|
|
is_.sigma2 = true;
|
|
is_.pi2 = true;
|
|
is_.delta2 = true;
|
|
break;
|
|
case op::eword:
|
|
is_.boolean = false;
|
|
is_.sugar_free_boolean = false;
|
|
is_.in_nenoform = true;
|
|
is_.syntactic_si = true;
|
|
is_.sugar_free_ltl = true;
|
|
is_.ltl_formula = false;
|
|
is_.psl_formula = false;
|
|
is_.sere_formula = true;
|
|
is_.finite = true;
|
|
is_.eventual = false;
|
|
is_.syntactic_safety = false;
|
|
is_.syntactic_guarantee = false;
|
|
is_.syntactic_obligation = false;
|
|
is_.syntactic_recurrence = false;
|
|
is_.syntactic_persistence = false;
|
|
is_.universal = false;
|
|
is_.not_marked = true;
|
|
is_.accepting_eword = true;
|
|
is_.lbt_atomic_props = true;
|
|
is_.spin_atomic_props = true;
|
|
is_.delta1 = true;
|
|
is_.sigma2 = true;
|
|
is_.pi2 = true;
|
|
is_.delta2 = true;
|
|
break;
|
|
case op::ap:
|
|
is_.boolean = true;
|
|
is_.sugar_free_boolean = true;
|
|
is_.in_nenoform = true;
|
|
is_.syntactic_si = true; // Assuming LTL (for PSL, a Boolean
|
|
// term that is not stared will be regarded as non-SI where
|
|
// this matters.)
|
|
is_.sugar_free_ltl = true;
|
|
is_.ltl_formula = true;
|
|
is_.psl_formula = true;
|
|
is_.sere_formula = true;
|
|
is_.finite = true;
|
|
is_.eventual = false;
|
|
is_.universal = false;
|
|
is_.syntactic_safety = true;
|
|
is_.syntactic_guarantee = true;
|
|
is_.syntactic_obligation = true;
|
|
is_.syntactic_recurrence = true;
|
|
is_.syntactic_persistence = true;
|
|
is_.not_marked = true;
|
|
is_.accepting_eword = false;
|
|
{
|
|
// is_.lbt_atomic_props should be true if the name has the
|
|
// form pNN where NN is any number of digit.
|
|
const std::string& n = ap_name();
|
|
std::string::const_iterator pos = n.begin();
|
|
bool lbtap = (pos != n.end() && *pos++ == 'p');
|
|
while (lbtap && pos != n.end())
|
|
{
|
|
char l = *pos++;
|
|
lbtap = (l >= '0' && l <= '9');
|
|
}
|
|
is_.lbt_atomic_props = lbtap;
|
|
is_.spin_atomic_props = lbtap || is_spin_ap(n.c_str());
|
|
}
|
|
is_.delta1 = true;
|
|
is_.sigma2 = true;
|
|
is_.pi2 = true;
|
|
is_.delta2 = true;
|
|
break;
|
|
case op::Not:
|
|
props = children[0]->props;
|
|
is_.not_marked = true;
|
|
is_.eventual = children[0]->is_universal();
|
|
is_.universal = children[0]->is_eventual();
|
|
is_.in_nenoform = (children[0]->is(op::ap));
|
|
is_.sere_formula = is_.boolean;
|
|
|
|
is_.syntactic_safety = children[0]->is_syntactic_guarantee();
|
|
is_.syntactic_guarantee = children[0]->is_syntactic_safety();
|
|
// is_.syntactic_obligation inherited from child
|
|
is_.syntactic_recurrence = children[0]->is_syntactic_persistence();
|
|
is_.syntactic_persistence = children[0]->is_syntactic_recurrence();
|
|
|
|
is_.accepting_eword = false;
|
|
// is_.delta1 inherited
|
|
is_.sigma2 = children[0]->is_pi2();
|
|
is_.pi2 = children[0]->is_sigma2();
|
|
// is_.delta2 inherited
|
|
break;
|
|
case op::X:
|
|
case op::strong_X:
|
|
props = children[0]->props;
|
|
is_.not_marked = true;
|
|
is_.boolean = false;
|
|
is_.syntactic_si = false;
|
|
is_.sere_formula = false;
|
|
// is_.syntactic_safety inherited
|
|
// is_.syntactic_guarantee inherited
|
|
// is_.syntactic_obligation inherited
|
|
// is_.syntactic_recurrence inherited
|
|
// is_.syntactic_persistence inherited
|
|
|
|
// is_.accepting_eword is currently unused outside SEREs, but
|
|
// we could make sense of it if we start supporting LTL over
|
|
// finite traces.
|
|
is_.accepting_eword = false;
|
|
// is_.delta1 inherited
|
|
// is_.sigma2 inherited
|
|
// is_.pi2 inherited
|
|
// is_.delta2 inherited
|
|
break;
|
|
case op::F:
|
|
props = children[0]->props;
|
|
is_.not_marked = true;
|
|
is_.boolean = false;
|
|
is_.sere_formula = false;
|
|
is_.finite = false;
|
|
is_.sugar_free_ltl = false;
|
|
is_.eventual = true;
|
|
is_.syntactic_safety = false;
|
|
// is_.syntactic_guarantee inherited
|
|
is_.syntactic_obligation = is_.syntactic_guarantee;
|
|
is_.syntactic_recurrence = is_.syntactic_guarantee;
|
|
// is_.syntactic_persistence inherited
|
|
is_.accepting_eword = false;
|
|
is_.delta1 = is_.syntactic_guarantee;
|
|
is_.pi2 = is_.syntactic_guarantee;
|
|
// is_.sigma2 inherited
|
|
is_.delta2 = is_.pi2 | is_.sigma2;
|
|
break;
|
|
case op::G:
|
|
props = children[0]->props;
|
|
is_.not_marked = true;
|
|
is_.boolean = false;
|
|
is_.sere_formula = false;
|
|
is_.finite = false;
|
|
is_.sugar_free_ltl = false;
|
|
is_.universal = true;
|
|
// is_.syntactic_safety inherited
|
|
is_.syntactic_guarantee = false;
|
|
is_.syntactic_obligation = is_.syntactic_safety;
|
|
// is_.syntactic_recurrence inherited
|
|
is_.syntactic_persistence = is_.syntactic_safety;
|
|
is_.accepting_eword = false;
|
|
is_.delta1 = is_.syntactic_safety;
|
|
is_.sigma2 = is_.syntactic_safety;
|
|
// is_.pi2 inherited
|
|
is_.delta2 = is_.pi2 | is_.sigma2;
|
|
break;
|
|
case op::NegClosure:
|
|
case op::NegClosureMarked:
|
|
props = children[0]->props;
|
|
is_.not_marked = (op_ == op::NegClosure);
|
|
is_.boolean = false;
|
|
is_.ltl_formula = false;
|
|
is_.psl_formula = true;
|
|
is_.sere_formula = false;
|
|
is_.syntactic_safety = is_.finite;
|
|
is_.syntactic_guarantee = true;
|
|
is_.syntactic_obligation = true;
|
|
is_.syntactic_recurrence = true;
|
|
is_.syntactic_persistence = true;
|
|
is_.accepting_eword = false;
|
|
is_.delta1 = true;
|
|
is_.sigma2 = true;
|
|
is_.pi2 = true;
|
|
is_.delta2 = true;
|
|
assert(children[0]->is_sere_formula());
|
|
assert(!children[0]->is_boolean());
|
|
break;
|
|
case op::Closure:
|
|
props = children[0]->props;
|
|
is_.not_marked = true;
|
|
is_.boolean = false;
|
|
is_.ltl_formula = false;
|
|
is_.psl_formula = true;
|
|
is_.sere_formula = false;
|
|
is_.syntactic_safety = true;
|
|
is_.syntactic_guarantee = is_.finite;
|
|
is_.syntactic_obligation = true;
|
|
is_.syntactic_recurrence = true;
|
|
is_.syntactic_persistence = true;
|
|
is_.accepting_eword = false;
|
|
is_.delta1 = true;
|
|
is_.sigma2 = true;
|
|
is_.pi2 = true;
|
|
is_.delta2 = true;
|
|
assert(children[0]->is_sere_formula());
|
|
assert(!children[0]->is_boolean());
|
|
break;
|
|
case op::Xor:
|
|
case op::Equiv:
|
|
props = children[0]->props & children[1]->props;
|
|
// Preserve suspendable property
|
|
is_.eventual = is_.universal = is_.eventual && is_.universal;
|
|
is_.sere_formula = is_.boolean;
|
|
is_.sugar_free_boolean = false;
|
|
is_.in_nenoform = false;
|
|
// is_.syntactic_obligation inherited;
|
|
is_.accepting_eword = false;
|
|
if (is_.syntactic_obligation)
|
|
{
|
|
// Only formula that are in the intersection of
|
|
// guarantee and safety are closed by Xor and <=>.
|
|
bool sg = is_.syntactic_safety && is_.syntactic_guarantee;
|
|
is_.syntactic_safety = sg;
|
|
is_.syntactic_guarantee = sg;
|
|
assert(is_.syntactic_recurrence == true);
|
|
assert(is_.syntactic_persistence == true);
|
|
}
|
|
else
|
|
{
|
|
is_.syntactic_safety = false;
|
|
is_.syntactic_guarantee = false;
|
|
is_.syntactic_recurrence = false;
|
|
is_.syntactic_persistence = false;
|
|
}
|
|
if (is_.delta1)
|
|
{
|
|
assert(is_.pi2 == true);
|
|
assert(is_.sigma2 == true);
|
|
assert(is_.delta2 == true);
|
|
}
|
|
else
|
|
{
|
|
is_.pi2 = false;
|
|
is_.sigma2 = false;
|
|
}
|
|
break;
|
|
case op::Implies:
|
|
props = children[0]->props & children[1]->props;
|
|
is_.eventual =
|
|
children[0]->is_universal() && children[1]->is_eventual();
|
|
is_.universal =
|
|
children[0]->is_eventual() && children[1]->is_universal();
|
|
is_.sere_formula = (children[0]->is_boolean()
|
|
&& children[1]->is_sere_formula());
|
|
is_.sugar_free_boolean = false;
|
|
is_.in_nenoform = false;
|
|
is_.syntactic_safety = (children[0]->is_syntactic_guarantee()
|
|
&& children[1]->is_syntactic_safety());
|
|
is_.syntactic_guarantee = (children[0]->is_syntactic_safety()
|
|
&& children[1]->is_syntactic_guarantee());
|
|
// is_.syntactic_obligation inherited
|
|
is_.syntactic_persistence = children[0]->is_syntactic_recurrence()
|
|
&& children[1]->is_syntactic_persistence();
|
|
is_.syntactic_recurrence = children[0]->is_syntactic_persistence()
|
|
&& children[1]->is_syntactic_recurrence();
|
|
is_.accepting_eword = false;
|
|
// is_.delta1 inherited
|
|
is_.sigma2 = children[0]->is_pi2() && children[1]->is_sigma2();
|
|
is_.pi2 = children[0]->is_sigma2() && children[1]->is_pi2();
|
|
// is_.delta2 inherited
|
|
break;
|
|
case op::EConcatMarked:
|
|
case op::EConcat:
|
|
props = children[0]->props & children[1]->props;
|
|
is_.not_marked = (op_ != op::EConcatMarked);
|
|
is_.ltl_formula = false;
|
|
is_.boolean = false;
|
|
is_.sere_formula = false;
|
|
is_.accepting_eword = false;
|
|
is_.psl_formula = true;
|
|
|
|
is_.syntactic_guarantee = children[1]->is_syntactic_guarantee();
|
|
is_.syntactic_persistence = children[1]->is_syntactic_persistence();
|
|
is_.sigma2 = children[1]->is_sigma2();
|
|
if (children[0]->is_finite()) // behaves like X
|
|
{
|
|
is_.syntactic_safety = children[1]->is_syntactic_safety();
|
|
is_.syntactic_obligation = children[1]->is_syntactic_obligation();
|
|
is_.syntactic_recurrence = children[1]->is_syntactic_recurrence();
|
|
is_.delta1 = children[1]->is_delta1();
|
|
is_.pi2 = children[1]->is_pi2();
|
|
is_.delta2 = children[1]->is_delta2();
|
|
}
|
|
else // behaves like F
|
|
{
|
|
is_.syntactic_safety = false;
|
|
bool g = children[1]->is_syntactic_guarantee();
|
|
is_.syntactic_obligation = g;
|
|
is_.syntactic_recurrence = g;
|
|
is_.delta1 = g;
|
|
is_.pi2 = g;
|
|
is_.delta2 = g | is_.sigma2;
|
|
}
|
|
assert(children[0]->is_sere_formula());
|
|
assert(children[1]->is_psl_formula());
|
|
if (children[0]->is_boolean())
|
|
is_.syntactic_si = false;
|
|
break;
|
|
case op::UConcat:
|
|
props = children[0]->props & children[1]->props;
|
|
is_.not_marked = true;
|
|
is_.ltl_formula = false;
|
|
is_.boolean = false;
|
|
is_.sere_formula = false;
|
|
is_.accepting_eword = false;
|
|
is_.psl_formula = true;
|
|
|
|
is_.syntactic_safety = children[1]->is_syntactic_safety();
|
|
is_.syntactic_recurrence = children[1]->is_syntactic_recurrence();
|
|
is_.pi2 = children[1]->is_pi2();
|
|
if (children[0]->is_finite()) // behaves like X
|
|
{
|
|
is_.syntactic_guarantee = children[1]->is_syntactic_guarantee();
|
|
is_.syntactic_obligation = children[1]->is_syntactic_obligation();
|
|
is_.syntactic_persistence = children[1]->is_syntactic_persistence();
|
|
is_.delta1 = children[1]->is_delta1();
|
|
is_.sigma2 = children[1]->is_sigma2();
|
|
is_.delta2 = children[1]->is_delta2();
|
|
}
|
|
else // behaves like G
|
|
{
|
|
is_.syntactic_guarantee = false;
|
|
bool s = children[1]->is_syntactic_safety();
|
|
is_.syntactic_obligation = s;
|
|
is_.syntactic_persistence = s;
|
|
is_.delta1 = s;
|
|
is_.sigma2 = s;
|
|
is_.delta2 = is_.pi2 | s;
|
|
}
|
|
assert(children[0]->is_sere_formula());
|
|
assert(children[1]->is_psl_formula());
|
|
if (children[0]->is_boolean())
|
|
is_.syntactic_si = false;
|
|
break;
|
|
case op::U:
|
|
// Beware: (f U g) is a pure eventuality if both operands
|
|
// are pure eventualities, unlike in the proceedings of
|
|
// Concur'00. (The revision of the paper available at
|
|
// http://www.bell-labs.com/project/TMP/ is fixed.) See
|
|
// also http://arxiv.org/abs/1011.4214v2 for a discussion
|
|
// about this problem. (Which we fixed in 2005 thanks
|
|
// to LBTT.)
|
|
// This means that we can use the following line to handle
|
|
// all cases of (f U g), (f R g), (f W g), (f M g) for
|
|
// universality and eventuality.
|
|
props = children[0]->props & children[1]->props;
|
|
// The matter can be further refined because:
|
|
// (f U g) is a pure eventuality if
|
|
// g is a pure eventuality (regardless of f),
|
|
// or f == 1
|
|
// (g M f) is a pure eventuality if f and g are,
|
|
// or f == 1
|
|
// (g R f) is purely universal if
|
|
// f is purely universal (regardless of g)
|
|
// or g == 0
|
|
// (f W g) is purely universal if f and g are
|
|
// or g == 0
|
|
is_.not_marked = true;
|
|
// f U g is universal if g is eventual, or if f == 1.
|
|
is_.eventual = children[1]->is_eventual();
|
|
is_.eventual |= children[0]->is_tt();
|
|
is_.boolean = false;
|
|
is_.sere_formula = false;
|
|
is_.finite = false;
|
|
is_.accepting_eword = false;
|
|
|
|
is_.syntactic_safety = false;
|
|
// is_.syntactic_guarantee = Guarantee U Guarantee
|
|
is_.syntactic_obligation = // Obligation U Guarantee
|
|
children[0]->is_syntactic_obligation()
|
|
&& children[1]->is_syntactic_guarantee();
|
|
is_.syntactic_recurrence = // Recurrence U Guarantee
|
|
children[0]->is_syntactic_recurrence()
|
|
&& children[1]->is_syntactic_guarantee();
|
|
// is_.syntactic_persistence = Persistence U Persistance
|
|
is_.delta1 = is_.syntactic_guarantee;
|
|
// is_.sigma2 = Σ₂ U Σ₂
|
|
is_.pi2 = is_.syntactic_guarantee;
|
|
is_.delta2 = is_.sigma2 | is_.pi2;
|
|
break;
|
|
case op::W:
|
|
// See comment for op::U.
|
|
props = children[0]->props & children[1]->props;
|
|
is_.not_marked = true;
|
|
// f W g is universal if f and g are, or if g == 0.
|
|
is_.universal |= children[1]->is_ff();
|
|
is_.boolean = false;
|
|
is_.sere_formula = false;
|
|
is_.finite = false;
|
|
is_.accepting_eword = false;
|
|
|
|
// is_.syntactic_safety = Safety W Safety;
|
|
is_.syntactic_guarantee = false;
|
|
is_.syntactic_obligation = // Safety W Obligation
|
|
children[0]->is_syntactic_safety()
|
|
&& children[1]->is_syntactic_obligation();
|
|
// is_.syntactic_recurrence = Recurrence W Recurrence
|
|
is_.syntactic_persistence = // Safety W Persistance
|
|
children[0]->is_syntactic_safety()
|
|
&& children[1]->is_syntactic_persistence();
|
|
is_.delta1 = is_.syntactic_safety;
|
|
is_.sigma2 = is_.syntactic_safety;
|
|
// is_.pi2 = Π₂ U Π₂
|
|
is_.delta2 = is_.sigma2 | is_.pi2;
|
|
break;
|
|
case op::R:
|
|
// See comment for op::U.
|
|
props = children[0]->props & children[1]->props;
|
|
is_.not_marked = true;
|
|
// g R f is universal if f is universal, or if g == 0.
|
|
is_.universal = children[1]->is_universal();
|
|
is_.universal |= children[0]->is_ff();
|
|
is_.boolean = false;
|
|
is_.sere_formula = false;
|
|
is_.finite = false;
|
|
is_.accepting_eword = false;
|
|
|
|
// is_.syntactic_safety = Safety R Safety;
|
|
is_.syntactic_guarantee = false;
|
|
is_.syntactic_obligation = // Obligation R Safety
|
|
children[0]->is_syntactic_obligation()
|
|
&& children[1]->is_syntactic_safety();
|
|
//is_.syntactic_recurrence = Recurrence R Recurrence
|
|
is_.syntactic_persistence = // Persistence R Safety
|
|
children[0]->is_syntactic_persistence()
|
|
&& children[1]->is_syntactic_safety();
|
|
is_.delta1 = is_.syntactic_safety;
|
|
is_.sigma2 = is_.syntactic_safety;
|
|
// is_.pi2 = Π₂ U Π₂
|
|
is_.delta2 = is_.sigma2 | is_.pi2;
|
|
break;
|
|
case op::M:
|
|
// See comment for op::U.
|
|
props = children[0]->props & children[1]->props;
|
|
is_.not_marked = true;
|
|
// g M f is eventual if both g and f are eventual, or if f == 1.
|
|
is_.eventual |= children[1]->is_tt();
|
|
is_.boolean = false;
|
|
is_.sere_formula = false;
|
|
is_.finite = false;
|
|
is_.accepting_eword = false;
|
|
|
|
is_.syntactic_safety = false;
|
|
// is_.syntactic_guarantee = Guarantee M Guarantee
|
|
is_.syntactic_obligation = // Guarantee M Obligation
|
|
children[0]->is_syntactic_guarantee()
|
|
&& children[1]->is_syntactic_obligation();
|
|
is_.syntactic_recurrence = // Guarantee M Recurrence
|
|
children[0]->is_syntactic_guarantee()
|
|
&& children[1]->is_syntactic_recurrence();
|
|
// is_.syntactic_persistence = Persistence M Persistance
|
|
is_.delta1 = is_.syntactic_guarantee;
|
|
// is_.sigma2 = Σ₂ M Σ₂
|
|
is_.pi2 = is_.syntactic_guarantee;
|
|
is_.delta2 = is_.sigma2 | is_.pi2;
|
|
break;
|
|
case op::Or:
|
|
{
|
|
props = children[0]->props;
|
|
unsigned s = size_;
|
|
bool ew = children[0]->accepts_eword();
|
|
for (unsigned i = 1; i < s; ++i)
|
|
{
|
|
ew |= children[i]->accepts_eword();
|
|
props &= children[i]->props;
|
|
}
|
|
is_.accepting_eword = ew;
|
|
break;
|
|
}
|
|
case op::OrRat:
|
|
{
|
|
props = children[0]->props;
|
|
unsigned s = size_;
|
|
bool syntactic_si = is_.syntactic_si && !is_.boolean;
|
|
// Note: OrRat(p1,p2) is a Boolean formula, but its is
|
|
// actually rewritten as Or(p1,p2) by trivial identities
|
|
// before this constructor is called. So at this point,
|
|
// AndNLM is always used with at most one Boolean argument,
|
|
// and the result is therefore NOT Boolean.
|
|
is_.boolean = false;
|
|
is_.ltl_formula = false;
|
|
is_.psl_formula = false;
|
|
is_.eventual = false;
|
|
is_.universal = false;
|
|
|
|
bool ew = children[0]->accepts_eword();
|
|
for (unsigned i = 1; i < s; ++i)
|
|
{
|
|
ew |= children[i]->accepts_eword();
|
|
syntactic_si &= children[i]->is_syntactic_stutter_invariant()
|
|
&& !children[i]->is_boolean();
|
|
props &= children[i]->props;
|
|
}
|
|
is_.accepting_eword = ew;
|
|
is_.syntactic_si = syntactic_si;
|
|
break;
|
|
}
|
|
case op::And:
|
|
{
|
|
props = children[0]->props;
|
|
unsigned s = size_;
|
|
for (unsigned i = 1; i < s; ++i)
|
|
props &= children[i]->props;
|
|
break;
|
|
}
|
|
case op::Fusion:
|
|
case op::Concat:
|
|
case op::AndNLM:
|
|
case op::AndRat:
|
|
{
|
|
props = children[0]->props;
|
|
unsigned s = size_;
|
|
bool syntactic_si = is_.syntactic_si && !is_.boolean;
|
|
// Note: AndNLM(p1,p2) and AndRat(p1,p2) are Boolean
|
|
// formulae, but they are actually rewritten as And(p1,p2)
|
|
// by trivial identities before this constructor is called.
|
|
// So at this point, AndNLM/AndRat are always used with at
|
|
// most one Boolean argument, and the result is therefore
|
|
// NOT Boolean.
|
|
is_.boolean = false;
|
|
is_.ltl_formula = false;
|
|
is_.psl_formula = false;
|
|
is_.eventual = false;
|
|
is_.universal = false;
|
|
|
|
for (unsigned i = 1; i < s; ++i)
|
|
{
|
|
syntactic_si &= children[i]->is_syntactic_stutter_invariant()
|
|
&& !children[i]->is_boolean();
|
|
props &= children[i]->props;
|
|
}
|
|
is_.syntactic_si = syntactic_si;
|
|
if (op_ == op::Fusion)
|
|
is_.accepting_eword = false;
|
|
// A concatenation is an siSERE if it looks like
|
|
// r;b* or b*;r
|
|
// where b is Boolean and r is siSERE. generalized to n-ary
|
|
// concatenation, it means all arguments should be of the
|
|
// form b*, except one that is siSERE (i.e., a sub-formula
|
|
// that verify is_syntactic_stutter_invariant() and
|
|
// !is_boolean()). Since b* is siSERE, that means we
|
|
// want at least s-1 operands of the form b*.
|
|
if (op_ == op::Concat)
|
|
{
|
|
unsigned sb = 0; // stared Boolean formulas seen
|
|
for (unsigned i = 0; i < s; ++i)
|
|
{
|
|
auto ci = children[i];
|
|
if (ci->is_Kleene_star())
|
|
{
|
|
sb += ci->nth(0)->is_boolean();
|
|
}
|
|
else if (!ci->is_syntactic_stutter_invariant()
|
|
|| ci->is_boolean())
|
|
{
|
|
sb = 0;
|
|
break;
|
|
}
|
|
}
|
|
is_.syntactic_si = sb >= s - 1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case op::Star:
|
|
case op::FStar:
|
|
{
|
|
props = children[0]->props;
|
|
assert(is_.sere_formula);
|
|
is_.boolean = false;
|
|
is_.ltl_formula = false;
|
|
is_.psl_formula = false;
|
|
is_.eventual = false;
|
|
is_.universal = false;
|
|
is_.syntactic_safety = false;
|
|
is_.syntactic_guarantee = false;
|
|
is_.syntactic_obligation = false;
|
|
is_.syntactic_recurrence = false;
|
|
is_.syntactic_persistence = false;
|
|
is_.delta1 = false;
|
|
is_.pi2 = false;
|
|
is_.sigma2 = false;
|
|
is_.delta2 = false;
|
|
|
|
switch (op_)
|
|
{
|
|
case op::Star:
|
|
if (max_ == unbounded())
|
|
{
|
|
is_.finite = false;
|
|
is_.syntactic_si = min_ <= 1 && children[0]->is_boolean();
|
|
}
|
|
else
|
|
{
|
|
is_.syntactic_si = false;
|
|
}
|
|
if (min_ == 0)
|
|
is_.accepting_eword = true;
|
|
break;
|
|
case op::FStar:
|
|
is_.accepting_eword = false;
|
|
is_.syntactic_si &= !children[0]->is_boolean();
|
|
if (max_ == unbounded())
|
|
is_.finite = false;
|
|
if (min_ == 0)
|
|
is_.syntactic_si = false;
|
|
break;
|
|
default:
|
|
SPOT_UNREACHABLE();
|
|
}
|
|
}
|
|
break;
|
|
case op::first_match:
|
|
props = children[0]->props;
|
|
assert(is_.sere_formula);
|
|
// {(a[+];b*);c*}<>->d is stutter invariant
|
|
// {first_match(a[+];b*);c*}<>->d is NOT.
|
|
is_.syntactic_si = false;
|
|
is_.boolean = false;
|
|
is_.ltl_formula = false;
|
|
is_.psl_formula = false;
|
|
is_.eventual = false;
|
|
is_.universal = false;
|
|
is_.syntactic_safety = false;
|
|
is_.syntactic_guarantee = false;
|
|
is_.syntactic_obligation = false;
|
|
is_.syntactic_recurrence = false;
|
|
is_.syntactic_persistence = false;
|
|
is_.delta1 = false;
|
|
is_.pi2 = false;
|
|
is_.sigma2 = false;
|
|
is_.delta2 = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
const fnode*
|
|
fnode::nested_unop_range(op uo, op bo, unsigned min, unsigned max,
|
|
const fnode* f)
|
|
{
|
|
if (max < min)
|
|
std::swap(min, max);
|
|
if (SPOT_UNLIKELY(min >= unbounded()))
|
|
report_repetition_overflow(min);
|
|
// We are testing strict ">", because unbounded() is a legitimate
|
|
// input for max. We just cannot tell if it was really intented
|
|
// as "unbounded".
|
|
if (SPOT_UNLIKELY(max > unbounded()))
|
|
report_repetition_overflow(max);
|
|
const fnode* res = f;
|
|
if (max != unbounded())
|
|
for (unsigned i = min; i < max; ++i)
|
|
{
|
|
const fnode* a = f->clone();
|
|
res = fnode::multop(bo, {a, fnode::unop(uo, res)});
|
|
}
|
|
else
|
|
res = fnode::unop(bo == op::Or ? op::F : op::G, res);
|
|
for (unsigned i = 0; i < min; ++i)
|
|
res = fnode::unop(uo, res);
|
|
return res;
|
|
}
|
|
|
|
std::ostream& fnode::dump(std::ostream& os) const
|
|
{
|
|
os << kindstr() << "(@" << id_ << " #" << refs_;
|
|
if (op_ == op::Star || op_ == op::FStar)
|
|
{
|
|
os << ' ' << +min() << "..";
|
|
auto m = max();
|
|
if (m != unbounded())
|
|
os << +m;
|
|
}
|
|
if (op_ == op::ap)
|
|
os << " \"" << ap_name() << '"';
|
|
if (auto s = size())
|
|
{
|
|
os << " [";
|
|
for (auto c: *this)
|
|
{
|
|
c->dump(os);
|
|
if (--s)
|
|
os << ", ";
|
|
}
|
|
os << ']';
|
|
}
|
|
return os << ')';
|
|
}
|
|
|
|
const fnode* fnode::all_but(unsigned i) const
|
|
{
|
|
switch (op o = kind())
|
|
{
|
|
case op::Or:
|
|
case op::OrRat:
|
|
case op::And:
|
|
case op::AndRat:
|
|
case op::AndNLM:
|
|
case op::Concat:
|
|
case op::Fusion:
|
|
{
|
|
unsigned s = size();
|
|
assert(s > 1);
|
|
vec v;
|
|
v.reserve(s - 1);
|
|
for (unsigned j = 0; j < s; ++j)
|
|
if (i != j)
|
|
v.emplace_back(nth(j)->clone());
|
|
return multop(o, v);
|
|
}
|
|
default:
|
|
throw
|
|
std::runtime_error("all_but() is incompatible with this operator");
|
|
}
|
|
SPOT_UNREACHABLE();
|
|
}
|
|
|
|
const fnode* fnode::boolean_operands(unsigned* width) const
|
|
{
|
|
unsigned s = boolean_count();
|
|
if (width)
|
|
*width = s;
|
|
if (s == 0)
|
|
return nullptr;
|
|
if (s == 1)
|
|
return nth(0)->clone();
|
|
vec v(children, children + s);
|
|
for (auto c: v)
|
|
c->clone();
|
|
return multop(op_, v);
|
|
}
|
|
|
|
bool fnode::instances_check()
|
|
{
|
|
unsigned cnt = 0;
|
|
for (auto i: m.uniq)
|
|
if (!i->saturated_)
|
|
{
|
|
if (!cnt++)
|
|
std::cerr << "*** m.uniq is not empty ***\n";
|
|
i->dump(std::cerr) << '\n';
|
|
}
|
|
return cnt == 0;
|
|
}
|
|
|
|
formula formula::sugar_goto(const formula& b, unsigned min, unsigned max)
|
|
{
|
|
if (!b.is_boolean())
|
|
throw
|
|
std::runtime_error("sugar_goto() called with non-Boolean argument");
|
|
// b[->min..max] is implemented as ((!b)[*];b)[*min..max]
|
|
return Star(Concat({Star(Not(b)), b}), min, max);
|
|
}
|
|
|
|
formula formula::sugar_equal(const formula& b, unsigned min, unsigned max)
|
|
{
|
|
if (!b.is_boolean())
|
|
throw
|
|
std::runtime_error("sugar_equal() called with non-Boolean argument");
|
|
|
|
// b[=0..] = 1[*]
|
|
if (min == 0 && max == unbounded())
|
|
return one_star();
|
|
|
|
// b[=min..max] is implemented as ((!b)[*];b)[*min..max];(!b)[*]
|
|
formula s = Star(Not(b));
|
|
return Concat({Star(Concat({s, b}), min, max), s});
|
|
}
|
|
|
|
formula formula::sugar_delay(const formula& b,
|
|
unsigned min, unsigned max)
|
|
{
|
|
// ##[min:max] b = 1[*min:max];b
|
|
return Concat({Star(tt(), min, max), b});
|
|
}
|
|
formula formula::sugar_delay(const formula& a, const formula& b,
|
|
unsigned min, unsigned max)
|
|
{
|
|
// If min>=1
|
|
// a ##[min:max] b = a;1[*min-1:max-1];b
|
|
// If min==0 we can use
|
|
// a ##[0:0] b = a:b
|
|
// a ##[0:max] b = a:(1[*0:max];b) if a rejects [*0]
|
|
// a ##[0:max] b = (a;1[*0:max]):b if b rejects [*0]
|
|
// a ##[0:max] b = (a:b)|(a;[*0:max-1];b) else
|
|
if (min > 0)
|
|
{
|
|
--min;
|
|
if (max != unbounded())
|
|
--max;
|
|
return Concat({a, Star(tt(), min, max), b});
|
|
}
|
|
if (max == 0)
|
|
return Fusion({a, b});
|
|
if (!a.accepts_eword())
|
|
return Fusion({a, Concat({Star(tt(), 0, max), b})});
|
|
if (!b.accepts_eword())
|
|
return Fusion({Concat({a, Star(tt(), 0, max)}), b});
|
|
|
|
if (max != unbounded())
|
|
--max;
|
|
formula left = Fusion({a, b});
|
|
formula right = Concat({a, Star(tt(), 0, max), b});
|
|
return OrRat({left, right});
|
|
}
|
|
|
|
int atomic_prop_cmp(const fnode* f, const fnode* g)
|
|
{
|
|
return strverscmp(f->ap_name().c_str(), g->ap_name().c_str());
|
|
}
|
|
|
|
#define printprops \
|
|
proprint(is_boolean, "B", "Boolean formula"); \
|
|
proprint(is_sugar_free_boolean, "&", "without Boolean sugar"); \
|
|
proprint(is_in_nenoform, "!", "in negative normal form"); \
|
|
proprint(is_syntactic_stutter_invariant, "x", \
|
|
"syntactic stutter invariant"); \
|
|
proprint(is_sugar_free_ltl, "f", "without LTL sugar"); \
|
|
proprint(is_ltl_formula, "L", "LTL formula"); \
|
|
proprint(is_psl_formula, "P", "PSL formula"); \
|
|
proprint(is_sere_formula, "S", "SERE formula"); \
|
|
proprint(is_finite, "F", "finite"); \
|
|
proprint(is_eventual, "e", "pure eventuality"); \
|
|
proprint(is_universal, "u", "purely universal"); \
|
|
proprint(is_syntactic_safety, "s", "syntactic safety"); \
|
|
proprint(is_syntactic_guarantee, "g", "syntactic guarantee"); \
|
|
proprint(is_syntactic_obligation, "o", "syntactic obligation"); \
|
|
proprint(is_syntactic_persistence, "p", "syntactic persistence"); \
|
|
proprint(is_syntactic_recurrence, "r", "syntactic recurrence"); \
|
|
proprint(is_marked, "+", "marked"); \
|
|
proprint(accepts_eword, "0", "accepts the empty word"); \
|
|
proprint(has_lbt_atomic_props, "l", \
|
|
"has LBT-style atomic props"); \
|
|
proprint(has_spin_atomic_props, "a", \
|
|
"has Spin-style atomic props"); \
|
|
proprint(is_delta1, "O", "delta1"); \
|
|
proprint(is_sigma2, "P", "sigma2"); \
|
|
proprint(is_pi2, "R", "pi2"); \
|
|
proprint(is_delta2, "D", "delta2");
|
|
|
|
// O (for Δ₁), P (for Σ₂), R (for Π₂) are the uppercase versions of
|
|
// o (obligation), p (persistence), r (recurrence) because they are
|
|
// stricter subsets of those.
|
|
|
|
std::list<std::string>
|
|
list_formula_props(const formula& f)
|
|
{
|
|
std::list<std::string> res;
|
|
#define proprint(m, a, l) \
|
|
if (f.m()) \
|
|
res.emplace_back(l);
|
|
printprops;
|
|
#undef proprint
|
|
return res;
|
|
}
|
|
|
|
std::ostream&
|
|
print_formula_props(std::ostream& out, const formula& f, bool abbr)
|
|
{
|
|
const char* comma = abbr ? "" : ", ";
|
|
const char* sep = "";
|
|
|
|
#define proprint(m, a, l) \
|
|
if (f.m()) \
|
|
{ \
|
|
out << sep; out << (abbr ? a : l); \
|
|
sep = comma; \
|
|
}
|
|
printprops;
|
|
#undef proprint
|
|
|
|
return out;
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& os, const formula& f)
|
|
{
|
|
return print_psl(os, f);
|
|
}
|
|
|
|
bool
|
|
formula_ptr_less_than_bool_first::operator()(const formula& left,
|
|
const formula& right) const
|
|
{
|
|
return operator()(left.ptr_, right.ptr_);
|
|
}
|
|
}
|