spot/src/ltlast/bunop.cc
Alexandre Duret-Lutz 34f1601b9b ltl: rename is_X_free() into is_syntactic_stutter_invariant()
and adjust it to detect siPSL formulas, as in the paper of Dax et
al. (ATVA'09).  For issue #51.

* src/ltlast/atomic_prop.cc, src/ltlast/binop.cc,
src/ltlast/bunop.cc, src/ltlast/constant.cc, src/ltlast/formula.cc,
src/ltlast/formula.hh, src/ltlast/multop.cc, src/ltlast/unop.cc: Rename
the property, and adjust its computation on siSERE.
* src/ltlvisit/remove_x.cc, src/ltlvisit/simplify.cc,
src/tgbaalgos/stutter.cc: Adjust to new names.
* src/bin/ltlfilt.cc: Add option --syntactic-sutter-invariant.
* src/ltltest/kind.test: Update tests and add some new.
2015-01-19 14:39:41 +01:00

355 lines
8.1 KiB
C++

// -*- coding: utf-8 -*-
// Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Laboratoire de
// Recherche et Développement de l'Epita (LRDE).
//
// 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 "bunop.hh"
#include "visitor.hh"
#include <cassert>
#include <iostream>
#include <sstream>
#include "constant.hh"
#include "unop.hh"
#include "multop.hh"
namespace spot
{
namespace ltl
{
// Can't build it on startup, because it uses
// constant::true_instance that may not have been built yet...
const formula* bunop::one_star_ = 0;
bunop::bunop(type op, const formula* child, unsigned min, unsigned max)
: formula(BUnOp), op_(op), child_(child), min_(min), max_(max)
{
props = child->get_props();
assert(is.sere_formula);
is.boolean = false;
is.ltl_formula = false;
is.eltl_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;
switch (op_)
{
case Star:
if (max_ == unbounded)
{
is.finite = false;
is.syntactic_si = min_ == 1 && child->is_boolean();
}
else
{
is.syntactic_si = false;
}
if (min_ == 0)
is.accepting_eword = true;
break;
case FStar:
is.accepting_eword = false;
if (max_ == unbounded)
{
is.finite = false;
is.syntactic_si &= !child->is_boolean();
}
else
{
is.syntactic_si = false;
}
break;
}
}
bunop::~bunop()
{
// one_star_ should never get deleted. Otherwise, that means it
// has been destroyed too much, or not cloned enough.
assert(this != one_star_);
// Get this instance out of the instance map.
size_t c = instances.erase(key(op(), child(), min_, max_));
assert(c == 1);
(void) c; // For the NDEBUG case.
// Dereference child.
child()->destroy();
}
std::string
bunop::dump() const
{
std::ostringstream out;
out << "bunop(" << op_name() << ", "
<< child()->dump() << ", " << min_ << ", ";
if (max_ == unbounded)
out << "unbounded";
else
out << max_;
out << ')';
return out.str();
}
void
bunop::accept(visitor& v) const
{
v.visit(this);
}
const char*
bunop::op_name() const
{
switch (op_)
{
case Star:
return "Star";
case FStar:
return "FStar";
}
SPOT_UNREACHABLE();
}
std::string
bunop::format() const
{
std::ostringstream out;
switch (op_)
{
case Star:
// Syntactic sugaring
if (min_ == 1 && max_ == unbounded)
return "[+]";
out << "[*";
break;
case FStar:
// Syntactic sugaring
if (min_ == 1 && max_ == unbounded)
return "[:+]";
out << "[:*";
break;
}
if (min_ != 0 || max_ != unbounded)
{
// Always print the min_, even when it is equal to 0, this
// way we avoid ambiguities (like when reading
// a[*..3];b[->..2] which actually means a[*0..3];b[->1..2].
out << min_;
if (min_ != max_)
{
out << "..";
if (max_ != unbounded)
out << max_;
}
}
out << ']';
return out.str();
}
bunop::map bunop::instances;
const formula*
bunop::instance(type op, const formula* child,
unsigned min, unsigned max)
{
assert(min <= max);
const formula* neutral = nullptr;
switch (op)
{
case Star:
neutral = constant::empty_word_instance();
break;
case FStar:
neutral = constant::true_instance();
break;
}
// common trivial simplifications
// - [*0][*min..max] = [*0]
// - [*0][:*0..max] = 1
// - [*0][:*min..max] = 0 if min > 0
if (child == constant::empty_word_instance())
switch (op)
{
case Star:
return neutral;
case FStar:
if (min == 0)
return neutral;
else
return constant::false_instance();
}
// - 0[*0..max] = [*0]
// - 0[*min..max] = 0 if min > 0
// - b[:*0..max] = 1
// - b[:*min..max] = 0 if min > 0
if (child == constant::false_instance()
|| (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 (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 (const bunop* s = is_bunop(child, op))
{
unsigned i = s->min();
unsigned j = s->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 formula* exp = s->child();
if (j == unbounded)
{
min *= i;
max = unbounded;
// Exp[*min..max]
exp->clone();
child->destroy();
child = exp;
}
else
{
if (i * (min + 1) <= (j * min) + 1)
{
min *= i;
if (max != unbounded)
{
if (j == unbounded)
max = unbounded;
else
max *= j;
}
exp->clone();
child->destroy();
child = exp;
}
}
}
const formula* res;
auto ires = instances.emplace(key(op, child, min, max), nullptr);
if (!ires.second)
{
// This instance already exists.
child->destroy();
res = ires.first->second->clone();
}
else
{
res = ires.first->second = new bunop(op, child, min, max);
}
return res;
}
const formula*
bunop::sugar_goto(const formula* b, unsigned min, unsigned max)
{
assert(b->is_boolean());
// b[->min..max] is implemented as ((!b)[*];b)[*min..max]
const formula* s =
bunop::instance(bunop::Star,
unop::instance(unop::Not, b->clone()));
return bunop::instance(bunop::Star,
multop::instance(multop::Concat, s, b),
min, max);
}
const formula*
bunop::sugar_equal(const formula* b, unsigned min, unsigned max)
{
assert(b->is_boolean());
// b[=0..] = 1[*]
if (min == 0 && max == unbounded)
{
b->destroy();
return instance(Star, constant::true_instance());
}
// b[=min..max] is implemented as ((!b)[*];b)[*min..max];(!b)[*]
const formula* s =
bunop::instance(bunop::Star,
unop::instance(unop::Not, b->clone()));
const formula* t =
bunop::instance(bunop::Star,
multop::instance(multop::Concat,
s->clone(), b), min, max);
return multop::instance(multop::Concat, t, s);
}
unsigned
bunop::instance_count()
{
// Don't count one_star_ since it should not be destroyed.
return instances.size() - !!one_star_;
}
std::ostream&
bunop::dump_instances(std::ostream& os)
{
for (const auto& i: instances)
os << i.second << " = "
<< 1 + i.second->refs_ << " * "
<< i.second->dump()
<< '\n';
return os;
}
}
}