autfilt, dstar2tgba: add CSV input

Fixes #91.

* bin/autfilt.cc, bin/dstar2tgba.cc: Implement reading CSV files.
* bin/common_finput.cc: Fix comments.
* bin/common_aoutput.cc: Show %<, %> in help text.
* NEWS, doc/org/csv.org: Document it.
* tests/core/readsave.test: Add a short test case.
This commit is contained in:
Alexandre Duret-Lutz 2016-08-08 12:21:34 +02:00
parent f423c424eb
commit ca0d81b5d7
7 changed files with 229 additions and 51 deletions

6
NEWS
View file

@ -109,9 +109,13 @@ New in spot 2.0.3a (not yet released)
* The --stats option of autfilt, dstar2tgba, ltl2tgba, ltldo, * The --stats option of autfilt, dstar2tgba, ltl2tgba, ltldo,
randaut learned to display the output (or input if that makes randaut learned to display the output (or input if that makes
sense) automaton as a HOA one-liner using %H (or %h), helping to sense) automaton as a HOA one-liner using %h (or %H), helping to
create CSV files contained automata. create CSV files contained automata.
* autfilt and dstar2tgba learned to read automata from columns in
CSV files, specified using the same filename/COLUMN syntax used by
tools reading formulas.
* Arguments passed to -x (in ltl2tgba, ltl2tgta, autfilt, dstar2tgba) * Arguments passed to -x (in ltl2tgba, ltl2tgta, autfilt, dstar2tgba)
that are not used are now reported as they might be typos. that are not used are now reported as they might be typos.
This ocurred a couple of times in our test-suite. A similar This ocurred a couple of times in our test-suite. A similar

View file

@ -24,6 +24,8 @@
#include <limits> #include <limits>
#include <set> #include <set>
#include <memory> #include <memory>
#include <sys/stat.h>
#include <unistd.h>
#include <argp.h> #include <argp.h>
#include "error.h" #include "error.h"
@ -874,6 +876,90 @@ namespace
SPOT_UNREACHABLE(); SPOT_UNREACHABLE();
} }
int process_string(const std::string& input, const char* filename,
int linenum) override
{
std::ostringstream loc;
loc << filename << ':' << linenum;
std::string locstr = loc.str();
return process_automaton_stream
(spot::automaton_stream_parser(input.c_str(), locstr, opt_parse),
locstr.c_str());
}
int
aborted(const spot::const_parsed_aut_ptr& h, const char* filename)
{
std::cerr << filename << ':' << h->loc << ": aborted input automaton\n";
return 2;
}
int
process_file(const char* filename) override
{
// If we have a filename like "foo/NN" such
// that:
// ① foo/NN is not a file,
// ② NN is a number,
// ③ foo is a file,
// then it means we want to open foo as
// a CSV file and process column NN.
if (const char* slash = strrchr(filename, '/'))
{
char* end;
errno = 0;
long int col = strtol(slash + 1, &end, 10);
if (errno == 0 && !*end && col != 0)
{
struct stat buf;
if (stat(filename, &buf) != 0)
{
col_to_read = col;
if (real_filename)
free(real_filename);
real_filename = strndup(filename, slash - filename);
// Special case for stdin.
if (real_filename[0] == '-' && real_filename[1] == 0)
return process_stream(std::cin, real_filename);
std::ifstream input(real_filename);
if (input)
return process_stream(input, real_filename);
error(2, errno, "cannot open '%s' nor '%s'",
filename, real_filename);
}
}
}
return process_automaton_stream(spot::automaton_stream_parser(filename,
opt_parse),
filename);
}
int process_automaton_stream(spot::automaton_stream_parser&& hp,
const char* filename)
{
int err = 0;
while (!abort_run)
{
auto haut = hp.parse(opt->dict);
if (!haut->aut && haut->errors.empty())
break;
if (haut->format_errors(std::cerr))
err = 2;
if (!haut->aut)
error(2, 0, "failed to read automaton from %s", filename);
else if (haut->aborted)
err = std::max(err, aborted(haut, filename));
else
process_automaton(haut, filename);
}
return err;
}
int int
process_automaton(const spot::const_parsed_aut_ptr& haut, process_automaton(const spot::const_parsed_aut_ptr& haut,
const char* filename) const char* filename)
@ -1111,42 +1197,14 @@ namespace
++match_count; ++match_count;
printer.print(aut, nullptr, filename, -1, conversion_time, haut); printer.print(aut, nullptr, filename, -1, conversion_time, haut,
prefix, suffix);
if (opt_max_count >= 0 && match_count >= opt_max_count) if (opt_max_count >= 0 && match_count >= opt_max_count)
abort_run = true; abort_run = true;
return 0; return 0;
} }
int
aborted(const spot::const_parsed_aut_ptr& h, const char* filename)
{
std::cerr << filename << ':' << h->loc << ": aborted input automaton\n";
return 2;
}
int
process_file(const char* filename) override
{
auto hp = spot::automaton_stream_parser(filename, opt_parse);
int err = 0;
while (!abort_run)
{
auto haut = hp.parse(opt->dict);
if (!haut->aut && haut->errors.empty())
break;
if (haut->format_errors(std::cerr))
err = 2;
if (!haut->aut)
error(2, 0, "failed to read automaton from %s", filename);
else if (haut->aborted)
err = std::max(err, aborted(haut, filename));
else
process_automaton(haut, filename);
}
return err;
}
}; };
} }
@ -1155,7 +1213,7 @@ main(int argc, char** argv)
{ {
setup(argv); setup(argv);
const argp ap = { options, parse_opt, "[FILENAMES...]", const argp ap = { options, parse_opt, "[FILENAME[/COL]...]",
argp_program_doc, children, nullptr, nullptr }; argp_program_doc, children, nullptr, nullptr };
try try

View file

@ -181,6 +181,12 @@ static const argp_option io_options[] =
"one word accepted by the output automaton", 0 }, "one word accepted by the output automaton", 0 },
{ "%%", 0, nullptr, OPTION_DOC | OPTION_NO_USAGE, { "%%", 0, nullptr, OPTION_DOC | OPTION_NO_USAGE,
"a single %", 0 }, "a single %", 0 },
{ "%<", 0, nullptr, OPTION_DOC | OPTION_NO_USAGE,
"the part of the line before the automaton if it "
"comes from a column extracted from a CSV file", 4 },
{ "%>", 0, nullptr, OPTION_DOC | OPTION_NO_USAGE,
"the part of the line after the automaton if it "
"comes from a column extracted from a CSV file", 4 },
{ nullptr, 0, nullptr, 0, nullptr, 0 } { nullptr, 0, nullptr, 0, nullptr, 0 }
}; };

View file

@ -305,7 +305,7 @@ job_processor::process_file(const char* filename)
// If we have a filename like "foo/NN" such // If we have a filename like "foo/NN" such
// that: // that:
// ① foo/NN is not a file (already the case), // ① foo/NN is not a file (already the case),
// ② NN is a number > 0, // ② NN is a number,
// ③ foo is a file, // ③ foo is a file,
// then it means we want to open foo as // then it means we want to open foo as
// a CSV file and process column NN. // a CSV file and process column NN.
@ -315,7 +315,6 @@ job_processor::process_file(const char* filename)
char* end; char* end;
errno = 0; errno = 0;
long int col = strtol(slash + 1, &end, 10); long int col = strtol(slash + 1, &end, 10);
// strtol ate all remaining characters and NN is positive
if (errno == 0 && !*end && col != 0) if (errno == 0 && !*end && col != 0)
{ {
col_to_read = col; col_to_read = col;

View file

@ -22,6 +22,8 @@
#include <string> #include <string>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <sys/stat.h>
#include <unistd.h>
#include <argp.h> #include <argp.h>
#include "error.h" #include "error.h"
@ -123,19 +125,15 @@ namespace
SPOT_UNREACHABLE(); SPOT_UNREACHABLE();
} }
int int process_string(const std::string& input, const char* filename,
process_automaton(const spot::const_parsed_aut_ptr& haut, int linenum) override
const char* filename)
{ {
spot::stopwatch sw; std::ostringstream loc;
sw.start(); loc << filename << ':' << linenum;
auto nba = spot::to_generalized_buchi(haut->aut); std::string locstr = loc.str();
auto aut = post.run(nba, nullptr); return process_automaton_stream
const double conversion_time = sw.stop(); (spot::automaton_stream_parser(input.c_str(), locstr, opt_parse),
locstr.c_str());
printer.print(aut, nullptr, filename, -1, conversion_time, haut);
flush_cout();
return 0;
} }
int int
@ -148,7 +146,50 @@ namespace
int int
process_file(const char* filename) override process_file(const char* filename) override
{ {
auto hp = spot::automaton_stream_parser(filename, opt_parse); // If we have a filename like "foo/NN" such
// that:
// ① foo/NN is not a file,
// ② NN is a number,
// ③ foo is a file,
// then it means we want to open foo as
// a CSV file and process column NN.
if (const char* slash = strrchr(filename, '/'))
{
char* end;
errno = 0;
long int col = strtol(slash + 1, &end, 10);
if (errno == 0 && !*end && col != 0)
{
struct stat buf;
if (stat(filename, &buf) != 0)
{
col_to_read = col;
if (real_filename)
free(real_filename);
real_filename = strndup(filename, slash - filename);
// Special case for stdin.
if (real_filename[0] == '-' && real_filename[1] == 0)
return process_stream(std::cin, real_filename);
std::ifstream input(real_filename);
if (input)
return process_stream(input, real_filename);
error(2, errno, "cannot open '%s' nor '%s'",
filename, real_filename);
}
}
}
return process_automaton_stream(spot::automaton_stream_parser(filename,
opt_parse),
filename);
}
int process_automaton_stream(spot::automaton_stream_parser&& hp,
const char* filename)
{
int err = 0; int err = 0;
while (!abort_run) while (!abort_run)
{ {
@ -166,6 +207,22 @@ namespace
} }
return err; return err;
} }
int
process_automaton(const spot::const_parsed_aut_ptr& haut,
const char* filename)
{
spot::stopwatch sw;
sw.start();
auto nba = spot::to_generalized_buchi(haut->aut);
auto aut = post.run(nba, nullptr);
const double conversion_time = sw.stop();
printer.print(aut, nullptr, filename, -1, conversion_time, haut);
flush_cout();
return 0;
}
}; };
} }
@ -174,7 +231,7 @@ main(int argc, char** argv)
{ {
setup(argv); setup(argv);
const argp ap = { options, parse_opt, "[FILENAMES...]", const argp ap = { options, parse_opt, "[FILENAME[/COL]...]",
argp_program_doc, children, nullptr, nullptr }; argp_program_doc, children, nullptr, nullptr };
if (int err = argp_parse(&ap, argc, argv, ARGP_NO_HELP, nullptr, nullptr)) if (int err = argp_parse(&ap, argc, argv, ARGP_NO_HELP, nullptr, nullptr))

View file

@ -9,8 +9,8 @@ tools to produce an consume CSV files.
* Producing CSV files * Producing CSV files
All the tools that normally produce formulas (like [[file:genltl.org][=genltl=]], [[file:randltl.org][=randltl=]], All the tools that normally produce formulas (like [[file:genltl.org][=genltl=]],
and [[file:ltlfilt.org][=ltlfilt=]]) have a [[file:ioltl.org][=--format= option]]. That can be used to [[file:randltl.org][=randltl=]], and [[file:ltlfilt.org][=ltlfilt=]]) have a [[file:ioltl.org][=--format= option]] that can be used to
customize the way output is formatted. customize the way output is formatted.
For instance here is how we could use =genltl= to generate a CSV file For instance here is how we could use =genltl= to generate a CSV file
@ -38,7 +38,8 @@ u-left,5,(((p1 U p2) U p3) U p4) U p5
Tools that produce automata (like [[file:ltl2tgba.org][=ltl2tgba=]], [[file:dstar2tgba.org][=dstar2tgba=]], [[file:autfilt.org][=autfilt=]], Tools that produce automata (like [[file:ltl2tgba.org][=ltl2tgba=]], [[file:dstar2tgba.org][=dstar2tgba=]], [[file:autfilt.org][=autfilt=]],
or [[file:randaut.org][=randaut=]]) have a =--stats= option that can be used to output or [[file:randaut.org][=randaut=]]) have a =--stats= option that can be used to output
various statistics about the constructed automaton (these statistics various statistics about the constructed automaton (these statistics
are shown *instead* of printing the automaton). are shown *instead* of printing the automaton, but one of those
allows printing the automaton as a one-liner in the HOA format).
For instance, the following command will translate all the previous For instance, the following command will translate all the previous
formulas, and show the resulting number of states (=%s=) and edges formulas, and show the resulting number of states (=%s=) and edges
@ -231,3 +232,51 @@ ltlfilt -F results.csv/-1 --format='%f' --unique
: 0 : 0
: !G(Fb | F!(b | Gb)) : !G(Fb | F!(b | Gb))
: G(Fb | F!(b | Gb)) : G(Fb | F!(b | Gb))
* CSV files containing automata
The =--stats= option of tools that generate automata can be used to
generate CSV files that embed automata. For instance
#+BEGIN_SRC sh :results verbatim :exports both
genltl --dac=1..3 | ltl2tgba --stats='"%f","%e edges","%h"' > csv-aut.csv
cat csv-aut.csv
#+END_SRC
#+RESULTS:
: "G!p0","1 edges","HOA: v1 name: ""G!p0"" States: 1 Start: 0 AP: 1 ""p0"" acc-name: Buchi Acceptance: 1 Inf(0) properties: trans-labels explicit-labels state-acc colored deterministic stutter-invariant weak --BODY-- State: 0 {0} [!0] 0 --END--"
: "G!p0 | (!p1 U p0)","5 edges","HOA: v1 name: ""G!p0 | (!p1 U p0)"" States: 3 Start: 2 AP: 2 ""p0"" ""p1"" acc-name: Buchi Acceptance: 1 Inf(0) properties: trans-labels explicit-labels state-acc colored deterministic stutter-invariant weak --BODY-- State: 0 {0} [!0] 0 State: 1 {0} [t] 1 State: 2 {0} [!0&1] 0 [0] 1 [!0&!1] 2 --END--"
: "G(!p0 | G!p1)","3 edges","HOA: v1 name: ""G(!p0 | G!p1)"" States: 2 Start: 1 AP: 2 ""p0"" ""p1"" acc-name: Buchi Acceptance: 1 Inf(0) properties: trans-labels explicit-labels state-acc colored deterministic stutter-invariant weak --BODY-- State: 0 {0} [!1] 0 State: 1 {0} [0&!1] 0 [!0] 1 --END--"
[[file:autfilt.org][=autfilt=]] can process a column of such a CSV file using the same
syntax we used previously, but by default it will just output the
automata. If you want to preserve the entire line, you should use
=%<= and =%>= in the =--stats= format.
#+BEGIN_SRC sh :results verbatim :exports both
autfilt csv-aut.csv/3 --states=2..3 --stats='%<,"%h"'
#+END_SRC
#+RESULTS:
: "G!p0 | (!p1 U p0)","5 edges","HOA: v1 States: 3 Start: 2 AP: 2 ""p0"" ""p1"" acc-name: Buchi Acceptance: 1 Inf(0) properties: trans-labels explicit-labels state-acc colored deterministic stutter-invariant weak --BODY-- State: 0 {0} [!0] 0 State: 1 {0} [t] 1 State: 2 {0} [!0&1] 0 [0] 1 [!0&!1] 2 --END--"
: "G(!p0 | G!p1)","3 edges","HOA: v1 States: 2 Start: 1 AP: 2 ""p0"" ""p1"" acc-name: Buchi Acceptance: 1 Inf(0) properties: trans-labels explicit-labels state-acc colored deterministic stutter-invariant weak --BODY-- State: 0 {0} [!1] 0 State: 1 {0} [0&!1] 0 [!0] 1 --END--"
Another source of automata in CSV format is =ltlcross=. Using options
=--automata= it will record the automata produced by each tool into
the CSV file:
#+BEGIN_SRC sh :results verbatim :exports both
genltl --dac=1..3 | ltlcross --csv=result.csv --automata ltl2tgba
cat result.csv
#+END_SRC
#+RESULTS:
: "formula","tool","exit_status","exit_code","time","states","edges","transitions","acc","scc","nonacc_scc","terminal_scc","weak_scc","strong_scc","nondet_states","nondet_aut","terminal_aut","weak_aut","strong_aut","ambiguous_aut","complete_aut","product_states","product_transitions","product_scc","automaton"
: "G(!(p0))","ltl2tgba","ok",0,0.0243614,1,1,1,1,1,0,0,1,0,0,0,0,1,0,0,0,200,2055,2,"HOA: v1 name: ""G!p0"" States: 1 Start: 0 AP: 1 ""p0"" acc-name: Buchi Acceptance: 1 Inf(0) properties: trans-labels explicit-labels state-acc colored deterministic stutter-invariant weak --BODY-- State: 0 {0} [!0] 0 --END--"
: "!(G(!(p0)))","ltl2tgba","ok",0,0.0240194,2,3,4,1,2,1,1,0,0,0,0,1,0,0,0,1,400,8272,3,"HOA: v1 name: ""Fp0"" States: 2 Start: 1 AP: 1 ""p0"" acc-name: Buchi Acceptance: 1 Inf(0) properties: trans-labels explicit-labels state-acc complete deterministic stutter-invariant terminal --BODY-- State: 0 {0} [t] 0 State: 1 [0] 0 [!0] 1 --END--"
: "(F(p0)) -> ((!(p1)) U (p0))","ltl2tgba","ok",0,0.0248105,3,5,10,1,3,0,1,2,0,0,0,0,1,0,0,0,598,10379,4,"HOA: v1 name: ""(!p1 U p0) | G!p0"" States: 3 Start: 2 AP: 2 ""p1"" ""p0"" acc-name: Buchi Acceptance: 1 Inf(0) properties: trans-labels explicit-labels state-acc colored deterministic stutter-invariant weak --BODY-- State: 0 {0} [!1] 0 State: 1 {0} [t] 1 State: 2 {0} [0&!1] 0 [1] 1 [!0&!1] 2 --END--"
: "!((F(p0)) -> ((!(p1)) U (p0)))","ltl2tgba","ok",0,0.0243147,3,5,10,1,3,2,1,0,0,0,0,1,0,0,0,0,598,10398,4,"HOA: v1 name: ""Fp0 & (p1 R !p0)"" States: 3 Start: 1 AP: 2 ""p1"" ""p0"" acc-name: Buchi Acceptance: 1 Inf(0) properties: trans-labels explicit-labels state-acc deterministic stutter-invariant weak --BODY-- State: 0 {0} [t] 0 State: 1 [!0&!1] 1 [0&!1] 2 State: 2 [1] 0 [!1] 2 --END--"
: "G((p0) -> (G(!(p1))))","ltl2tgba","ok",0,0.0201896,2,3,5,1,2,0,0,2,0,0,0,0,1,0,0,0,400,5025,2,"HOA: v1 name: ""G(!p0 | G!p1)"" States: 2 Start: 1 AP: 2 ""p0"" ""p1"" acc-name: Buchi Acceptance: 1 Inf(0) properties: trans-labels explicit-labels state-acc colored deterministic stutter-invariant weak --BODY-- State: 0 {0} [!1] 0 State: 1 {0} [0&!1] 0 [!0] 1 --END--"
: "!(G((p0) -> (G(!(p1)))))","ltl2tgba","ok",0,0.0194598,3,6,12,1,3,2,1,0,0,0,0,1,0,0,0,1,600,12354,3,"HOA: v1 name: ""F(p0 & Fp1)"" States: 3 Start: 1 AP: 2 ""p0"" ""p1"" acc-name: Buchi Acceptance: 1 Inf(0) properties: trans-labels explicit-labels state-acc complete deterministic stutter-invariant terminal --BODY-- State: 0 [!1] 0 [1] 2 State: 1 [0&!1] 0 [!0] 1 [0&1] 2 State: 2 {0} [t] 2 --END--"

View file

@ -1035,3 +1035,8 @@ autfilt -H1.1 input9 | autfilt -d > output9b
diff output9 output9b diff output9 output9b
test 2 = `ltl2tgba 'GFa' 'a U b' 'a U b U c'| autfilt --ap=2..3 --count` test 2 = `ltl2tgba 'GFa' 'a U b' 'a U b U c'| autfilt --ap=2..3 --count`
# reading CSV with embedded automata
test 2 = `genltl --dac=1..3 | ltl2tgba --stats='%e,"%h",%s' |
dstar2tgba -F-/2 --stats='%<,%>,"%h"' |
autfilt --states=2..3 -F-/3 --stats='%<,"%h"' | wc -l`