// -*- 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 .
#include "common_ioap.hh"
#include "error.h"
#include
// --ins and --outs, as supplied on the command-line
std::optional> all_output_aps;
std::optional> all_input_aps;
// Store refirst, separate the filters that are regular expressions from
// the others. Compile the regular expressions while we are at it.
std::vector regex_in;
std::vector regex_out;
// map identifier to input/output (false=input, true=output)
std::unordered_map identifier_map;
static std::string
str_tolower(std::string s)
{
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c){ return std::tolower(c); });
return s;
}
void
split_aps(const std::string& arg, std::vector& where)
{
std::istringstream aps(arg);
std::string ap;
while (std::getline(aps, ap, ','))
{
ap.erase(remove_if(ap.begin(), ap.end(), isspace), ap.end());
where.push_back(str_tolower(ap));
}
}
void process_io_options()
{
// Filter identifiers from regexes.
if (all_input_aps.has_value())
for (const std::string& f: *all_input_aps)
{
unsigned sz = f.size();
if (f[0] == '/' && f[sz - 1] == '/')
regex_in.push_back(std::regex(f.substr(1, sz - 2)));
else
identifier_map.emplace(f, false);
}
if (all_output_aps.has_value())
for (const std::string& f: *all_output_aps)
{
unsigned sz = f.size();
if (f[0] == '/' && f[sz - 1] == '/')
regex_out.push_back(std::regex(f.substr(1, sz - 2)));
else if (auto [it, is_new] = identifier_map.try_emplace(f, true);
!is_new && !it->second)
error(2, 0, "'%s' appears in both --ins and --outs",
f.c_str());
}
}
static std::unordered_set
list_aps_in_formula(spot::formula f)
{
std::unordered_set aps;
f.traverse([&aps](spot::formula s) {
if (s.is(spot::op::ap))
aps.emplace(s.ap_name());
return false;
});
return aps;
}
// Takes a set of the atomic propositions appearing in the formula,
// and separate them into two vectors: input APs and output APs.
std::pair, std::vector>
filter_list_of_aps(spot::formula f, const char* filename, int linenum)
{
std::unordered_set aps = list_aps_in_formula(f);
// now iterate over the list of atomic propositions to filter them
std::vector matched[2]; // 0 = input, 1 = output
for (const std::string& a: aps)
{
if (auto it = identifier_map.find(a); it != identifier_map.end())
{
matched[it->second].push_back(a);
continue;
}
bool found_in = false;
for (const std::regex& r: regex_in)
if (std::regex_search(a, r))
{
found_in = true;
break;
}
bool found_out = false;
for (const std::regex& r: regex_out)
if (std::regex_search(a, r))
{
found_out = true;
break;
}
if (all_input_aps.has_value() == all_output_aps.has_value())
{
if (!all_input_aps.has_value())
{
// If the atomic proposition hasn't been classified
// because neither --ins nor --out were specified,
// attempt to classify automatically using the first
// letter.
int fl = a[0];
if (fl == 'i' || fl == 'I')
found_in = true;
else if (fl == 'o' || fl == 'O')
found_out = true;
}
if (found_in && found_out)
error_at_line(2, 0, filename, linenum,
"'%s' matches both --ins and --outs",
a.c_str());
if (!found_in && !found_out)
{
if (all_input_aps.has_value() || all_output_aps.has_value())
error_at_line(2, 0, filename, linenum,
"one of --ins or --outs should match '%s'",
a.c_str());
else
error_at_line(2, 0, filename, linenum,
"since '%s' does not start with 'i' or 'o', "
"it is unclear if it is an input or "
"an output;\n use --ins or --outs",
a.c_str());
}
}
else
{
// if we had only --ins or only --outs, anything not
// matching that was given is assumed to belong to the
// other one.
if (!all_input_aps.has_value() && !found_out)
found_in = true;
else if (!all_output_aps.has_value() && !found_in)
found_out = true;
}
matched[found_out].push_back(a);
}
return {matched[0], matched[1]};
}
spot::formula relabel_io(spot::formula f, spot::relabeling_map& fro,
const char* filename, int linenum)
{
auto [ins, outs] = filter_list_of_aps(f, filename, linenum);
// Different implementation of unordered_set, usinged in
// filter_list_of_aps can cause aps to be output in different order.
// Let's sort everything for the sake of determinism.
std::sort(ins.begin(), ins.end());
std::sort(outs.begin(), outs.end());
spot::relabeling_map to;
unsigned ni = 0;
for (std::string& i: ins)
{
std::ostringstream s;
s << 'i' << ni++;
spot::formula a1 = spot::formula::ap(i);
spot::formula a2 = spot::formula::ap(s.str());
fro[a2] = a1;
to[a1] = a2;
}
unsigned no = 0;
for (std::string& o: outs)
{
std::ostringstream s;
s << 'o' << no++;
spot::formula a1 = spot::formula::ap(o);
spot::formula a2 = spot::formula::ap(s.str());
fro[a2] = a1;
to[a1] = a2;
}
return spot::relabel_apply(f, &to);
}