parseaut: Add support for PGSolver's format

* spot/parseaut/parseaut.yy, spot/parseaut/scanaut.ll: Add rules for
PGSolver's format.
* spot/parseaut/public.hh: PGAME is a new type of output.
* tests/core/pgsolver.test: New file.
* tests/Makefile.am: Add it.
* tests/python/games.ipynb: More exemples.
* NEWS: Mention the new feature.
This commit is contained in:
Alexandre Duret-Lutz 2022-07-22 10:49:23 +02:00
parent d6b3c757d0
commit 444e2b5b89
7 changed files with 1144 additions and 342 deletions

View file

@ -44,6 +44,7 @@
#include "spot/priv/accmap.hh"
#include <spot/tl/parse.hh>
#include <spot/twaalgos/alternation.hh>
#include <spot/twaalgos/game.hh>
using namespace std::string_literals;
@ -256,6 +257,11 @@ extern "C" int strverscmp(const char *s1, const char *s2);
%token ENDOFFILE 0 "end of file"
%token <str> '['
%token <str> LINEDIRECTIVE "#line"
%token <b> BDD
/**** DSTAR tokens ****/
%token ENDDSTAR "end of DSTAR automaton"
%token DRA "DRA"
%token DSA "DSA"
%token V2 "v2"
@ -263,14 +269,12 @@ extern "C" int strverscmp(const char *s1, const char *s2);
%token ACCPAIRS "Acceptance-Pairs:"
%token ACCSIG "Acc-Sig:"
%token ENDOFHEADER "---"
%token <str> LINEDIRECTIVE "#line"
%token <b> BDD
%left '|'
%left '&'
%precedence '!'
%type <states> init-state-conj-2 state-conj-2 state-conj-checked
%type <states> init-state-conj-2 state-conj-2 state-conj-checked pgame_succs
%type <num> checked-state-num state-num acc-set sign
%type <b> label-expr
%type <mark> acc-sig acc-sets trans-acc_opt state-acc_opt
@ -299,10 +303,14 @@ extern "C" int strverscmp(const char *s1, const char *s2);
%type <str> nc-one-ident nc-ident-list
%type <code> acceptance-cond
/**** PGAME tokens ****/
// Also using INT, STRING
%token PGAME "start of PGSolver game"
%token ENDPGAME "end of PGSolver game"
/**** LBTT tokens *****/
// Also using INT, STRING
// Also using INT, STRING
%token ENDAUT "-1"
%token ENDDSTAR "end of DSTAR automaton"
%token <num> LBTT "LBTT header"
%token <num> INT_S "state acceptance"
%token <num> LBTT_EMPTY "acceptance sets for empty automaton"
@ -364,6 +372,7 @@ aut-1: hoa { res.h->type = spot::parsed_aut_type::HOA; }
| never { res.h->type = spot::parsed_aut_type::NeverClaim; }
| lbtt { res.h->type = spot::parsed_aut_type::LBTT; }
| dstar /* will set type as DSA or DRA while parsing first line */
| pgame { res.h->type = spot::parsed_aut_type::PGAME; }
/**********************************************************************/
/* Rules for HOA */
@ -1765,7 +1774,7 @@ dstar_header: dstar_sizes
if (res.states > 0)
{
res.h->aut->new_states(res.states);;
res.h->aut->new_states(res.states);
res.info_states.resize(res.states);
}
res.acc_style = State_Acc;
@ -1908,6 +1917,93 @@ dstar_states: %empty
res.h->aut->new_edge(res.cur_state, i.first, i.second, $3);
}
/**********************************************************************/
/* Rules for PGSolver games */
/**********************************************************************/
pgamestart: PGAME
{
if (res.opts.want_kripke)
{
error(@$,
"cannot read a Kripke structure out of a PGSolver game.");
YYABORT;
}
}
pgame: pgamestart pgame_nodes ENDPGAME
{
unsigned n = res.accset;
auto p = spot::acc_cond::acc_code::parity_max_odd(n);
res.h->aut->set_acceptance(n, p);
res.acc_style = State_Acc;
// Pretend that we have declared all states.
n = res.h->aut->num_states();
res.info_states.resize(n);
for (auto& p: res.info_states)
p.declared = true;
}
| pgamestart error ENDPGAME
{
error(@$, "failed to parse this as a PGSolver game");
}
pgame_nodes: pgame_node ';'
| pgame_nodes pgame_node ';'
pgame_succs: INT
{ $$ = new std::vector<unsigned>{$1}; }
| pgame_succs ',' INT
{
$$ = $1;
$$->emplace_back($3);
}
pgame_node: INT INT INT pgame_succs string_opt
{
unsigned state = $1;
unsigned owner = $3;
if (owner > 1)
{
error(@3, "node owner should be 0 or 1");
owner = 0;
}
// Create any missing state
unsigned max_state = state;
for (unsigned s: *$4)
max_state = std::max(max_state, s);
unsigned n = res.h->aut->num_states();
if (n <= max_state)
res.h->aut->new_states(max_state + 1 - n);
// assume the source of the first edge is initial
if (res.start.empty())
res.start.emplace_back(@$, std::vector<unsigned>{state});
// Create all edges with priority $2
spot::acc_cond::mark_t m({$2});
for (unsigned s: *$4)
res.h->aut->new_edge(state, s, bddtrue, m);
res.accset = std::max(res.accset, 1 + (int) $2);
n = res.h->aut->num_states();
if (!res.state_player)
res.state_player = new std::vector<bool>(n);
else if (res.state_player->size() < n)
res.state_player->resize(n);
(*res.state_player)[state] = owner;
if (std::string* name = $5)
{
if (!res.state_names)
res.state_names = new std::vector<std::string>(n);
else if (res.state_names->size() < n)
res.state_names->resize(n);
(*res.state_names)[state] = std::move(*name);
delete name;
}
}
/**********************************************************************/
/* Rules for neverclaims */
/**********************************************************************/
@ -2487,7 +2583,7 @@ static void fix_initial_state(result_& r)
start.resize(std::distance(start.begin(), res));
assert(start.size() >= 1);
if (start.size() == 1)
if (start.size() == 1)
{
if (r.opts.want_kripke)
r.h->ks->set_init_state(start.front().front());

View file

@ -44,7 +44,14 @@ namespace spot
struct parse_aut_error_list {};
#endif
enum class parsed_aut_type { HOA, NeverClaim, LBTT, DRA, DSA, Unknown };
enum class parsed_aut_type {
HOA,
NeverClaim,
LBTT,
DRA, /* DSTAR format for Rabin */
DSA, /* DSTAR format for Streett */
PGAME, /* PG Solver Game */
Unknown };
/// \brief Result of the automaton parser
struct SPOT_API parsed_aut final
@ -91,11 +98,11 @@ namespace spot
struct automaton_parser_options final
{
bool ignore_abort = false; ///< Skip aborted automata
bool debug = false; ///< Run the parser in debug mode?
bool trust_hoa = true; ///< Trust properties in HOA files
bool raise_errors = false; ///< Raise errors as exceptions.
bool want_kripke = false; ///< Parse as a Kripke structure.
bool ignore_abort = false; ///< Skip aborted automata
bool debug = false; ///< Run the parser in debug mode?
bool trust_hoa = true; ///< Trust properties in HOA files
bool raise_errors = false; ///< Raise errors as exceptions.
bool want_kripke = false; ///< Parse as a Kripke structure.
};
/// \brief Parse a stream of automata

View file

@ -65,12 +65,18 @@ eol \n+|\r+
eol2 (\n\r)+|(\r\n)+
eols ({eol}|{eol2})*
identifier [[:alpha:]_][[:alnum:]_.-]*
pgameinit "parity"[ \t]+[0-9]+[ \t]*;
oldpgameinit [0-9]+[ \t]+[0-9]+[ \t]+[01]+[ \t]+[0-9,]+([ \t]+".*")?[ \t]*;
/* A pattern than match the start of an automaton, in order
to detect the end of the previous one. We do not try to match
LBTT automata here. */
startaut {eols}("HOA:"|"never"|"DSA"|"DRA"|{pgameinit})
%x in_COMMENT in_STRING in_NEVER_PAR
%s in_HOA in_NEVER in_LBTT_HEADER
%s in_LBTT_STATE in_LBTT_INIT in_LBTT_TRANS
%s in_LBTT_T_ACC in_LBTT_S_ACC in_LBTT_GUARD
%s in_DSTAR
%s in_DSTAR in_PGAME
%%
%{
@ -127,7 +133,20 @@ identifier [[:alpha:]_][[:alnum:]_.-]*
<INITIAL>"never" BEGIN(in_NEVER); return token::NEVER;
<INITIAL>"DSA" BEGIN(in_DSTAR); return token::DSA;
<INITIAL>"DRA" BEGIN(in_DSTAR); return token::DRA;
<INITIAL>{pgameinit} {
BEGIN(in_PGAME);
char* end = nullptr;
errno = 0;
unsigned long n = strtoul(yytext + 7, &end, 10);
yylval->num = n;
return token::PGAME;
}
<INITIAL>{oldpgameinit} {
BEGIN(in_PGAME);
yylval->num = 0;
yyless(0);
return token::PGAME;
}
<INITIAL>[0-9]+[ \t][0-9]+[ts]? {
BEGIN(in_LBTT_HEADER);
char* end = nullptr;
@ -229,10 +248,8 @@ identifier [[:alpha:]_][[:alnum:]_.-]*
return token::INT;
}
[0-9]+ parse_int(); return token::INT;
/* The start of any automaton is the end of this one.
We do not try to detect LBTT automata, as that would
be too hard to distinguish from state numbers. */
{eols}("HOA:"|"never"|"DSA"|"DRA") {
/* The start of any automaton is the end of this one. */
{startaut} {
yylloc->end = yylloc->begin;
yyless(0);
BEGIN(INITIAL);
@ -270,6 +287,24 @@ identifier [[:alpha:]_][[:alnum:]_.-]*
}
}
<in_PGAME>{
/* Handle short numbers without going through parse_int() for efficiency. */
[0-9] yylval->num = *yytext - '0'; return token::INT;
[0-9][0-9] {
yylval->num = (yytext[0] * 10) + yytext[1] - '0' * 11;
return token::INT;
}
[0-9]+ parse_int(); return token::INT;
/* The start of any automaton is the end of this one. */
{startaut} {
yylloc->end = yylloc->begin;
yyless(0);
BEGIN(INITIAL);
return token::ENDPGAME;
}
<<EOF>> return token::ENDPGAME;
}
/* Note: the LBTT format is scanf friendly, but not Bison-friendly.
If we only tokenize it as a stream of INTs, the parser will have
a very hard time recognizing what is a state from what is a