ltlfilt, ltlsynt, ltlmix: add a --part-file option

* bin/common_ioap.cc, bin/common_ioap.hh (read_part_file): New
function.
* bin/ltlfilt.cc, bin/ltlmix.cc, bin/ltlsynt.cc: Use it.
* doc/org/ltlfilt.org, doc/org/ltlmix.org, doc/org/ltlsynt.org:
Mention that new option, and improve the links to its description
in ltlsynt.org.
* NEWS: Mention the new option.
* tests/core/ltlfilt.test, tests/core/ltlmix.test,
tests/core/ltlsynt.test: Adjust test cases.
This commit is contained in:
Alexandre Duret-Lutz 2024-09-03 14:20:17 +02:00
parent 388d005635
commit e6ebbdf65f
12 changed files with 271 additions and 138 deletions

View file

@ -18,6 +18,7 @@
#include "common_ioap.hh"
#include "error.h"
#include <fstream>
#include <unordered_set>
// --ins and --outs, as supplied on the command-line
@ -31,6 +32,8 @@ std::vector<std::regex> regex_out;
// map identifier to input/output (false=input, true=output)
std::unordered_map<std::string, bool> identifier_map;
static bool a_part_file_was_read = false;
static std::string
str_tolower(std::string s)
{
@ -71,7 +74,10 @@ void process_io_options()
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",
error(2, 0,
a_part_file_was_read ?
"'%s' appears in both inputs and outputs" :
"'%s' appears in both --ins and --outs",
f.c_str());
}
}
@ -125,19 +131,23 @@ is_output(const std::string& a, const char* filename, int linenum)
}
if (found_in && found_out)
error_at_line(2, 0, filename, linenum,
a_part_file_was_read ?
"'%s' matches both inputs and outputs" :
"'%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,
a_part_file_was_read ?
"'%s' does not match any input or output" :
"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",
"an output;\n use --ins, --outs, or --part-file",
a.c_str());
}
}
@ -200,3 +210,59 @@ spot::formula relabel_io(spot::formula f, spot::relabeling_map& fro,
}
return spot::relabel_apply(f, &to);
}
// Read FILENAME as a ".part" file. It should
// contains lines of text of the following form:
//
// .inputs IN1 IN2 IN3...
// .outputs OUT1 OUT2 OUT3...
void read_part_file(const char* filename)
{
std::ifstream in(filename);
if (!in)
error(2, errno, "cannot open '%s'", filename);
// This parsing is inspired from Lily's parser for .part files. We
// read words one by one, and change the "mode" if we the word is
// ".inputs" or ".outputs". A '#' introduce a comment until the end
// of the line.
std::string word;
enum { Unknown, Input, Output } mode = Unknown;
while (in >> word)
{
// The benchmarks for Syft use ".inputs:" instead of ".inputs".
if (word == ".inputs" || word == ".inputs:")
{
mode = Input;
if (!all_input_aps.has_value())
all_input_aps.emplace();
}
// The benchmarks for Syft use ".outputs:" instead of ".outputs".
else if (word == ".outputs" || word == ".outputs:")
{
mode = Output;
if (!all_output_aps.has_value())
all_output_aps.emplace();
}
else if (word[0] == '#')
{
// Skip the rest of the line.
in.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
else if (mode == Unknown)
{
error_at_line(2, 0, filename, 0,
"expected '.inputs' or '.outputs' instead of '%s'",
word.c_str());
}
else if (mode == Input)
{
all_input_aps->push_back(str_tolower(word));
}
else /* mode == Output */
{
all_output_aps->push_back(str_tolower(word));
}
}
a_part_file_was_read = true;
}

View file

@ -64,3 +64,6 @@ filter_list_of_aps(spot::formula f, const char* filename, int linenum);
// Relabel APs incrementally, based on i/o class.
spot::formula relabel_io(spot::formula f, spot::relabeling_map& fro,
const char* filename, int linenum);
// Read a .part file.
void read_part_file(const char* filename);

View file

@ -120,6 +120,7 @@ enum {
OPT_SYNTACTIC_SI,
OPT_TO_DELTA2,
OPT_OUTS,
OPT_PART_FILE,
OPT_UNABBREVIATE,
OPT_UNIVERSAL,
};
@ -187,6 +188,9 @@ static const argp_option options[] =
{ "outs", OPT_OUTS, "PROPS", 0,
"comma-separated list of output atomic propositions to use with "
"--relabel=io, interpreted as a regex if enclosed in slashes", 0 },
{ "part-file", OPT_PART_FILE, "FILENAME", 0,
"file containing the partition of atomic propositions to use with "
"--relabel=io", 0 },
DECLARE_OPT_R,
LEVEL_DOC(4),
/**************************************************/
@ -516,11 +520,9 @@ parse_opt(int key, char* arg, struct argp_state*)
break;
}
case OPT_INS:
{
all_input_aps.emplace(std::vector<std::string>{});
split_aps(arg, *all_input_aps);
break;
}
all_input_aps.emplace();
split_aps(arg, *all_input_aps);
break;
case OPT_LIVENESS:
liveness = true;
break;
@ -537,11 +539,12 @@ parse_opt(int key, char* arg, struct argp_state*)
nnf = true;
break;
case OPT_OUTS:
{
all_output_aps.emplace(std::vector<std::string>{});
split_aps(arg, *all_output_aps);
break;
}
all_output_aps.emplace();
split_aps(arg, *all_output_aps);
break;
case OPT_PART_FILE:
read_part_file(arg);
break;
case OPT_SONF:
sonf = arg ? arg : "sonf_";
break;

View file

@ -40,6 +40,7 @@ enum {
OPT_INS,
OPT_LTL_PRIORITIES,
OPT_OUTS,
OPT_PART_FILE,
OPT_SEED,
OPT_TREE_SIZE,
};
@ -102,8 +103,10 @@ static const argp_option options[] = {
"comma-separated list of atomic propositions to consider as input, "
"interpreted as a regex if enclosed in slashes", 0 },
{ "outs", OPT_OUTS, "PROPS", 0,
"comma-separated list of atomic propositions to consider as putput, "
"comma-separated list of atomic propositions to consider as output, "
"interpreted as a regex if enclosed in slashes", 0 },
{ "part-file", OPT_PART_FILE, "FILENAME", 0,
"read the I/O partition of atomic propositions from FILENAME", 0 },
RANGE_DOC,
/**************************************************/
{ nullptr, 0, nullptr, 0, "Adjusting probabilities:", 4 },
@ -253,19 +256,19 @@ parse_opt(int key, char* arg, struct argp_state*)
opt_unique = false;
break;
case OPT_INS:
{
all_input_aps.emplace(std::vector<std::string>{});
split_aps(arg, *all_input_aps);
opt_io = true;
break;
}
all_input_aps.emplace();
split_aps(arg, *all_input_aps);
opt_io = true;
break;
case OPT_OUTS:
{
all_output_aps.emplace(std::vector<std::string>{});
split_aps(arg, *all_output_aps);
opt_io = true;
break;
}
all_output_aps.emplace();
split_aps(arg, *all_output_aps);
opt_io = true;
break;
case OPT_PART_FILE:
read_part_file(arg);
opt_io = true;
break;
case OPT_SEED:
opt_seed = to_int(arg, "--seed");
break;
@ -303,7 +306,7 @@ main(int argc, char* argv[])
if (opt_io && !opt_out_ap_count)
error(2, 0,
"options --ins and --outs only make sense when the "
"options --ins, --outs, --part-file only make sense when the "
"two-argument version of '-A N,M' or '-P N,M' is used.");
if (opt_out_ap_count > 0)
// Do not require --ins/--outs to be used, as the input

View file

@ -57,6 +57,7 @@ enum
OPT_HIDE,
OPT_INPUT,
OPT_OUTPUT,
OPT_PART_FILE,
OPT_POLARITY,
OPT_PRINT,
OPT_PRINT_AIGER,
@ -79,6 +80,8 @@ static const argp_option options[] =
{ "ins", OPT_INPUT, "PROPS", 0,
"comma-separated list of uncontrollable (a.k.a. input) atomic"
" propositions, interpreted as a regex if enclosed in slashes", 0 },
{ "part-file", OPT_PART_FILE, "FILENAME", 0,
"read the I/O partition of atomic propositions from FILENAME", 0 },
{ "tlsf", OPT_TLSF, "FILENAME", 0,
"Read a TLSF specification from FILENAME, and call syfco to "
"convert it into LTL", 0 },
@ -993,17 +996,16 @@ parse_opt(int key, char *arg, struct argp_state *)
show_status = false;
break;
case OPT_INPUT:
{
all_input_aps.emplace(std::vector<std::string>{});
split_aps(arg, *all_input_aps);
break;
}
all_input_aps.emplace();
split_aps(arg, *all_input_aps);
break;
case OPT_OUTPUT:
{
all_output_aps.emplace(std::vector<std::string>{});
split_aps(arg, *all_output_aps);
break;
}
all_output_aps.emplace();
split_aps(arg, *all_output_aps);
break;
case OPT_PART_FILE:
read_part_file(arg);
break;
case OPT_POLARITY:
opt_polarity = XARGMATCH("--polarity", arg,
polarity_args, polarity_values);