1 // -*- coding: utf-8 -*-
2 // Copyright (C) 2017-2021 Laboratoire de Recherche et Développement
3 // de l'Epita (LRDE).
4 //
5 // This file is part of Spot, a model checking library.
6 //
7 // Spot is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU General Public License as published by
9 // the Free Software Foundation; either version 3 of the License, or
10 // (at your option) any later version.
11 //
12 // Spot is distributed in the hope that it will be useful, but WITHOUT
13 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 // or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
15 // License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 
20 #include <config.h>
21 
22 #include "argmatch.h"
23 
24 #include "common_aoutput.hh"
25 #include "common_finput.hh"
26 #include "common_setup.hh"
27 #include "common_sys.hh"
28 
29 #include <spot/misc/bddlt.hh>
30 #include <spot/misc/escape.hh>
31 #include <spot/misc/timer.hh>
32 #include <spot/tl/formula.hh>
33 #include <spot/tl/apcollect.hh>
34 #include <spot/twa/twagraph.hh>
35 #include <spot/twaalgos/aiger.hh>
36 #include <spot/twaalgos/game.hh>
37 #include <spot/twaalgos/hoa.hh>
38 #include <spot/twaalgos/minimize.hh>
39 #include <spot/twaalgos/mealy_machine.hh>
40 #include <spot/twaalgos/product.hh>
41 #include <spot/twaalgos/synthesis.hh>
42 #include <spot/twaalgos/translate.hh>
43 
44 enum
45 {
46   OPT_ALGO = 256,
47   OPT_CSV,
48   OPT_DECOMPOSE,
49   OPT_INPUT,
50   OPT_OUTPUT,
51   OPT_PRINT,
52   OPT_PRINT_AIGER,
53   OPT_PRINT_HOA,
54   OPT_REAL,
55   OPT_SIMPLIFY,
56   OPT_VERBOSE,
57   OPT_VERIFY
58 };
59 
60 static const argp_option options[] =
61   {
62     /**************************************************/
63     { nullptr, 0, nullptr, 0, "Input options:", 1 },
64     { "outs", OPT_OUTPUT, "PROPS", 0,
65       "comma-separated list of controllable (a.k.a. output) atomic"
66       " propositions", 0},
67     { "ins", OPT_INPUT, "PROPS", 0,
68       "comma-separated list of controllable (a.k.a. output) atomic"
69       " propositions", 0},
70     /**************************************************/
71     { nullptr, 0, nullptr, 0, "Fine tuning:", 10 },
72     { "algo", OPT_ALGO, "sd|ds|ps|lar|lar.old", 0,
73       "choose the algorithm for synthesis:"
74       " \"sd\": translate to tgba, split, then determinize;"
75       " \"ds\": translate to tgba, determinize, then split;"
76       " \"ps\": translate to dpa, then split;"
77       " \"lar\": translate to a deterministic automaton with arbitrary"
78       " acceptance condition, then use LAR to turn to parity,"
79       " then split (default);"
80       " \"lar.old\": old version of LAR, for benchmarking.\n", 0 },
81     { "decompose", OPT_DECOMPOSE, "yes|no", 0,
82       "whether to decompose the specification as multiple output-disjoint "
83       "problems to solve independently (enabled by default)", 0 },
84     { "simplify", OPT_SIMPLIFY, "no|bisim|bwoa|sat|bisim-sat|bwoa-sat", 0,
85       "simplification to apply to the controler (no) nothing, "
86       "(bisim) bisimulation-based reduction, (bwoa) bissimulation-based "
87       "reduction with output assignment, (sat) SAT-based minimization, "
88       "(bisim-sat) SAT after bisim, (bwoa-sat) SAT after bwoa.  Defaults "
89       "to 'bwoa'.", 0 },
90     /**************************************************/
91     { nullptr, 0, nullptr, 0, "Output options:", 20 },
92     { "print-pg", OPT_PRINT, nullptr, 0,
93       "print the parity game in the pgsolver format, do not solve it", 0},
94     { "print-game-hoa", OPT_PRINT_HOA, "options", OPTION_ARG_OPTIONAL,
95       "print the parity game in the HOA format, do not solve it", 0},
96     { "realizability", OPT_REAL, nullptr, 0,
97       "realizability only, do not compute a winning strategy", 0},
98     { "aiger", OPT_PRINT_AIGER, "ite|isop|both[+ud][+dc]"
99                                  "[+sub0|sub1|sub2]", OPTION_ARG_OPTIONAL,
100       "prints a winning strategy as an AIGER circuit. The first, and only "
101       "mandatory option defines the method to be used. \"ite\" for "
102       "If-then-else normal form; "
103       "\"isop\" for irreducible sum of producs; "
104       "\"both\" tries both encodings and keeps the smaller one. "
105       "The other options further "
106       "refine the encoding, see aiger::encode_bdd.", 0},
107     { "verbose", OPT_VERBOSE, nullptr, 0,
108       "verbose mode", -1 },
109     { "verify", OPT_VERIFY, nullptr, 0,
110        "verifies the strategy or (if demanded) aiger against the spec.", -1 },
111     { "csv", OPT_CSV, "[>>]FILENAME", OPTION_ARG_OPTIONAL,
112       "output statistics as CSV in FILENAME or on standard output "
113       "(if '>>' is used to request append mode, the header line is "
114       "not output)", 0 },
115     /**************************************************/
116     { nullptr, 0, nullptr, 0, "Miscellaneous options:", -1 },
117     { "extra-options", 'x', "OPTS", 0,
118         "fine-tuning options (see spot-x (7))", 0 },
119     { nullptr, 0, nullptr, 0, nullptr, 0 },
120   };
121 
122 static const struct argp_child children[] =
123   {
124     { &finput_argp_headless, 0, nullptr, 0 },
125     { &aoutput_argp, 0, nullptr, 0 },
126     //{ &aoutput_o_format_argp, 0, nullptr, 0 },
127     { &misc_argp, 0, nullptr, 0 },
128     { nullptr, 0, nullptr, 0 }
129   };
130 
131 const char argp_program_doc[] = "\
132 Synthesize a controller from its LTL specification.\v\
133 Exit status:\n\
134   0   if the input problem is realizable\n\
135   1   if the input problem is not realizable\n\
136   2   if any error has been reported";
137 
138 static std::vector<std::string> all_output_aps;
139 static std::vector<std::string> all_input_aps;
140 
141 static const char* opt_csv = nullptr;
142 static bool opt_print_pg = false;
143 static bool opt_print_hoa = false;
144 static const char* opt_print_hoa_args = nullptr;
145 static bool opt_real = false;
146 static bool opt_do_verify = false;
147 static const char* opt_print_aiger = nullptr;
148 
149 static spot::synthesis_info* gi;
150 
151 static char const *const algo_names[] =
152   {
153    "ds",
154    "sd",
155    "ps",
156    "lar",
157    "lar.old"
158   };
159 
160 static char const *const algo_args[] =
161 {
162   "detsplit", "ds",
163   "splitdet", "sd",
164   "dpasplit", "ps",
165   "lar",
166   "lar.old",
167   nullptr
168 };
169 static spot::synthesis_info::algo const algo_types[] =
170 {
171   spot::synthesis_info::algo::DET_SPLIT, spot::synthesis_info::algo::DET_SPLIT,
172   spot::synthesis_info::algo::SPLIT_DET, spot::synthesis_info::algo::SPLIT_DET,
173   spot::synthesis_info::algo::DPA_SPLIT, spot::synthesis_info::algo::DPA_SPLIT,
174   spot::synthesis_info::algo::LAR,
175   spot::synthesis_info::algo::LAR_OLD,
176 };
177 ARGMATCH_VERIFY(algo_args, algo_types);
178 
179 static const char* const decompose_args[] =
180   {
181     "yes", "true", "enabled", "1",
182     "no", "false", "disabled", "0",
183     nullptr
184   };
185 static bool decompose_values[] =
186   {
187     true, true, true, true,
188     false, false, false, false,
189   };
190 ARGMATCH_VERIFY(decompose_args, decompose_values);
191 bool opt_decompose_ltl = true;
192 
193 static const char* const simplify_args[] =
194   {
195     "no", "false", "disabled", "0",
196     "bisim", "1",
197     "bwoa", "bisim-with-output-assignment", "2",
198     "sat", "3",
199     "bisim-sat", "4",
200     "bwoa-sat", "5",
201     nullptr
202   };
203 static unsigned simplify_values[] =
204   {
205     0, 0, 0, 0,
206     1, 1,
207     2, 2, 2,
208     3, 3,
209     4, 4,
210     5, 5,
211   };
212 ARGMATCH_VERIFY(simplify_args, simplify_values);
213 
214 namespace
215 {
216   auto str_tolower = [] (std::string s)
__anon8920dfc70302(std::string s) 217     {
218       std::transform(s.begin(), s.end(), s.begin(),
219                      [](unsigned char c){ return std::tolower(c); });
220       return s;
221     };
222 
223   static void
print_csv(const spot::formula & f)224   print_csv(const spot::formula& f)
225   {
226     auto& vs = gi->verbose_stream;
227     auto& bv = gi->bv;
228     if (not bv)
229       error(2, 0, "no information available for csv (please send bug report)");
230     if (vs)
231       *vs << "writing CSV to " << opt_csv << '\n';
232 
233     output_file outf(opt_csv);
234     std::ostream& out = outf.ostream();
235 
236     // Do not output the header line if we append to a file.
237     // (Even if that file was empty initially.)
238     if (!outf.append())
239       {
240         out << ("\"formula\",\"algo\",\"tot_time\",\"trans_time\","
241                 "\"split_time\",\"todpa_time\"");
242         if (!opt_print_pg && !opt_print_hoa)
243           {
244             out << ",\"solve_time\"";
245             if (!opt_real)
246               out << ",\"strat2aut_time\"";
247             if (opt_print_aiger)
248               out << ",\"aig_time\"";
249             out << ",\"realizable\""; //-1: Unknown, 0: Unreal, 1: Real
250           }
251         out << ",\"dpa_num_states\",\"dpa_num_states_env\""
252             << ",\"strat_num_states\",\"strat_num_edges\"";
253         if (opt_print_aiger)
254             out << ",\"nb latches\",\"nb gates\"";
255         out << '\n';
256       }
257     std::ostringstream os;
258     os << f;
259     spot::escape_rfc4180(out << '"', os.str());
260     out << "\",\"" << algo_names[(int) gi->s]
261         << "\"," << bv->total_time
262         << ',' << bv->trans_time
263         << ',' << bv->split_time
264         << ',' << bv->paritize_time;
265     if (!opt_print_pg && !opt_print_hoa)
266       {
267         out << ',' << bv->solve_time;
268         if (!opt_real)
269           out << ',' << bv->strat2aut_time;
270         if (opt_print_aiger)
271           out << ',' << bv->aig_time;
272         out << ',' << bv->realizable;
273       }
274     out << ',' << bv->nb_states_arena
275         << ',' << bv->nb_states_arena_env
276         << ',' << bv->nb_strat_states
277         << ',' << bv->nb_strat_edges;
278 
279     if (opt_print_aiger)
280     {
281       out << ',' << bv->nb_latches
282           << ',' << bv->nb_gates;
283     }
284     out << '\n';
285     outf.close(opt_csv);
286   }
287 
288   int
solve_formula(const spot::formula & f,const std::vector<std::string> & input_aps,const std::vector<std::string> & output_aps)289   solve_formula(const spot::formula& f,
290                 const std::vector<std::string>& input_aps,
291                 const std::vector<std::string>& output_aps)
292   {
293     spot::stopwatch sw;
294     if (gi->bv)
295       sw.start();
296 
297     auto safe_tot_time = [&]()
298     {
299       if (gi->bv)
300         gi->bv->total_time = sw.stop();
301     };
302 
303     std::vector<spot::formula> sub_form;
304     std::vector<std::set<spot::formula>> sub_outs;
305     if (opt_decompose_ltl)
306     {
307       auto subs = split_independant_formulas(f, output_aps);
308       if (subs.first.size() > 1)
309       {
310         sub_form = subs.first;
311         sub_outs = subs.second;
312       }
313     }
314     // When trying to split the formula, we can apply transformations that
315     // increase its size. This is why we will use the original formula if it
316     // has not been cut.
317     if (sub_form.empty())
318     {
319       sub_form = { f };
320       sub_outs.resize(1);
321       std::transform(output_aps.begin(), output_aps.end(),
322             std::inserter(sub_outs[0], sub_outs[0].begin()),
323             [](const std::string& name) {
324               return spot::formula::ap(name);
325             });
326     }
327     std::vector<std::vector<std::string>> sub_outs_str;
328     std::transform(sub_outs.begin(), sub_outs.end(),
329                    std::back_inserter(sub_outs_str),
330                    [](const auto& forms)
331                     {
332                       std::vector<std::string> r;
333                       r.reserve(forms.size());
334                       for (auto f : forms)
335                         r.push_back(f.ap_name());
336                       return r;
337                     });
338 
339     assert((sub_form.size() == sub_outs.size())
340            && (sub_form.size() == sub_outs_str.size()));
341 
342     const bool want_game = opt_print_pg || opt_print_hoa;
343 
344     std::vector<spot::twa_graph_ptr> arenas;
345 
346     auto sub_f = sub_form.begin();
347     auto sub_o = sub_outs_str.begin();
348     std::vector<spot::mealy_like> mealy_machines;
349 
350     auto print_game = want_game ?
351       [](const spot::twa_graph_ptr& game)->void
352         {
353           if (opt_print_pg)
354             pg_print(std::cout, game);
355           else
356             spot::print_hoa(std::cout, game, opt_print_hoa_args) << '\n';
357         }
358       :
359       [](const spot::twa_graph_ptr&)->void{};
360 
361     for (; sub_f != sub_form.end(); ++sub_f, ++sub_o)
362     {
363       spot::mealy_like m_like
364               {
365                 spot::mealy_like::realizability_code::UNKNOWN,
366                 nullptr,
367                 bddfalse
368               };
369       // If we want to print a game,
370       // we never use the direct approach
371       if (!want_game)
372         m_like =
373             spot::try_create_direct_strategy(*sub_f, *sub_o, *gi);
374 
375       switch (m_like.success)
376       {
377       case spot::mealy_like::realizability_code::UNREALIZABLE:
378         {
379           std::cout << "UNREALIZABLE" << std::endl;
380           safe_tot_time();
381           return 1;
382         }
383       case spot::mealy_like::realizability_code::UNKNOWN:
384         {
385           auto arena = spot::ltl_to_game(*sub_f, *sub_o, *gi);
386           if (gi->bv)
387             {
388               gi->bv->nb_states_arena += arena->num_states();
389               auto spptr =
390                   arena->get_named_prop<std::vector<bool>>("state-player");
391               assert(spptr);
392               gi->bv->nb_states_arena_env +=
393                   std::count(spptr->cbegin(), spptr->cend(), false);
394               assert((spptr->at(arena->get_init_state_number()) == false)
395                      && "Env needs first turn");
396             }
397           print_game(arena);
398           if (!spot::solve_game(arena, *gi))
399             {
400               std::cout << "UNREALIZABLE" << std::endl;
401               safe_tot_time();
402               return 1;
403             }
404           // Create the (partial) strategy
405           // only if we need it
406           if (!opt_real)
407             {
408               spot::mealy_like ml;
409               ml.success =
410                 spot::mealy_like::realizability_code::REALIZABLE_REGULAR;
411               ml.mealy_like =
412                   spot::solved_game_to_separated_mealy(arena, *gi);
413               ml.glob_cond = bddfalse;
414               mealy_machines.push_back(ml);
415             }
416           break;
417         }
418       case spot::mealy_like::realizability_code::REALIZABLE_REGULAR:
419         {
420           // the direct approach yielded a strategy
421           // which can now be minimized
422           // We minimize only if we need it
423           assert(m_like.mealy_like && "Expected success but found no mealy!");
424           if (!opt_real)
425             {
426               spot::stopwatch sw_direct;
427               sw_direct.start();
428 
429               if ((0 < gi->minimize_lvl) && (gi->minimize_lvl < 3))
430                 // Uses reduction or not,
431                 // both work with mealy machines (non-separated)
432                 reduce_mealy_here(m_like.mealy_like, gi->minimize_lvl == 2);
433 
434               auto delta = sw_direct.stop();
435 
436               sw_direct.start();
437               // todo better algo here?
438               m_like.mealy_like =
439                   split_2step(m_like.mealy_like,
440                               spot::get_synthesis_outputs(m_like.mealy_like),
441                               false);
442               if (gi->bv)
443                 gi->bv->split_time += sw_direct.stop();
444 
445               sw_direct.start();
446               if (gi->minimize_lvl >= 3)
447                 {
448                   sw_direct.start();
449                   // actual minimization, works on split mealy
450                   m_like.mealy_like = minimize_mealy(m_like.mealy_like,
451                                                      gi->minimize_lvl - 4);
452                   delta = sw_direct.stop();
453                 }
454 
455               // Unsplit to have separated mealy
456               m_like.mealy_like = unsplit_mealy(m_like.mealy_like);
457 
458               if (gi->bv)
459                 gi->bv->strat2aut_time += delta;
460               if (gi->verbose_stream)
461                 *gi->verbose_stream << "final strategy has "
462                                     << m_like.mealy_like->num_states()
463                                     << " states and "
464                                     << m_like.mealy_like->num_edges()
465                                     << " edges\n"
466                                     << "minimization took " << delta
467                                     << " seconds\n";
468             }
469           SPOT_FALLTHROUGH;
470         }
471       case spot::mealy_like::realizability_code::REALIZABLE_DTGBA:
472         if (!opt_real)
473           mealy_machines.push_back(m_like);
474         break;
475       default:
476         error(2, 0, "unexpected success code during mealy machine generation "
477               "(please send bug report)");
478       }
479     }
480 
481     // If we only wanted to print the game we are done
482     if (want_game)
483       {
484         safe_tot_time();
485         return 0;
486       }
487 
488     std::cout << "REALIZABLE" << std::endl;
489     if (opt_real)
490       {
491         safe_tot_time();
492         return 0;
493       }
494     // If we reach this line
495     // a strategy was found for each subformula
496     assert(mealy_machines.size() == sub_form.size()
497            && "There are subformula for which no mealy like object"
498                 "has been created.");
499 
500     spot::aig_ptr saig = nullptr;
501     spot::twa_graph_ptr tot_strat = nullptr;
502     automaton_printer printer;
503     spot::process_timer timer_printer_dummy;
504 
505     if (opt_print_aiger)
506       {
507         spot::stopwatch sw2;
508         if (gi->bv)
509           sw2.start();
510         saig = spot::mealy_machines_to_aig(mealy_machines, opt_print_aiger,
511                                            input_aps,
512                                            sub_outs_str);
513         if (gi->bv)
514           {
515             gi->bv->aig_time = sw2.stop();
516             gi->bv->nb_latches = saig->num_latches();
517             gi->bv->nb_gates = saig->num_gates();
518           }
519         if (gi->verbose_stream)
520           {
521             *gi->verbose_stream << "AIG circuit was created in "
522                                << gi->bv->aig_time
523                                << " seconds and has " << saig->num_latches()
524                                << " latches and "
525                                << saig->num_gates() << " gates\n";
526           }
527         spot::print_aiger(std::cout, saig) << '\n';
528       }
529     else
530       {
531         assert(std::all_of(
532            mealy_machines.begin(), mealy_machines.end(),
533            [](const auto& ml)
534            {return ml.success ==
535                spot::mealy_like::realizability_code::REALIZABLE_REGULAR; })
536                && "ltlsynt: Cannot handle TGBA as strategy.");
537         tot_strat = mealy_machines.front().mealy_like;
538         for (size_t i = 1; i < mealy_machines.size(); ++i)
539           tot_strat = spot::product(tot_strat, mealy_machines[i].mealy_like);
540         printer.print(tot_strat, timer_printer_dummy);
541       }
542 
543     // Final step: Do verification if demanded
544     if (!opt_do_verify)
545       {
546         safe_tot_time();
547         return 0;
548       }
549 
550 
551     // TODO: different options to speed up verification?!
552     spot::translator trans(gi->dict, &gi->opt);
553     auto neg_spec = trans.run(spot::formula::Not(f));
554     if (saig)
555       {
556         // Test the aiger
557         auto saigaut = saig->as_automaton(false);
558         if (neg_spec->intersects(saigaut))
559           error(2, 0, "Aiger and negated specification do intersect: "
560                 "circuit is not OK.");
561         std::cout << "c\nCircuit was verified\n";
562       }
563     else if  (tot_strat)
564       {
565         // Test the strategy
566         if (neg_spec->intersects(tot_strat))
567           error(2, 0, "Strategy and negated specification do intersect: "
568                 "strategy is not OK.");
569         std::cout << "/*Strategy was verified*/\n";
570       }
571     // Done
572 
573     safe_tot_time();
574     return 0;
575   }
576 
577   class ltl_processor final : public job_processor
578   {
579   private:
580     std::vector<std::string> input_aps_;
581     std::vector<std::string> output_aps_;
582 
583   public:
ltl_processor(std::vector<std::string> input_aps_,std::vector<std::string> output_aps_)584     ltl_processor(std::vector<std::string> input_aps_,
585                   std::vector<std::string> output_aps_)
586         : input_aps_(std::move(input_aps_)),
587           output_aps_(std::move(output_aps_))
588     {
589     }
590 
process_formula(spot::formula f,const char * filename,int linenum)591     int process_formula(spot::formula f,
592                         const char* filename, int linenum) override
593     {
594       auto unknown_aps = [](spot::formula f,
595                             const std::vector<std::string>& known,
596                             const std::vector<std::string>* known2 = nullptr)
597       {
598         std::vector<std::string> unknown;
599         std::set<spot::formula> seen;
600         f.traverse([&](const spot::formula& s)
601         {
602           if (s.is(spot::op::ap))
603             {
604               if (!seen.insert(s).second)
605                 return false;
606               const std::string& a = s.ap_name();
607               if (std::find(known.begin(), known.end(), a) == known.end()
608                   && (!known2
609                       || std::find(known2->begin(),
610                                    known2->end(), a) == known2->end()))
611                 unknown.push_back(a);
612             }
613           return false;
614         });
615         return unknown;
616       };
617 
618       // Decide which atomic propositions are input or output.
619       int res;
620       if (input_aps_.empty() && !output_aps_.empty())
621         {
622           res = solve_formula(f, unknown_aps(f, output_aps_), output_aps_);
623         }
624       else if (output_aps_.empty() && !input_aps_.empty())
625         {
626           res = solve_formula(f, input_aps_, unknown_aps(f, input_aps_));
627         }
628       else if (output_aps_.empty() && input_aps_.empty())
629         {
630           for (const std::string& ap: unknown_aps(f, input_aps_, &output_aps_))
631             error_at_line(2, 0, filename, linenum,
632                           "one of --ins or --outs should list '%s'",
633                           ap.c_str());
634           res = solve_formula(f, input_aps_, output_aps_);
635         }
636       else
637         {
638           for (const std::string& ap: unknown_aps(f, input_aps_, &output_aps_))
639             error_at_line(2, 0, filename, linenum,
640                           "both --ins and --outs are specified, "
641                           "but '%s' is unlisted",
642                           ap.c_str());
643           res = solve_formula(f, input_aps_, output_aps_);
644         }
645 
646       if (opt_csv)
647         print_csv(f);
648       return res;
649     }
650   };
651 }
652 
653 static int
parse_opt(int key,char * arg,struct argp_state *)654 parse_opt(int key, char *arg, struct argp_state *)
655 {
656   // Called from C code, so should not raise any exception.
657   BEGIN_EXCEPTION_PROTECT;
658   switch (key)
659     {
660     case OPT_ALGO:
661       gi->s = XARGMATCH("--algo", arg, algo_args, algo_types);
662       break;
663     case OPT_CSV:
664       opt_csv = arg ? arg : "-";
665       if (not gi->bv)
666         gi->bv = spot::synthesis_info::bench_var();
667       break;
668     case OPT_DECOMPOSE:
669       opt_decompose_ltl = XARGMATCH("--decompose", arg,
670                                     decompose_args, decompose_values);
671       break;
672     case OPT_INPUT:
673       {
674         std::istringstream aps(arg);
675         std::string ap;
676         while (std::getline(aps, ap, ','))
677           {
678             ap.erase(remove_if(ap.begin(), ap.end(), isspace), ap.end());
679             all_input_aps.push_back(str_tolower(ap));
680           }
681         break;
682       }
683     case OPT_OUTPUT:
684       {
685         std::istringstream aps(arg);
686         std::string ap;
687         while (std::getline(aps, ap, ','))
688           {
689             ap.erase(remove_if(ap.begin(), ap.end(), isspace), ap.end());
690             all_output_aps.push_back(str_tolower(ap));
691           }
692         break;
693       }
694     case OPT_PRINT:
695       opt_print_pg = true;
696       gi->force_sbacc = true;
697       break;
698     case OPT_PRINT_HOA:
699       opt_print_hoa = true;
700       opt_print_hoa_args = arg;
701       break;
702     case OPT_PRINT_AIGER:
703       opt_print_aiger = arg ? arg : "ite";
704       break;
705     case OPT_REAL:
706       opt_real = true;
707       break;
708     case OPT_SIMPLIFY:
709       gi->minimize_lvl = XARGMATCH("--simplify", arg,
710                                    simplify_args, simplify_values);
711       break;
712     case OPT_VERBOSE:
713       gi->verbose_stream = &std::cerr;
714       if (not gi->bv)
715         gi->bv = spot::synthesis_info::bench_var();
716       break;
717     case OPT_VERIFY:
718       opt_do_verify = true;
719       break;
720     case 'x':
721       {
722         const char* opt = gi->opt.parse_options(arg);
723         if (opt)
724           error(2, 0, "failed to parse --options near '%s'", opt);
725       }
726       break;
727     }
728   END_EXCEPTION_PROTECT;
729   return 0;
730 }
731 
732 int
main(int argc,char ** argv)733 main(int argc, char **argv)
734 {
735   return protected_main(argv, [&] {
736     // Create gi_ as a local variable, so make sure it is destroyed
737     // before all global variables.
738     spot::synthesis_info gi_;
739     gi = &gi_;
740     //gi_.opt.set("simul", 0);     // no simulation, except...
741     //gi_.opt.set("dpa-simul", 1); // ... after determinization
742     gi_.opt.set("tls-impl", 1);  // no automata-based implication check
743     gi_.opt.set("wdba-minimize", 2); // minimize only syntactic oblig
744     const argp ap = { options, parse_opt, nullptr,
745                       argp_program_doc, children, nullptr, nullptr };
746     if (int err = argp_parse(&ap, argc, argv, ARGP_NO_HELP, nullptr, nullptr))
747       exit(err);
748     check_no_formula();
749 
750     // Check if inputs and outputs are distinct
751     for (const std::string& ai : all_input_aps)
752       if (std::find(all_output_aps.begin(), all_output_aps.end(), ai)
753           != all_output_aps.end())
754         error(2, 0, "'%s' appears both in --ins and --outs", ai.c_str());
755 
756     ltl_processor processor(all_input_aps, all_output_aps);
757     if (int res = processor.run(); res == 0 || res == 1)
758       {
759         // Diagnose unused -x options
760         gi_.opt.report_unused_options();
761         return res;
762       }
763     return 2;
764   });
765 }
766