// -*- 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_trans.hh" #include "common_setup.hh" #include #include #include #include #include #include #include #include #include #if __has_include() #define HAVE_SPAWN_H 1 #include #endif #include #include "error.h" #include #include #include "common_conv.hh" #include #include #include #include // A set of tools for which we know the correct output struct shorthands_t { const char* prefix; std::regex rprefix; const char* suffix; }; #define SHORTHAND(PRE, POST) { PRE, std::regex("^" PRE), POST } static const shorthands_t shorthands_ltl[] = { SHORTHAND("delag", " %f>%O"), SHORTHAND("lbt", " <%L>%O"), SHORTHAND("ltl2ba", " -f %s>%O"), SHORTHAND("ltl2(da|dgra|dpa|dra|ldba|na|nba|ngba)", " %f>%O"), SHORTHAND("ltl2dstar", " --output-format=hoa %[MW]L %O"), SHORTHAND("ltl2tgba", " -H %f>%O"), // ltl3tela is the new name of ltl3hoa SHORTHAND("ltl3(ba|dra|hoa|tela)", " -f %s>%O"), SHORTHAND("modella", " %[MWei^]L %O"), SHORTHAND("spin", " -f %s>%O"), // Starting from Owl 21.0, Owl's tools have been grouped as // sub-commands of the Owl binary. We still want to support // use cases where several version of owl are installed with // different names. SHORTHAND("owl.* ltl2[bdeglnpr]+a\\b", " -f %f>%O"), SHORTHAND("owl.* ltl2delta2\\b", " -f %f"), SHORTHAND("owl.* ltl-utilities\\b", " -f %f"), }; static const shorthands_t shorthands_autproc[] = { SHORTHAND("autfilt", " %H>%O"), SHORTHAND("dra2dpa", " <%H>%O"), SHORTHAND("dstar2tgba", " %H>%O"), SHORTHAND("ltl2dstar", " -B %H %O"), SHORTHAND("nba2l?dpa", " <%H>%O"), SHORTHAND("seminator", " %H>%O"), SHORTHAND("owl.* " "(ngba2ldba|nba(2dpa|2det|sim)|aut2parity|gfg-minimization)\\b", " <%H>%O"), }; static void show_shorthands(const shorthands_t* begin, const shorthands_t* end) { std::cout << ("If a COMMANDFMT does not use any %-sequence, and starts with one of\n" "the following regexes, then the string on the right is appended.\n\n"); while (begin != end) { std::cout << " " << std::left << std::setw(40) << begin->prefix << begin->suffix << '\n'; ++begin; } } tool_spec::tool_spec(const char* spec, const shorthands_t* begin, const shorthands_t* end, bool is_ref) noexcept : spec(spec), cmd(spec), name(spec), reference(is_ref) { if (*cmd == '{') { // Match the closing '}' const char* pos = cmd; unsigned count = 1; while (*++pos) { if (*pos == '{') ++count; else if (*pos == '}' && --count == 0) { name = strndup(cmd + 1, pos - cmd - 1); cmd = pos + 1; // skip leading whitespace while (*cmd == ' ' || *cmd == '\t') ++cmd; break; } } } // If there is no % in the string, look for a known // command from our shorthand list. If we find it, // add the suffix. bool allocated = false; if (!strchr(cmd, '%')) { // Skip any leading directory name. auto basename = cmd; auto pos = cmd; while (*pos) { if (*pos == '/') basename = pos + 1; else if (*pos == ' ') break; ++pos; } // Match a shorthand. while (begin != end) { auto& p = *begin++; if (std::regex_search(basename, p.rprefix)) { size_t m = strlen(p.suffix); size_t q = strlen(cmd); char* tmp = static_cast(malloc(q + m + 1)); memcpy(tmp, cmd, q); memcpy(tmp + q, p.suffix, m + 1); cmd = tmp; allocated = true; break; } } } if (!allocated) cmd = strdup(cmd); } tool_spec::tool_spec(const tool_spec& other) noexcept : spec(other.spec), cmd(other.cmd), name(other.name), reference(other.reference) { if (cmd != spec) cmd = strdup(cmd); if (name != spec) name = strdup(name); } tool_spec& tool_spec::operator=(const tool_spec& other) { spec = other.spec; cmd = other.cmd; if (cmd != spec) cmd = strdup(cmd); name = other.name; if (name != spec) name = strdup(name); return *this; } tool_spec::~tool_spec() { if (name != spec) free(const_cast(name)); if (cmd != spec) free(const_cast(cmd)); } std::vector tools; void tools_push_trans(const char* trans, bool is_ref) { tools.emplace_back(trans, std::begin(shorthands_ltl), std::end(shorthands_ltl), is_ref); } void tools_push_autproc(const char* proc, bool is_ref) { tools.emplace_back(proc, std::begin(shorthands_autproc), std::end(shorthands_autproc), is_ref); } void quoted_formula::print(std::ostream& os, const char* pos) const { spot::formula f = val_; if (*pos == '[') { ++pos; auto end = strchr(pos, ']'); auto arg = strndup(pos, end - pos); f = spot::unabbreviate(f, arg); free(arg); pos = end + 1; } std::ostringstream ss; std::ostream* out = &ss; bool quoted = true; switch (*pos) { case 'F': case 'S': case 'L': case 'W': out = &os; quoted = false; } switch (*pos) { case 'f': case 'F': print_psl(*out, f, true); break; case 's': case 'S': print_spin_ltl(*out, f, true); break; case 'l': case 'L': print_lbt_ltl(*out, f); break; case 'w': case 'W': print_wring_ltl(*out, f); break; } if (quoted) { std::string s = ss.str(); spot::quote_shell_string(os, s.c_str()); } } printable_result_filename::printable_result_filename() { val_ = nullptr; } printable_result_filename::~printable_result_filename() { delete val_; } void printable_result_filename::reset(unsigned n) { translator_num = n; } void printable_result_filename::cleanup() { delete val_; val_ = nullptr; } void printable_result_filename::print(std::ostream& os, const char*) const { char prefix[30]; snprintf(prefix, sizeof prefix, "lcr-o%u-", translator_num); const_cast(this)->val_ = spot::create_tmpfile(prefix); spot::quote_shell_string(os, val()->name()); } static std::string string_to_tmp(const std::string str, unsigned n) { char prefix[30]; snprintf(prefix, sizeof prefix, "lcr-i%u-", n); spot::open_temporary_file* tmpfile = spot::create_open_tmpfile(prefix); std::string tmpname = tmpfile->name(); int fd = tmpfile->fd(); ssize_t s = str.size(); if (write(fd, str.c_str(), s) != s || write(fd, "\n", 1) != 1) error(2, errno, "failed to write into %s", tmpname.c_str()); tmpfile->close(); return tmpname; } void filed_formula::print(std::ostream& os, const char* pos) const { std::ostringstream ss; f_.print(ss, pos); os << '\'' << string_to_tmp(ss.str(), serial_) << '\''; } void filed_automaton::print(std::ostream& os, const char* pos) const { std::ostringstream ss; char* opt = nullptr; bool filename = true; if (*pos == '[') { ++pos; auto end = strchr(pos, ']'); opt = strndup(pos, end - pos); pos = end + 1; } switch (*pos) { case 'H': spot::print_hoa(ss, aut_, opt); break; case 'S': spot::print_never_claim(ss, aut_, opt); break; case 'L': spot::print_lbtt(ss, aut_, opt); break; case 'M': { filename = false; std::string* name = aut_->get_named_prop("automaton-name"); spot::quote_shell_string(os, name ? name->c_str() : opt ? opt : ""); } } if (opt) free(opt); if (filename) os << '\'' << string_to_tmp(ss.str(), serial_) << '\''; } translator_runner::translator_runner(spot::bdd_dict_ptr dict, bool no_output_allowed) : dict(dict) { declare('f', <l_formula); declare('s', <l_formula); declare('l', <l_formula); declare('w', <l_formula); declare('F', &filename_formula); declare('S', &filename_formula); declare('L', &filename_formula); declare('W', &filename_formula); declare('D', &output); declare('H', &output); declare('N', &output); declare('T', &output); declare('O', &output); size_t s = tools.size(); assert(s); for (size_t n = 0; n < s; ++n) { // Check that each translator uses at least one input and // one output. std::vector has(256); const tool_spec& t = tools[n]; scan(t.cmd, has); if (!(has['f'] || has['s'] || has['l'] || has['w'] || has['F'] || has['S'] || has['L'] || has['W'])) error(2, 0, "no input %%-sequence in '%s'.\n Use " "one of %%f,%%s,%%l,%%w,%%F,%%S,%%L,%%W to indicate how " "to pass the formula.", t.spec); if (!no_output_allowed && !(has['O'] || // backward-compatibility has['D'] || has['N'] || has['T'] || has['H'])) error(2, 0, "no output %%-sequence in '%s'.\n Use " "%%O to indicate where the automaton is output.", t.spec); // Remember the %-sequences used by all tools. prime(t.cmd); } } std::string translator_runner::formula() const { // Pick the most readable format we have... if (has('f') || has('F')) return spot::str_psl(ltl_formula, true); if (has('s') || has('S')) return spot::str_spin_ltl(ltl_formula, true); if (has('l') || has('L')) return spot::str_lbt_ltl(ltl_formula); if (has('w') || has('W')) return spot::str_wring_ltl(ltl_formula); SPOT_UNREACHABLE(); return spot::str_psl(ltl_formula, true); } void translator_runner::round_formula(spot::formula f, unsigned serial) { ltl_formula = f; filename_formula.new_round(serial); } autproc_runner::autproc_runner(bool no_output_allowed) { declare('H', &filename_automaton); declare('S', &filename_automaton); declare('L', &filename_automaton); declare('M', &filename_automaton); declare('O', &output); size_t s = tools.size(); assert(s); for (size_t n = 0; n < s; ++n) { // Check that each translator uses at least one input and // one output. std::vector has(256); const tool_spec& t = tools[n]; scan(t.cmd, has); if (!(has['H'] || has['S'] || has['L'] || has['M'])) error(2, 0, "no input %%-sequence in '%s'.\n Use " "one of %%H,%%S,%%L to indicate the input automaton filename.\n" " (Or use %%M if you want to work from its name.)\n", t.spec); if (!no_output_allowed && !has['O']) error(2, 0, "no output %%-sequence in '%s'.\n Use " "%%O to indicate where the automaton is output.", t.spec); // Remember the %-sequences used by all tools. prime(t.cmd); } } void autproc_runner::round_automaton(spot::const_twa_graph_ptr aut, unsigned serial) { filename_automaton.new_round(aut, serial); } std::string read_stdout_of_command(char* const* args) { #if HAVE_SPAWN_H int cout_pipe[2]; if (int err = pipe(cout_pipe)) error(2, err, "pipe() failed"); posix_spawn_file_actions_t actions; if (int err = posix_spawn_file_actions_init(&actions)) error(2, err, "posix_spawn_file_actions_init() failed"); posix_spawn_file_actions_addclose(&actions, STDIN_FILENO); posix_spawn_file_actions_addclose(&actions, cout_pipe[0]); posix_spawn_file_actions_adddup2(&actions, cout_pipe[1], STDOUT_FILENO); posix_spawn_file_actions_addclose(&actions, cout_pipe[1]); pid_t pid; if (int err = posix_spawnp(&pid, args[0], &actions, nullptr, args, environ)) error(2, err, "failed to run '%s'", args[0]); if (int err = posix_spawn_file_actions_destroy(&actions)) error(2, err, "posix_spawn_file_actions_destroy() failed"); if (close(cout_pipe[1]) < 0) error(2, errno, "closing write-side of pipe failed"); std::string results; ssize_t bytes_read; for (;;) { static char buffer[512]; bytes_read = read(cout_pipe[0], buffer, sizeof(buffer)); if (bytes_read > 0) results.insert(results.end(), buffer, buffer + bytes_read); else break; } if (bytes_read < 0) error(2, bytes_read, "failed to read from pipe"); if (cout_pipe[0] < 0) error(2, errno, "closing read-side of pipe failed"); int exit_code = 0; if (waitpid(pid, &exit_code, 0) == -1) error(2, errno, "waitpid() failed"); if (exit_code) error(2, 0, "'%s' exited with status %d", args[0], exit_code); return results; #else // We could provide a pipe+fork+exec alternative implementation, but // systems without posix_spawn() might also not have fork and exec. // For instance MinGW does not. So let's fallback to system+tmpfile // instead for maximum portability. char prefix[30]; snprintf(prefix, sizeof prefix, "spot-tmp"); spot::temporary_file* tmpfile = spot::create_tmpfile(prefix); std::string tmpname = tmpfile->name(); std::ostringstream cmd; for (auto t = args; *t != nullptr; ++t) spot::quote_shell_string(cmd, *t) << ' '; cmd << '>'; spot::quote_shell_string(cmd, tmpfile->name()); std::string cmdstr = cmd.str(); int exit_code = system(cmdstr.c_str()); if (exit_code < 0) error(2, errno, "failed to execute %s", cmdstr.c_str()); if (exit_code > 0) error(2, 0, "'%s' exited with status %d", args[0], exit_code); std::ifstream ifs(tmpname, std::ifstream::in); if (!ifs) error(2, 0, "failed to open %s (output of %s)", tmpname.c_str(), args[0]); ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); std::stringstream buffer; buffer << ifs.rdbuf(); delete tmpfile; return buffer.str(); #endif } std::atomic timed_out{false}; unsigned timeout_count = 0; static unsigned timeout = 0; #if ENABLE_TIMEOUT static std::atomic alarm_on{0}; static int child_pid = -1; static void sig_handler(int sig) { if (child_pid == 0) error(2, 0, "received signal %d before starting child", sig); if (sig == SIGALRM && alarm_on) { timed_out = true; if (--alarm_on) { // Send SIGTERM to children. kill(-child_pid, SIGTERM); // Try again later if it didn't work. (alarm() will be reset // if it did work and the call to wait() returns) alarm(2); } else { // After a few gentle tries, really kill that child. kill(-child_pid, SIGKILL); } } else { // forward signal kill(-child_pid, sig); // cleanup files spot::cleanup_tmpfiles(); // and die verbosely error(2, 0, "received signal %d", sig); } } void setup_sig_handler() { struct sigaction sa; sa.sa_handler = sig_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; // So that wait() doesn't get aborted by SIGALRM. sigaction(SIGALRM, &sa, nullptr); // Catch termination signals, so we can kill the subprocess. sigaction(SIGHUP, &sa, nullptr); sigaction(SIGINT, &sa, nullptr); sigaction(SIGQUIT, &sa, nullptr); sigaction(SIGTERM, &sa, nullptr); } static char* get_arg(const char*& cmd) { const char* start = cmd; std::string arg; while (char c = *cmd) { switch (c) { // Those characters can have special meaning for the shell. case '`': case '~': case '|': case ';': case '!': case '?': case '(': case ')': case '[': case ']': case '{': case '}': case '$': case '*': case '&': case '#': case '\\': case '>': case '<': case ' ': case '\n': case '\t': goto end_loop; case '\'': { char d = '\0'; while ((d = *++cmd)) { if (d == '\'') break; arg.push_back(d); } if (d == '\0') return nullptr; } break; case '"': { int d = 0; while ((d = *++cmd)) { if (d == '\"') break; // Backslash can only be used to escape \, $, `, and " if (d == '\\' && strchr("\\$`\"", *cmd)) d = *++cmd; else if (strchr("\\$`", d)) return nullptr; arg.push_back(d); } if (d == 0) return nullptr; } break; default: arg.push_back(c); break; } ++cmd; } end_loop: if (cmd == start) // Not the same as arg.empty() return nullptr; return strndup(arg.c_str(), arg.size()); } static void skip_ws(const char*& cmd) { while (isspace(*cmd)) ++cmd; } // Commands are run via 'sh -c' so we get all the expressive power of // the shell. However starting a shell for each translation is slow. // To mitigate that, if the command to run is simple: we run it // directly, bypassing the shell. Our definition of simple is: // - a single command // - can have single or double-quoted arguments // - can have >stderr and & and such) are not support. Chains of commands // (pipes, semi-colons, etc.) are not supported. Envvar definitions // before the command are not supported. struct simple_command { std::vector args; char* stdin_filename = nullptr; char* stdout_filename = nullptr; simple_command(const simple_command& other) = delete; simple_command& operator=(const simple_command& other) = delete; simple_command() = default; simple_command(simple_command&& other) = default; void clear() { for (auto arg: args) free(arg); args.clear(); free(stdin_filename); free(stdout_filename); stdin_filename = nullptr; stdout_filename = nullptr; } ~simple_command() { clear(); } }; static simple_command parse_simple_command(const char* cmd) { simple_command res; const char* start = cmd; while (*cmd) { skip_ws(cmd); switch (*cmd) { case '<': { if (cmd > start && isdigit(cmd[-1])) goto use_shell; ++cmd; skip_ws(cmd); if (res.stdin_filename) free(res.stdin_filename); res.stdin_filename = get_arg(cmd); if (res.stdin_filename == nullptr) goto use_shell; break; } case '>': { if (cmd > start && isdigit(cmd[-1])) goto use_shell; ++cmd; skip_ws(cmd); if (res.stdout_filename) free(res.stdout_filename); res.stdout_filename = get_arg(cmd); if (res.stdout_filename == nullptr) goto use_shell; break; } default: { char* tmp = get_arg(cmd); if (tmp == nullptr) goto use_shell; res.args.push_back(tmp); break; } } } // If result is empty, we failed to found the command; let's see if // the shell is smarter. If the command contains '=', it's unlikely // to be a command, but probably an environment variable definition // as in // FOO=1 command args.. if (res.args.empty() || strchr(res.args[0], '=')) goto use_shell; res.args.push_back(nullptr); return res; use_shell: res.clear(); return res; } #ifndef HAVE_SPAWN_H static void exec_command(const char* cmd) { simple_command res = parse_simple_command(cmd); if (!res.args.empty()) { if (res.stdin_filename) { int fd0 = open(res.stdin_filename, O_RDONLY, 0644); if (fd0 < 0) error(2, errno, "failed to open '%s'", res.stdin_filename); if (dup2(fd0, STDIN_FILENO) < 0) error(2, errno, "dup2() failed"); if (close(fd0) < 0) error(2, errno, "close() failed"); } if (res.stdout_filename) { int fd1 = creat(res.stdout_filename, 0644); if (fd1 < 0) error(2, errno, "failed to open '%s'", res.stdout_filename); if (dup2(fd1, STDOUT_FILENO) < 0) error(2, errno, "dup2() failed"); if (close(fd1) < 0) error(2, errno, "close() failed"); } execvp(res.args[0], res.args.data()); error(2, errno, "failed to run '%s'", res.args[0]); SPOT_UNREACHABLE(); return; } // Try /bin/sh first, because it is faster to not do any PATH // lookup. static bool has_bin_sh = true; if (has_bin_sh) execl("/bin/sh", "sh", "-c", cmd, nullptr); has_bin_sh = false; execlp("sh", "sh", "-c", cmd, nullptr); error(2, errno, "failed to run '%s' via 'sh'", cmd); SPOT_UNREACHABLE(); return; } #endif int exec_with_timeout(const char* cmd) { int status; timed_out = false; #ifdef HAVE_SPAWN_H simple_command res = parse_simple_command(cmd); // Using posix_spawn should be preferred over fork()/exec(). It can // be implemented in more systems. Since 2016, posix_spawn() // should be faster than fork()/exec() on Linux. See also // https://www.microsoft.com/en-us/research/publication/a-fork-in-the-road/ posix_spawnattr_t attr; if (int err = posix_spawnattr_init(&attr)) error(2, err, "posix_spawnattr_init() failed"); if (int err = posix_spawnattr_setpgroup(&attr, 0)) error(2, err, "posix_spawnattr_setpgroup() failed"); if (int err = posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETPGROUP)) error(2, err, "posix_spawnattr_setflags() failed"); posix_spawn_file_actions_t actions; if (int err = posix_spawn_file_actions_init(&actions)) error(2, err, "posix_spawn_file_actions_init() failed"); // Close stdin so that children may not read our input. We had this // nice surprise with Seminator, who greedily consumes its stdin // (which was also ours) even if it does not use it because it was // given a file. if (int err = posix_spawn_file_actions_addclose(&actions, STDIN_FILENO)) error(2, err, "posix_spawn_file_actions_addclose() failed"); if (!res.args.empty()) { if (res.stdin_filename) if (int err = posix_spawn_file_actions_addopen(&actions, STDIN_FILENO, res.stdin_filename, O_RDONLY, 0644)) error(2, err, "posix_spawn_file_actions_addopen() failed"); if (res.stdout_filename) if (int err = posix_spawn_file_actions_addopen(&actions, STDOUT_FILENO, res.stdout_filename, O_CREAT | O_WRONLY | O_TRUNC, 0644)) error(2, err, "posix_spawn_file_actions_addopen() failed"); if (int err = posix_spawnp(&child_pid, res.args[0], &actions, &attr, res.args.data(), environ)) error(2, err, "failed to run '%s'", res.args[0]); } else { // Try /bin/sh first, because it is faster to not do any PATH // lookup. static bool has_bin_sh = true; if (has_bin_sh) { const char* args[] = { "/bin/sh", "-c", cmd, nullptr }; if (posix_spawn(&child_pid, args[0], &actions, &attr, const_cast(args), environ)) has_bin_sh = false; } if (!has_bin_sh) { const char* args[] = { "sh", "-c", cmd, nullptr }; if (int err = posix_spawnp(&child_pid, args[0], &actions, &attr, const_cast(args), environ)) error(2, err, "failed to run '%s' via 'sh'", cmd); } } if (int err = posix_spawn_file_actions_destroy(&actions)) error(2, err, "posix_spawn_file_actions_destroy() failed"); if (int err = posix_spawnattr_destroy(&attr)) error(2, err, "posix_spawnattr_destroy() failed"); #else child_pid = fork(); if (child_pid == -1) error(2, errno, "failed to fork()"); if (child_pid == 0) { setpgid(0, 0); // Close stdin so that children may not read our input. We had // this nice surprise with Seminator, who greedily consumes its // stdin (which was also ours) even if it does not use it // because it was given a file. close(STDIN_FILENO); exec_command(cmd); // never reached return -1; } #endif alarm(timeout); // Upon SIGALRM, the child will receive up to 3 // signals: SIGTERM, SIGTERM, SIGKILL. alarm_on = 3; int w = waitpid(child_pid, &status, 0); alarm_on = 0; if (w == -1) error(2, errno, "error during wait()"); alarm(0); return status; } #endif // ENABLE_TIMEOUT enum { OPT_LIST = 1, OPT_RELABEL = 2, }; static const argp_option options[] = { /**************************************************/ { nullptr, 0, nullptr, 0, "Specifying translators to call:", 2 }, { "translator", 't', "COMMANDFMT", 0, "register one translator to call", 0 }, { "timeout", 'T', "NUMBER", 0, "kill translators after NUMBER seconds", 0 }, { "list-shorthands", OPT_LIST, nullptr, 0, "list availabled shorthands to use in COMMANDFMT", 0}, { "relabel", OPT_RELABEL, nullptr, 0, "always relabel atomic propositions before calling any translator", 0 }, /**************************************************/ { nullptr, 0, nullptr, 0, "COMMANDFMT should specify input and output arguments using the " "following character sequences:", 3 }, { "%f,%s,%l,%w", 0, nullptr, OPTION_DOC | OPTION_NO_USAGE, "the formula as a (quoted) string in Spot, Spin, LBT, or Wring's syntax", 0 }, { "%F,%S,%L,%W", 0, nullptr, OPTION_DOC | OPTION_NO_USAGE, "the formula as a file in Spot, Spin, LBT, or Wring's syntax", 0 }, { "%O", 0, nullptr, OPTION_DOC | OPTION_NO_USAGE, "the automaton output in HOA, never claim, LBTT, or ltl2dstar's " "format", 0 }, { "%%", 0, nullptr, OPTION_DOC | OPTION_NO_USAGE, "a single %", 0 }, { nullptr, 0, nullptr, 0, "If either %l, %L, or %T are used, any input formula that does " "not use LBT-style atomic propositions (i.e. p0, p1, ...) will be " "relabeled automatically. Likewise if %s or %S are used with " "atomic proposition that compatible with Spin's syntax. You can " "force this relabeling to always occur with option --relabel.\n" "The sequences %f,%s,%l,%w,%F,%S,%L,%W can optionally be \"infixed\"" " by a bracketed sequence of operators to unabbreviate before outputting" " the formula. For instance %[MW]f will rewrite operators M and W" " before outputting it.\n" "Furthermore, if COMMANDFMT has the form \"{NAME}CMD\", then only CMD " "will be passed to the shell, and NAME will be used to name the tool " "in the output.", 4 }, { nullptr, 0, nullptr, 0, nullptr, 0 } }; bool opt_relabel = false; static int parse_opt_trans(int key, char* arg, struct argp_state*) { // Called from C code, so should not raise any exception. BEGIN_EXCEPTION_PROTECT; switch (key) { case 't': tools_push_trans(arg); break; case 'T': timeout = to_pos_int(arg, "-T/--timeout"); #if !ENABLE_TIMEOUT std::cerr << "warning: setting a timeout is not supported " << "on your platform" << std::endl; #endif break; case OPT_LIST: show_shorthands(std::begin(shorthands_ltl), std::end(shorthands_ltl)); std::cout << ("\nAny {name} and directory component is skipped for the purpose " "of\nmatching those prefixes. So for instance\n " "'{DRA} ~/mytools/ltl2dstar-0.5.2'\n" "will be changed into\n " "'{DRA} ~/mytools/ltl2dstar-0.5.2 --output-format=hoa %[MW]L %O'" "\n"); exit(0); case OPT_RELABEL: opt_relabel = true; break; default: return ARGP_ERR_UNKNOWN; } END_EXCEPTION_PROTECT; return 0; } const struct argp trans_argp = { options, parse_opt_trans, nullptr, nullptr, nullptr, nullptr, nullptr }; static const argp_option options_aut[] = { /**************************************************/ { nullptr, 0, nullptr, 0, "Specifying tools to call:", 2 }, { "tool", 't', "COMMANDFMT", 0, "register one tool to call", 0 }, { "timeout", 'T', "NUMBER", 0, "kill tools after NUMBER seconds", 0 }, { "list-shorthands", OPT_LIST, nullptr, 0, "list availabled shorthands to use in COMMANDFMT", 0}, /**************************************************/ { nullptr, 0, nullptr, 0, "COMMANDFMT should specify input and output arguments using the " "following character sequences:", 3 }, { "%H,%S,%L", 0, nullptr, OPTION_DOC | OPTION_NO_USAGE, "filename for the input automaton in (H) HOA, (S) Spin's neverclaim, " "or (L) LBTT's format", 0 }, { "%M, %[val]M", 0, nullptr, OPTION_DOC | OPTION_NO_USAGE, "the name of the input automaton, with an optional default value", 0 }, { "%O", 0, nullptr, OPTION_DOC | OPTION_NO_USAGE, "filename for the automaton output in HOA, never claim, LBTT, or " "ltl2dstar's format", 0 }, { "%%", 0, nullptr, OPTION_DOC | OPTION_NO_USAGE, "a single %", 0 }, { nullptr, 0, nullptr, 0, nullptr, 0 } }; static int parse_opt_autproc(int key, char* arg, struct argp_state*) { switch (key) { case 't': tools_push_autproc(arg); break; case 'T': timeout = to_pos_int(arg, "-T/--timeout"); #if !ENABLE_TIMEOUT std::cerr << "warning: setting a timeout is not supported " << "on your platform" << std::endl; #endif break; case OPT_LIST: show_shorthands(std::begin(shorthands_autproc), std::end(shorthands_autproc)); std::cout << ("\nAny {name} and directory component is skipped for the purpose " "of\nmatching those prefixes. So for instance\n " "'{AF} ~/mytools/autfilt-2.4 --remove-fin'\n" "will be changed into\n " "'{AF} ~/mytools/autfilt-2.4 --remove-fin %H>%O'\n"); exit(0); default: return ARGP_ERR_UNKNOWN; } return 0; } const struct argp autproc_argp = { options_aut, parse_opt_autproc, nullptr, nullptr, nullptr, nullptr, nullptr };