diff --git a/lbtt/ChangeLog b/lbtt/ChangeLog index 0d368e560..f0b8db9e4 100644 --- a/lbtt/ChangeLog +++ b/lbtt/ChangeLog @@ -1,3 +1,744 @@ +2004-07-07 Heikki Tauriainen + + * Version 1.1.0 released. + +2004-07-06 Heikki Tauriainen + + * configure.ac: Make detection of the need for obstack.h + compilation workaround with glibc 2.3.2 more accurate. + Remove check for single_client_alloc. + * src/LbttAlloc.h: Remove conditional definition of + the ALLOC macro. + * src/UserCommandReader.cc: Remove workaround for + compilation with gcc 2.95. + +2004-07-02 Heikki Tauriainen + + * doc/texinfo.tex: New upstream version. + * doc/lbtt.texi: Update to edition 1.1.0. + * NEWS: Update. + +2004-07-02 Heikki Tauriainen + + * src/UserCommandReader.cc (parseCommand): Recognize + `implementations' and `translators' as aliases of the + `algorithms' command. + * src/UserCommands.cc (printCommandHelp): List + `implementations' and `translators' as aliases of the + `algorithms' command. + +2004-06-30 Heikki Tauriainen + + * src/Configuration.h (CommandLineOptionType): Rearrange the + values to list constants with an explicit character value + first. Change the value of OPT_VERSION to `V'. + * src/Configuration.cc (Configuration::read): Treat the single- + character option `V' as an alias of the `--version' option. + (Configuration::showCommandLineHelp): Describe the `-V' option. + * src/translate.cc (main): Rename the single character option + `v' to `V'. + +2004-06-29 Heikki Tauriainen + + * src/BuchiAutomaton.h (BuchiAutomaton::regularize) + (BuchiAutomaton::intersect): Remove. + (BuchiAutomaton::BuchiTransition::print(_, _, _)): Override + the corresponding virtual function in parent class explicitly. + * src/BuchiAutomaton.cc (BuchiAutomaton::regularize) + (BuchiAutomaton::intersect): Remove. + +2004-05-31 Heikki Tauriainen + + * src/LtlFormula.h: Do not include the map header. Include the + set header instead. + (LtlFormula::refcount): New variable. + (LtlFormula::formula_storage): Change type to a set of + pointers to LtlFormulas. + (LtlFormula::LtlFormula): Initialize `this->refcount' to 1. Do + not initialize `info_flags' explicitly (this is always done in + subclasses). + (LtlFormula::destruct): Update the reference counts of formulas + directly. + (LtlFormula::clone, LtlFormula::insertToStorage): Likewise. + (Atom::Atom): Do not call parent class constructor explicitly. + (UnaryFormula::UnaryFormula): Likewise. + (BinaryFormula::BinaryFormula): Likewise. + * src/LtlFormula.cc: Update definition of the static member + `LtlFormula::formula_storage'. + * src/FormulaRandomizer.h: Include the map header. + +2004-05-19 Heikki Tauriainen + + * configure.ac (BIGUINT): Define as unsigned long long int + if the compiler supports it (unsigned long int otherwise). + * src/TestStatistics.h + (TestStatistics::total_number_of_buchi_states) + (TestStatistics::total_number_of_buchi_transitions) + (TestStatistics::total_number_of_acceptance_sets) + (TestStatistics::total_number_of_product_states) + (TestStatistics::total_number_of_product_transitions): + Change type to BIGUINT. + (TestStatistics::total_number_of_msccs): Remove. + (TestStatistics::TestStatistics): Remove initialization + of `total_number_of_msccs'. + * src/StatDisplay.cc (printCollectiveStats): Use BIGUINTs + for referencing total state, transition and acceptance set + counts. + +2004-05-18 Heikki Tauriainen + + * src/ProductAutomaton.h, src/ProductAutomaton.cc, + src/SccIterator.h: Remove. The functionality provided by + these files has been rearranged into more general product + computation and emptiness checking routines in: + * src/Product.h, src/SccCollection.h: New files. + * src/BuchiProduct.h, src/BuchiProduct.cc, src/StateSpaceProduct.h: + New files for providing specializations of the general product + computation operation applicable to Büchi automata and state + spaces. + + * src/Makefile.am: Add BuchiProduct.h, BuchiProduct.cc, + Product.h, SccCollection.h and StateSpaceProduct.h to + lbtt_SOURCES. + Remove ProductAutomaton.h, ProductAutomaton.cc and + SccIterator.h from lbtt_SOURCES. + + * src/Graph.h.in (Graph::EdgeContainerType, Graph::Path): New + type definitions. + (Graph::PathElement): New class. + + * src/PathEvaluator.h (evaluate): Change prototype to accept + a path prefix and a cycle as parameters. + * src/PathEvaluator.cc (evaluate): Rewrite to support the + changed prototype. + + * src/TestRoundInfo.h: Do not include the ProductAutomaton.h + header. + (TestRoundInfo::product_automaton): Remove. + (TestRoundInfo::TestRoundInfo): Remove initialization of + `product_automaton'. + + * src/TestStatistics.h: Do not include the ProductAutomaton.h + header. Include the EdgeContainer.h and Graph.h headers. + (AutomatonStats::number_of_product_states): Change type to + Graph::size_type. + + * src/main.cc (testLoop): Remove references to + `round_info.product_automaton' and `generateProductAutomaton'. + + * src/TestOperations.h (generateProductAutomaton): Remove. + * src/TestOperations.cc: Include the BuchiProduct.h, Product.h, + SccCollection.h and StateSpaceProduct.h headers. Do not + include the ProductAutomaton.h and SccIterator.h headers. + Define static members of + Product and Product. + (generateProductAutomaton): Remove. + (performEmptinessCheck): Display output messages from the + removed `generateProductAutomaton' function here. Do the + same for updating product automaton statistics. Use the + Product interface for constructing the product and checking + it for emptiness. + (performBuchiIntersectionCheck): Use the Product interface + for constructing the intersection of Büchi automata and + checking it for emptiness. + + * src/UserCommandReader.cc: Do not include the + ProductAutomaton.h header. + (executeUserCommands): Remove the variables `product_automaton' + and `last_computed_product_automaton'. + + * src/UserCommands.h: Do not include the ProductAutomaton.h + header. Include the EdgeContainer.h, Graph.h and Product.h + headers. + (computeProductAutomaton): Remove. + (synchronizePrefixAndCycle): Remove. + (printCrossComparisonAnalysisResults): Remove + `product_automaton' and `last_product_automaton' from + prototype. + (printPath): Change the types of `prefix' and `cycle' to + constant references to StateSpace::Path objects. + (printAcceptingCycle). Expect also a prefix and cycle of a + state space as parameters. Represent the path segments as + constant references to {BuchiAutomaton,StateSpace}::Path + objects. + * src/UserCommands.cc: Include the BuchiProduct.h, Product.h + and StateSpaceProduct.h headers. + (computeProductAutomaton): Remove. + (synchronizePrefixAndCycle): Remove. + (printCrossComparisonAnalysisResults): Update parameter list and + documentation. Use the Product interface for constructing and + analyzing a witness for the nonemptiness of a product automaton. + (printAutomatonAnalysisResults): Use the Product interface + for constructing and analyzing a witness for the nonemptiness + of the intersection of two Büchi automata. + (printPath): Update parameter list and documentation. Standardize + the output to resemble that produced by printAcceptingCycle. + (printAcceptingCycle): Update parameter list and documentation. + Display all relevant information about an accepting execution + of a Büchi automaton. + +2004-05-18 Heikki Tauriainen + + * configure.ac (YACC): Do not add `-d' here. + + * src/Makefile.am: Rearrange to make future updates easier. + (BUILT_SOURCES): New variable. + (AM_YFLAGS): New variable for adding the `-d' flag to be + passed to YACC. + Move Config-parse.h and NeverClaim-parse.h from + EXTRA_lbtt_SOURCES and EXTRA_lbtt_translate_SOURCES to + BUILT_SOURCES. + + Thanks to Alexandre Duret-Lutz for patches. + +2004-05-13 Heikki Tauriainen + + * configure.ac: Add checks for the sys/times.h and sys/wait.h + headers. Add checks for the strerror, mkstemp, open, read, write, + close, popen, pclose, pipe, fork, execvp, getpid, waitpid, alarm, + sigaction, sigprocmask, sigemptyset, sigaddset, times, sysconf, + and strsignal library functions. Remove check for the return type + of signal. + +2004-05-13 Heikki Tauriainen + + * src/NeverClaim-lex.ll: Add %option nounput to avoid a + compiler warning. + + * src/ExternalTranslator.h: Include the TempFsysName.h header. + (ExternalTranslator::TempFileObject): Remove. + (ExternalTranslator::registerTempFileObject): Change + prototype to match that of TempFsysName::allocate. + (ExternalTranslator::temporary_file_objects): Change to a + stack of pointers to TempFsysName objects. + * src/ExternalTranslator.cc + (ExternalTranslator::registerTempFileObject): Use the + TempFsysName interface for creating temporary names. + (ExternalTranslator::translate): Handle temporary file names + as C-style strings. + (ExternalTranslator::TempFileObject): Remove. + * src/translate.cc: Make `translator' a static variable. + (signalHandler): Delete `translator' if necessary to remove + any temporary files before aborting. Restore the default + signal handler using sigaction. + (installSignalHandler): New function. + (main): Fix the number of dashes in the descriptions of + single-character command line options. + Install handler for signals that terminate the + process. + + * src/Makefile.am: Add TempFsysName.h and TempFsysName.cc to + lbtt_translate_SOURCES. + +2004-05-12 Heikki Tauriainen + + * src/Graph.h.in (Graph::Edge) Fix return + type of targetNode() and the type of the `target_node' + variable to allow compilation with gcc 3.4.0. + (Graph::stats): Fix type definition of + `result' to allow compilation with gcc 3.4.0. + * src/FormulaWriter.h: Include the LtlFormula.h header + to allow compilation with gcc 3.4.0. Remove forward + declarations of class LtlFormula and class Atom. + +2004-03-18 Heikki Tauriainen + + * src/UserCommandReader.cc (executeUserCommands): Print a + newline after each nonempty input line regardless of whether + the line consists only of an external command or not. Don't + print a newline after piping the output of a command to an + external program. Print a newline after the output of a + command. Do not interpret failures when writing to pipe as + errors to allow external programs to ignore some of the input. + + * src/UserCommands.cc (printAlgorithmList) + (printCrossComparisonAnalysisResults) + (printConsistencyAnalysisResults, printAutomatonAnalysisResults) + (printAcceptingCycle, printBuchiAutomaton, evaluateFormula) + (printCommandHelp, printInconsistencies, printStateSpace): + Remove extra newlines from the end of output. + + * src/StatDisplay.cc (printBuchiIntersectionCheckStats): + Remove extra newlines from the end of output. + + * src/TestOperations.cc (performBuchiIntersectionCheck): Print + a newline after the results to transcript file and to console + in verbosity modes >= 3. + +2004-03-17 Heikki Tauriainen + + * src/BuchiAutomaton.h: Include the cstdlib header. + Fix description of the BuchiAutomaton class. + (BuchiAutomaton::connect): Add parameters for the acceptance + sets associated with a transition. + (BuchiAutomaton::BuchiTransition::BuchiTransition): Add + parameters for the acceptance sets associated with a transition. + Remove copy constructor. + (BuchiAutomaton::BuchiTransition::print): Remove default values + for arguments. Add parameter for the number of acceptance sets + associated with a transition. + (BuchiAutomaton::BuchiTransition::acceptanceSets) New functions. + (BuchiAutomaton::BuchiTransition::acceptance_sets): New + variable. + + * src/BuchiAutomaton.cc + (BuchiAutomaton::BuchiAutomaton(const BuchiAutomaton&): Copy + also the acceptance sets of each transition. + (BuchiAutomaton::operator=): Copy also the acceptance sets of + each transition. + (BuchiAutomaton::regularize): Handle also acceptance sets on + transitions. + (BuchiAutomaton::read): Update description. Accept automata with + acceptance sets on states and/or transitions. + (BuchiAutomaton::intersect): Take care of acceptance sets + associated with transitions when forming the intersection of two + automata. + (BuchiAutomaton::print): Change output to refer to "acceptance + sets" instead of "sets of accepting states". Pass the number of + acceptance sets to BuchiAutomaton::BuchiTransition::print when + displaying the automaton in dot format. + (BuchiAutomaton::BuchiTransition::print): List the acceptance + sets associated with each transition in all output modes. + (BuchiAutomaton::BuchiState::print): Pass the number of + acceptance sets to BuchiAutomaton::BuchiTransition::print. + +2004-03-15 Heikki Tauriainen + + * src/UserCommands.h (printFormula): Change prototype to accept + the vector of input tokens as an additional parameter. + * src/UserCommands.cc (printFormula): Accept the vector of + input tokens as an additional parameter. Use the optional + parameter "nnf" or "normal" to choose the display mode. + Display only the formula to accommodate feeding the output (if + redirected to a file) back to lbtt using the --formulafile + command line option. + (printCommandHelp): Update the description of the `formula' + command. + + * src/UserCommandReader.cc (executeUserCommands): Accept one + optional parameter for the `formula' command. Pass the input + tokens as an additional parameter in the printFormula call. + Print an additional newline after the call if the output is + not redirected to a file. + +2004-03-12 Heikki Tauriainen + + * src/Exception.h: Include the istream header. + (Exceptional_istream::get, Exceptional_istream::read): New + functions. + + * src/Ltl-parse.yy: New file. + + * src/LtlFormula.h (parseFormula): Declare. + (LtlFormula::read(istream&)): Read the formula by calling + parseFormula (provided by Ltl-parse.yy). + (LtlFormula::read(Exceptional_istream&)): Remove. + * src/LtlFormula.cc (LtlFormula::read(Exceptional_istream&)): + Remove. + + * src/Makefile.am: Add Ltl-parse.yy to lbtt_SOURCES and + lbtt_translate_SOURCES. + +2004-03-08 Heikki Tauriainen + + * src/TempFsysName.h, src/TempFsysName.cc New files. + * src/Makefile.am: Add TempFsysName.h and TempFsysName.cc to + lbtt_SOURCES. + + * src/TestRoundInfo.h: Include the TempFsysName.h header. + (TestRoundInfo::formula_file_name[]) + (TestRoundInfo::automaton_file_name) + (TestRoundInfo::{cout,cerr}_capture_file): Change type to + TempFsysName*. + (TestRoundInfo::TestRoundInfo): Initialize temporary file + name pointers to 0. + + * src/main.cc: Include the TempFsysName.h header. + (allocateTempFilenames, deallocateTempFilenames): New functions. + (abortHandler): New signal handler. + (installSignalHandler): New function. + (testLoop): Do not explicitly remove the temporary formula files. + (main): Use the above functions for allocating and deallocating + names for temporary files. Handle signals that terminate the + process by cleaning up temporary files before aborting; however, + for SIGINT, do this only if user breaks are not handled by lbtt. + + * src/TestOperations.h (removeFile): Rename as truncateFile. + * src/TestOperations.cc: Include the TempFsysName.h header. + (removeFile): Rename as truncateFile; truncate file instead of + removing it. + (writeFormulaeToFiles): Use the TempFsysName interface for + referring to the names of temporary files. + (generateBuchiAutomaton): Truncate all temporary files before + exec'ing an external process. Use the TempFsysName interface + for referring to the names of temporary files. + + * src/ExternalTranslator.cc: Include the cstring header. + (ExternalTranslator::TempFileObject::TempFileObject): Use + mkstemp instead of tmpnam to allocate a name for a temporary + file. + +2004-03-08 Heikki Tauriainen + + * src/StringUtil.h (findInQuotedString): New function prototype. + * src/StringUtil.cc (findInQuotedString): New function. + + * src/UserCommandReader.cc (executeUserCommands): + [HAVE_READLINE] Add the input line to history before extracting + the external command from the line. + Ignore white space within quotes when splitting input line into + tokens. + Move parsing the algorithm identifiers given for the + `buchianalysis' command to + src/UserCommands.cc (printAutomatonAnalysisResults). + (parseRedirection): Explicitly unquote the file name. + + * src/UserCommands.h: Include the map and the IntervalList.h + headers. + (parseAlgorithmId, parseAlgorithmIdList): New function prototypes. + (printAutomatonAnalysisResults): Change function prototype to + accept a list of tokens instead of two algorithm identifiers. + + * src/ProductAutomaton.h: Include the string header. Rewrite + the friend declaration of + UserCommands::printAutomatonAnalysisResults. + + * src/UserCommands.cc (parseAlgorithmId, parseAlgorithmIdList): + New functions. + (printCrossComparisonAnalysisResults) + (printConsistencyAnalysisResults): Parse algorithm identifier + using the parseAlgorithmId function. + (printAutomatonAnalysisResults): Parse algorithm identifiers + using the parseAlgorithmId function. Throw a CommandErrorException + in the case one of the algorithm identifiers refers to lbtt's + internal model checking algorithm. + (printBuchiAutomaton): Parse algorithm identifier using the + parseAlgorithmId function. Throw a CommandErrorException in the + case the identifier refers to lbtt's internal model checking + algorithm. + (printCommandHelp): Do not treat the internal model checking + algorithm as a special case in command descriptions. + (evaluateFormula, printInconsistencies, printTestResults) + (printStateSpace, changeAlgorithmState): Parse lists of + algorithms and states using the parseAlgorithmIdList and the + StringUtil::parseIntervalList functions, respectively. Access the + numeric algorithm and state identifiers via IntervalLists. Make + error messages more consistent. + +2004-03-07 Heikki Tauriainen + + * src/StatDisplay.h: Include the IntervalList.h header. + (printStatTableHeader): New function prototype. + (printCrossComparisonStats, printBuchiIntersectionCheckStats): + Change the function prototypes to accept a reference to an + IntervalList for the algorithm identifiers. + * src/StatDisplay.cc (printStatTableHeader): New function. + * src/StatDisplay.cc (printBuchiAutomatonStats) + (printProductAutomatonStats, printAcceptanceCycleStats) + (printConsistencyCheckStats): Display statistics in a table in + verbosity mode <= 2. + (printCrossComparisonStats): Iterate over the set of algorithms + using an IntervalList. Do not handle the internal model checking + algorithm as a special case. + (printBuchiIntersectionCheckStats) Iterate over the set of + algorithms using an IntervalList. Skip the internal model + checking algorithm when displaying automata generation or + intersection check statistics. + (printAllStats): Display statistics in a table in verbosity + mode <= 2. + (printCollectiveStats): Skip the internal algorithm when + displaying the rows of the cross-comparison result table. Use + the changed semantics of Configuration::AlgorithmInformation + to find the name of an implementation. + + * src/main.cc (testLoop): Display test statistics in a table in + verbosity modes 1 and 2. + + * src/TestOperations.cc: Include the IntervalList.h header. + Remove inclusion of the TestStatistics.h header. + (generateBuchiAutomaton, generateProductAutomaton) + (performEmptinessCheck): Display the test statistics in a table + in verbosity modes 1 and 2. + (compareResults, performBuchiIntersectionCheck): Display the + result of the test also in verbosity mode 2. Show test statistics + using the changed semantics of + StatDisplay::printCrossComparisonStats and + StatDisplay::printBuchiIntersectionCheckStats. + (verifyFormulaOnPath, generateProductAutomaton) + (performEmptinessCheck, performBuchiIntersectionCheck): More + consistent error messages when the test is aborted. + + * src/UserCommands.cc (evaluateFormula): Use the changed + semantics of Configuration::AlgorithmInformation to find the + name of an implementation. + (printTestResults): Display the test results using the changed + semantics of StatDisplay::printCrossComparisonStats and + StatDisplay::printBuchiIntersectionCheckStats. + +2004-03-04 Heikki Tauriainen + + * src/TestOperations.h (openFile(const char*, int&, int, int)): + New function prototype. + * src/TestOperations.cc: Include the csignal, cstring, cerrno, + [HAVE_SYS_TYPES_H] sys/types.h, [HAVE_SYS_STAT_H] sys/stat.h, + [HAVE_FCNTL_H] fcntl.h, and [HAVE_SYS_WAIT_H] sys/wait.h headers. + (timeout): New variable. + (timeoutHandler): New function. + (openFile(const char*, int&, int, int)): New function. + (verifyFormulaOnPath): Fix references to the internal model + checking algorithm. + (generateBuchiAutomaton): Execute external program using fork/exec + instead of `system' to improve the detection of the case when an + external program was terminated by a signal (the behavior of + "system" in the presence of signals varies between architectures). + Handle timeouts when running the external programs. Discard I/O + exceptions while displaying the stdout/stderr capture file. + (performBuchiIntersectionCheck): Skip the internal model checking + algorithm when iterating over algorithms. + +2004-03-04 Heikki Tauriainen + + * src/main.cc (breakHandler): Make function static and change + return type to void. + (testLoop): Do not add the internal model checking algorithm to + the list of implementations (it is done in Configuration::read). + Apply the internal model checking algorithm if it is enabled. + Otherwise skip the automata generation loop as it is not + applicable for this algorithm. + (main): Enable interrupt handler only if mandated by the program + configuration. Use sigaction instead of signal to register the + signal handler. + +2004-03-04 Heikki Tauriainen + + * src/Config-lex.ll, src/Config-parse.yy, src/Configuration.h, + src/Configuration.cc: Rewrite the configuration file parser to + allow reusing a common set of functions for changing the various + settings independently of whether the settings were given in the + configuration file or as command-line options. Rearrange the + structure of Configuration::AlgorithmInformation, remove the + "nopause" and "pauseonbreak" command line options (and make + "pause" an alias of "interactive"), recognize lists of + interactivity modes (with the new mode "onbreak") both in the + configuration file and command line, and add the new + configuration setting "translatortimeout". In more detail, + the changes are: + + * src/Config-lex.ll: Do not include any standard headers. Add + %option nounput to suppress a compiler warning. Accept + "Implementation" or "Translator" as an alias of the Algorithm + block identifier. Do not accept newlines in "option = value" + strings. Do not interpret option values in the lexer. Make + specifying option values more flexible: quotes are now needed + only for values including white space. Both single and double + quotes may be used (as before, \ works as an escape character in + double quotes). Reject values with unmatched quotes. Remove the + getCharacter function (add rules for matching unknown tokens in + full). + + * src/Config-parse.yy: Do not include the cstdio header. Add + namespace StringUtil to the current namespace. + ([declarations]) Change %union declaration to make all semantic + values constant C-style strings. Remove tokens CFG_TRUTH_VALUE, + CFG_INTERACTIVITY_VALUE, CFG_FORMULA_MODE_VALUE, + CFG_STATESPACE_MODE_VALUE, CFG_PRODUCT_TYPE_VALUE, CFG_INTEGER, + CFG_REAL, CFG_INTEGER_INTERVAL and CFG_STRING_CONSTANT. Add + new token CFG_VALUE and nonterminal equals_value. + (algorithm_information): Replaced with `algorithm_name', + `algorithm_path', `algorithm_parameters' and `algorithm_enabled'. + (current_block_type, current_option_type): Removed. + (yyerror): Make parameter const. Remove the loop for reading the + remainder of the current token (the token is recognized in full + directly by the lexer). Remove error messages for all tokens in + the above list. + (checkIntegerRange, checkProbability, isLocked): Removed. + ([grammar rule `equals_value']): New rule. + ([grammar rules for the "Algorithm" configuration file section]): + Read the implementation information into `algorithm_name', + `algorithm_path', `algorithm_parameters' and `algorithm_enabled'. + Use the functions provided by the Configuration class to update + the configuration. + ([grammar rules for the other configuration file sections]): Use + the functions provided by the Configuration class to update the + configuration. Remove the rules `formula_size' and + `statespace_size'. + + * src/Configuration.h: Include the cstdio header. Add + declarations for variables and functions provided by the parser. + Declare yyparse() as a friend. + (Configuration::AlgorithmInformation::path_to_program) + (Configuration::IntPair, Configuration::locked_options) + (Configuration::GENERATION_RANGE, Configuration::PRIORITY_RANGE) + (Configuration::PROPOSITION_COUNT_RANGE) + (Configuration::FORMULA_SIZE_RANGE) + (Configuration::FORMULA_MAX_SIZE_RANGE) + (Configuration::STATESPACE_SIZE_RANGE) + (Configuration::STATESPACE_MAX_SIZE_RANGE) + (Configuration::OPT_NOCOMPARISONTEST) + (Configuration::OPT_NOCONSISTENCYTEST) + (Configuration::OPT_NOINTERSECTIONTEST) + (Configuration::OPT_NOABBREVIATEDOPERATORS) + (Configuration::OPT_PAUSE, Configuration::OPT_NOPAUSE) + (Configuration::OPT_PAUSEONERROR): + Remove. + (Configuration::AlgorithmInformation::extra_parameters): Redefine + as `char** parameters'. + (Configuration::AlgorithmInformation::num_parameters) + (Configuration::GlobalConfiguration::handle_breaks) + (Configuration::GlobalConfiguration::translator_timeout) + (Configuration::algorithm_names: New variables. + (Configuration::IntegerRange): Change the type of lower and upper + bounds to unsigned long int. Make `error_message' const. + (Configuration::DEFAULT_RANGE, Configuration::RANDOM_SEED_RANGE) + (Configuration::ATOMIC_PRIORITY_RANGE) + (Configuration::OPERATOR_PRIORITY_RANGE): New IntegerRange + declarations. + (Configuration::OPT_TRANSLATORTIMEOUT): New OPT_ constant. + (Configuration::registerAlgorithm, Configuration::readProbability) + (Configuration::readSize, Configuration::readTruthValue) + (Configuration::readInteractivity, Configuration::readProductType) + (Configuration::readFormulaMode, Configuration::readStateSpaceMode) + (Configuration::readTranslatorTimeout): New function + prototypes. + (Configuration::isInternalAlgorithm): New function. + (Configuration::readInteger): New template function. + + * src/Configuration.cc: Do not include the cstdio and + Config-parse.h headers. Include the IntervalList.h header. Remove + declarations for checkIntegerRange and checkProbability. Move + remaining extern declarations to Configuration.h. + (Configuration::GENERATION_RANGE, Configuration::PRIORITY_RANGE) + (Configuration::PROPOSITION_COUNT_RANGE) + (Configuration::FORMULA_SIZE_RANGE) + (Configuration::FORMULA_MAX_SIZE_RANGE) + (Configuration::STATESPACE_SIZE_RANGE) + (Configuration::STATESPACE_MAX_SIZE_RANGE) + (Configuration::parseCommandLineInteger): Remove. + (Configuration::DEFAULT_RANGE, Configuration::RANDOM_SEED_RANGE) + (Configuration::ATOMIC_PRIORITY_RANGE) + (Configuration::OPERATOR_PRIORITY_RANGE): New IntegerRange + definitions. + (Configuration::ROUND_COUNT_RANGE): Change upper bound to + ULONG_MAX. + (Configuration::~Configuration): Use the changed semantics + of Configuration::AlgorithmInformation. + (Configuration::read): The + --{comparison,consistency,intersection}{test,check} and + --abbreviatedoperators command line options now take an optional + argument. + The options + --no{{comparison,consistency,intersection}{test,check}, + abbreviatedoperators} return the same OPT_ constant as the + corresponding positive option. + The --interactive and --pause options take an optional argument. + Remove the --nopause and --pauseonerror command line options. + Store the command line parameters into a vector of + pairs (preprocess the special options --configfile, --formulafile, + --help, --logfile, --showconfig, --showoperatordistribution, + --skip and --version; preprocess also the options accepting an + optional argument). Then read the configuration file. Add the + internal model checking algorithm to the set of algorithms if + necessary here instead of in src/main.cc (testLoop). Finally + process the parameter vector again to override any settings made + in the configuration file. + Use the new functions for modifying the configuration. + Allow symbolic algorithm identifiers for the options --enable + and --disable. + Make error messages more consistent. + Exit with the status 2 if an unknown command line option was + encountered or an argument for an option was missing. + (Configuration::print): Tell whether the user break handler is + enabled; tell also the value of the timeout for translators. + (Configuration::algorithmString): Use the new structure of + Configuration::AlgorithmInformation. + (Configuration::showCommandLineHelp): Rewrite the descriptions of + the --{comparison,consistency,intersection}test, + --abbreviatedoperators, --interactive and --pause command line + options. Correct typos in the description of the + --abbreviatedoperators option. Remove descriptions of the + --nopause and --pauseonerror options. + (Configuration::reset): Initialize the values of + `global_options.handle_breaks' and + `global_options.translator_timeout' to false and 0, respectively. + (Configuration::registerAlgorithm): New function for adding a + new implementation to the configuration. The names of + implementations should be unique and different from the reserved + name "lbtt". + (Configuration::readProbability, Configuration::readSize) + (Configuration::readTruthValue, Configuration::readInteractivity) + (Configuration::readProductType, Configuration::readFormulaMode) + (Configuration::readStateSpaceMode) + (Configuration::readTranslatorTimeout): New functions. The + interactivity can now be specified using a comma-separated list + of modes that may include the new keyword "onbreak". + +2004-03-02 Heikki Tauriainen + + * src/StringUtil.h (toLowerCase, unquoteString) + (substituteInQuotedString, parseTime): New function prototypes. + (QuoteMode): New enumeration type. + * src/StringUtil.cc: Include the cctype header. + (toLowerCase, interpretSpecialCharacters, unquoteString) + (substituteInQuotedString, parseTime): New functions. + +2004-02-19 Heikki Tauriainen + + * src/TestRoundInfo.h (TestRoundInfo::all_tests_successful): New + variable. + (TestRoundInfo::TestRoundInfo): Initialize the value of + all_tests_successful to true. + + * src/main.cc (testLoop): Update the value of + round_info.all_tests_successful to indicate the occurrence of an + error. Use the value of this variable as return value from + testLoop. + (main): Return 1 if an error occurred during testing. Return 2 if + there was an error in program configuration; remove extra space + after colon in output. Return 3 if an unexpected exception + occurred. In this case print an additional newline before the + error message. + +2004-02-19 Heikki Tauriainen + + * src/StringUtil.h: Include the IntervalList.h header. + (IntervalStringType): New enumeration type for describing the + boundedness of interval strings. + (parseInterval): New function prototype. Renamed the old prototype + to `parseIntervalList'. + * src/StringUtil.cc: Include the climits header. + (parseNumber): Be more strict about ensuring that the numbers are + positive. + (parseInterval): New function for parsing interval strings. + (parseIntervalList): Use the `parseInterval' function as a + subroutine when parsing a list of intervals. Add a parameter for + optionally storing tokens not recognized as intervals into a + vector of strings. Store the result into an IntervalList. Throw an + IntervalRangeException if an interval does not fit within the + given bounds. + +2004-02-18 Heikki Tauriainen + + * src/IntervalList.h, src/IntervalList.cc: New files. + * src/Makefile.am: Add IntervalList.h and IntervalList.cc to + lbtt_SOURCES. + +2004-02-17 Heikki Tauriainen + + * src/TestOperations.cc (writeFormulaeToFiles): Make the order of + debugging messages (verbosity level 5) more consistent. + +2004-02-15 Heikki Tauriainen + + * src/DispUtil.h (printText): Move function declarations to their + correct place (out of struct StreamFormatting). + +2004-02-15 Heikki Tauriainen + + * configure.ac: Require Autoconf 2.59. + Remove use of deprecated macros. + --with-readline-prefix, --with-readline-includes, + --with-readline-libs: New options. + 2004-02-13 Heikki Tauriainen * src/BitArray.{h,cc}, src/BuchiAutomaton.{h,cc}, @@ -12,6 +753,8 @@ src/translate.{h,cc}, src/UserCommandReader.{h,cc}, src/UserCommands.{h,cc}: Remove all #pragmas. + * Version 1.0.3 released. + 2004-02-12 Heikki Tauriainen * doc/texinfo.tex: New upstream version. diff --git a/lbtt/INSTALL b/lbtt/INSTALL index a4b34144d..54caf7c19 100644 --- a/lbtt/INSTALL +++ b/lbtt/INSTALL @@ -1,4 +1,4 @@ -Copyright 1994, 1995, 1996, 1999, 2000, 2001, 2002 Free Software +Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. This file is free documentation; the Free Software Foundation gives diff --git a/lbtt/NEWS b/lbtt/NEWS index 574e07d73..4abdfd71b 100644 --- a/lbtt/NEWS +++ b/lbtt/NEWS @@ -1,4 +1,4 @@ -lbtt NEWS -- history of user-visible changes. 13 Feb 2004 +lbtt NEWS -- history of user-visible changes. 7 Jul 2004 Copyright (C) 2004 Heikki Tauriainen Permission is granted to anyone to make or distribute verbatim copies @@ -12,6 +12,136 @@ Copyright (C) 2004 Heikki Tauriainen Please send bug reports to . +Version 1.1.0 + +* File formats + + - The file format for automata description files has changed to + accommodate automata with acceptance conditions on both states + and transitions. The old format for automata remains supported + with the restriction that each guard formula of a transition + should be followed by a newline (with optional preceding white + space). + + - In addition to the prefix format for LTL formulas, the input + files used with the `--formulafile' command line option may now + contain formulas in a variety of other formats, such as in the + infix format used by lbtt for log messages, together with formats + used by some LTL-to-Büchi translator implementations (Spin, + LTL2BA, LTL2AUT, Temporal Massage Parlor, Wring, Spot, LBT). + These formats can also be used for guard formulas in automaton + description files (however, lbtt still uses the prefix format in + the input files for the translators). + + Thanks to Alexandre Duret-Lutz for useful suggestions for + enhancements. + +* Support for symbolic names of implementations + + - Beside the numeric identifiers of implementations, lbtt now + accepts also the symbolic names of implementations (as defined in + a configuration file) as parameters for command line options and + internal commands. Consequently, the names of implementations + defined in the configuration file have to be unique. + + - The name `lbtt' is now reserved for lbtt's internal model checking + algorithm and cannot be used as a name for an implementation in + the configuration file. + +* User commands + + - For consistency, numeric intervals in state or implementation + identifier lists can now be specified using either - or ... as a + separator between the bounds of the interval. + + - The user command `formula' now accepts an additional parameter + (`normal' or `nnf') for choosing whether to display a formula in + the form in which it was generated or in negation normal form. + + - The internal model checking algorithm is now referred to with the + keyword "lbtt" instead of "p" as was the case with previous + versions of lbtt. The internal model checking algorithm can now be + enabled or disabled similarly to the external translators. + + - The `consistencyanalysis' and `buchianalysis' commands now show + more information about the accepting runs of Büchi automata to + help examining the runs. (Because of this change, the runs and + witnesses may be longer than in previous versions of lbtt.) + + - The `implementations' and `translators' commands are now recognized + as synonyms of the `algorithms' command. + +* Configuration files + + - Quotes are no longer required for enclosing string values + containing no white space. + + - Numeric intervals in formula or state space sizes can now be + specified using either - or ... as a separator between the bounds + of the interval. + + - The keywords "Implementation" and "Translator" are now recognized + as synonyms of the "Algorithm" block identifier. + +* User interrupts + + Keyboard interrupt handling is now enabled only at explicit request + (if not enabled, lbtt simply aborts on keyboard interrupts). The + interrupt handler is enabled by combining the keyword `onbreak' with + any of the three standard interactivity modes (`always', `never', or + `onerror') in the arguments for the `GlobalOptions.Interactive' + configuration file option or the `--interactive' command line option. + For example, use the command line option + `--interactive=onerror,onbreak' to pause testing in case of an error + or on a user interrupt. + +* Command line options + + - The `--pause' command line option now works identically to the + `--interactive' option. + + - The command-line options `--nopause' and `--pauseonerror' are no + longer supported. Use the `--interactive' or the `--pause' + option instead with an optional argument of a comma-separated list + of interactivity modes (`always', `never', `onerror', `onbreak'). + +* Timeouts + + lbtt now supports specifying a time (in wall-clock time) after + which the execution of a translator is aborted if it has not yet + produced a result. The timeout can be set using either the new + configuration file option `GlobalOptions.TranslatorTimeout' or the + equivalent command line option `--translatortimeout'. Both options + require a parameter of the form [hours]h[minutes]min[seconds]s; for + example, use the command line option `--translatortimeout=1h30min' + to set the timeout at one hour and thirty minutes. + +* Reporting + + - lbtt now reports test statistics also in verbosity modes 1 and 2. + The output of the user command `results' also reflects the active + verbosity mode more accurately. + + - lbtt now exits with status 0 only if no test failures were + detected; otherwise the exit status is either 1 (at least + one failure occurred), 2 (error in program configuration or + command line parameters) or 3 (lbtt exited due to an internal + error). + +* Internal changes + + Due to the changes in the supported file formats, this version + includes a rewrite of the product computation and emptiness checking + algorithms. As this is a major internal change, any information about + unexpected changes in the stability (*) of the tool is welcomed at the + e-mail address given above. + + (*) Unfortunately, the above changes in the source code are known to + cause build problems with GCC 2.95. Therefore, this compiler is no + longer officially supported for building the tool. + +--------------------------------------------------------------------------- + Version 1.0.3 * This release fixes several compilation issues with GNU libc 2.3.2 diff --git a/lbtt/README b/lbtt/README index ad585e237..c400bf804 100644 --- a/lbtt/README +++ b/lbtt/README @@ -1,14 +1,14 @@ -lbtt version 1.0.3 +lbtt version 1.1.0 ------------------ lbtt is a tool for testing programs that translate formulas expressed in propositional linear temporal logic (LTL) into -Büchi automata. The goal of the tool is to assist in the -correct implementation of LTL-to-Büchi translation algorithms -by providing an automated testing environment for LTL-to-Büchi -translators. Additionally, the testing environment can be used -for very basic profiling of different LTL-to-Büchi translators -to evaluate their performance. +Büchi automata. The goal of the tool is to assist implementing +LTL-to-Büchi translation algorithms correctly by providing an +automated testing environment for LTL-to-B@"uchi translators. +Additionally, the testing environment can be used for very basic +profiling of different LTL-to-Büchi translators to evaluate their +performance. The latest version of the program is available at . diff --git a/lbtt/configure.ac b/lbtt/configure.ac index 14c5827b7..52119c3b4 100644 --- a/lbtt/configure.ac +++ b/lbtt/configure.ac @@ -1,11 +1,11 @@ # Process this file with autoconf to produce a configure script. -AC_PREREQ([2.53]) -AC_INIT([lbtt], [1.0.3], [heikki.tauriainen@hut.fi]) -AC_REVISION([Revision: 1.3]) +AC_PREREQ([2.59]) +AC_INIT([lbtt], [1.1.0], [heikki.tauriainen@hut.fi]) +AC_REVISION([Revision: 1.4]) AC_CONFIG_SRCDIR([src/main.cc]) +AC_CONFIG_HEADERS([config.h]) AM_INIT_AUTOMAKE -AM_CONFIG_HEADER([config.h]) @@ -18,35 +18,68 @@ AC_PROG_CXXCPP AM_PROG_LEX AC_PROG_YACC -YACC="${YACC} -d" - -# Check whether the user has explicitly disabled the test for the availability -# of the GNU readline library. +# Check whether the user has explicitly disabled support for the GNU readline +# library. readline=yes +readline_includedir= +readline_libdir= -AC_ARG_WITH([readline], - [AC_HELP_STRING([--without-readline], - [disable check for the GNU readline library])], - [if test x"${withval}" = xno; then readline=no; fi]) +AC_ARG_WITH( + [readline], + [AS_HELP_STRING( + [--without-readline], + [disable support for the GNU readline library (default enable)])], + [if test x"${withval}" = xno; then readline=no; fi]) + +AC_ARG_WITH( + [readline-prefix], + [AS_HELP_STRING( + [--with-readline-prefix=DIR], + [location where GNU readline is installed (optional)])], + [readline_includedir="${withval}/include" + readline_libdir="${withval}/lib"]) + +AC_ARG_WITH( + [readline-includes], + [AS_HELP_STRING( + [--with-readline-includes=DIR], + [location where GNU readline headers are installed (optional)])], + [readline_includedir="${withval}"]) + +AC_ARG_WITH( + [readline-libs], + [AS_HELP_STRING( + [--with-readline-libs=DIR], + [location where GNU readline libraries are installed (optional)])], + [readline_libdir="${withval}"]) + +old_CPPFLAGS=${CPPFLAGS} +old_LDFLAGS=${LDFLAGS} + +if test -n "${readline_includedir}"; then + CPPFLAGS="${CPPFLAGS} -I${readline_includedir}" +fi +if test -n "${readline_libdir}"; then + LDFLAGS="${LDFLAGS} -L${readline_libdir}" +fi # Check for the availability of headers. AC_HEADER_STDC -AC_CHECK_HEADERS([fcntl.h]) +AC_CHECK_HEADERS([libintl.h fcntl.h sys/times.h]) +AC_HEADER_SYS_WAIT # Check for the availability of the GNU readline headers. if test "${readline}" = yes; then rl_history_h="readline/history.h" rl_readline_h="readline/readline.h" - AC_CHECK_HEADERS([${rl_history_h} ${rl_readline_h}], - [], - [readline=no]) + AC_CHECK_HEADERS([${rl_history_h} ${rl_readline_h}], [], [readline=no]) fi AC_LANG([C++]) @@ -55,25 +88,36 @@ AC_LANG([C++]) # version of this header that cannot be compiled using g++; enable a workaround # if necessary. -AC_CHECK_HEADERS([obstack.h], - [AC_MSG_CHECKING([whether obstack.h compilation workaround is needed]) - AC_TRY_COMPILE([#include ], - [#if __GLIBC__ == 2 && __GLIBC_MINOR__ == 3 - obstack_alloc(0, 0); - #endif], - [AC_MSG_RESULT([no])], - [AC_MSG_RESULT([yes]) - AC_DEFINE([GLIBC_OBSTACK_WORKAROUND], - [1], - [Define to 1 to enable an obstack.h C++ compilation workaround for GNU libc 2.3.])])]) +AC_CHECK_HEADERS( + [obstack.h], + [AC_MSG_CHECKING([whether obstack.h compilation workaround is needed]) + old_cxxflags=${CXXFLAGS} + CXXFLAGS="${CXXFLAGS} -Werror" + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM( + [[#include ]], + [[ + #ifdef __GLIBC__ == 2 && __GLIBC_MINOR__ = 3 + obstack_alloc(0, 0); + #endif + ]])], + [AC_MSG_RESULT([no])], + [AC_MSG_RESULT([yes]) + AC_DEFINE( + [GLIBC_OBSTACK_WORKAROUND], + [1], + [Define to 1 to enable an obstack.h C++ compilation workaround for GNU libc 2.3.])]) + CXXFLAGS=${old_cxxflags}]) # Check for the availablility of the sstream or strstream header. -AC_CHECK_HEADERS([sstream], - [], - [AC_CHECK_HEADERS([strstream], - [], - [AC_MSG_ERROR([missing one or more standard C++ headers])])]) +AC_CHECK_HEADERS( + [sstream], + [], + [AC_CHECK_HEADERS( + [strstream], + [], + [AC_MSG_ERROR([missing one or more standard C++ headers])])]) @@ -86,30 +130,35 @@ AC_CHECK_HEADERS([sstream], AC_MSG_CHECKING([for slist]) for slist_header in slist ext/slist no; do if test "${slist_header}" != no; then - AC_TRY_CPP([#include <${slist_header}>], [break]) + AC_PREPROC_IFELSE( + [AC_LANG_SOURCE([[#include <${slist_header}>]])], + [break]) fi done # Try to determine the C++ namespace in which the class slist resides. -# (For example, GCC versions 3.1, 3.1.1 and 3.2 put slist into the -# __gnu_cxx namespace.) +# (For example, GCC versions >= 3.1 put slist into the __gnu_cxx namespace.) if test "${slist_header}" != no; then for slist_namespace in std __gnu_cxx error; do if test "${slist_namespace}" != error; then - AC_TRY_LINK([#include <${slist_header}>], - [${slist_namespace}::slist s;], - [break]) + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM( + [[#include <${slist_header}>]], + [[${slist_namespace}::slist s;]])], + [break]) fi done if test "${slist_namespace}" != error; then AC_MSG_RESULT([header <${slist_header}>, typename ${slist_namespace}::slist]) - AC_DEFINE([HAVE_SLIST], - [1], - [Define to 1 if you have the or header file.]) - AC_DEFINE_UNQUOTED([SLIST_NAMESPACE], - [${slist_namespace}], - [Define as the name of the C++ namespace containing slist.]) + AC_DEFINE( + [HAVE_SLIST], + [1], + [Define to 1 if you have the or header file.]) + AC_DEFINE_UNQUOTED( + [SLIST_NAMESPACE], + [${slist_namespace}], + [Define as the name of the C++ namespace containing slist.]) AC_SUBST([INCLUDE_SLIST_HEADER], ["#include <${slist_header}>"]) else slist_header=no @@ -119,26 +168,21 @@ fi if test "${slist_header}" = no; then AC_MSG_RESULT([no]) -# Check for the availability of the single_client_alloc memory allocator -# available in some compilers supporting the SGI extensions to the Standard -# template library (for example, pre-3.0 versions of gcc). This check is not -# needed if no suitable slist header was found above, since in that case the -# compiler does not support the SGI extensions. - -else - AC_MSG_CHECKING([for single_client_alloc]) - AC_TRY_LINK([#include <${slist_header}>], - [using namespace std; - ${slist_namespace}::slist s;], - [AC_MSG_RESULT([yes]) - AC_DEFINE([HAVE_SINGLE_CLIENT_ALLOC], - [1], - [Define if your C++ compiler supports the single_client_allocator memory allocator.])], - [AC_MSG_RESULT([no])]) fi AC_LANG(C) +AC_CHECK_TYPES( + [unsigned long long int], + [AC_DEFINE( + [BIGUINT], + [unsigned long long int], + [Define to an unsigned integer type supported by your compiler.])], + [AC_DEFINE( + [BIGUINT], + [unsigned long int], + [Define to an unsigned integer type supported by your compiler.])]) + AC_C_CONST AC_C_INLINE @@ -146,12 +190,14 @@ AC_C_INLINE # Checks for library functions. -AC_TYPE_SIGNAL -AC_CHECK_FUNCS([mkdir strchr strtod strtol strtoul getopt_long isatty]) +AC_CHECK_FUNCS( + [strchr strtod strtol strtoul strerror mkdir mkstemp open read write close popen pclose pipe fork execvp getpid waitpid alarm sigaction sigprocmask sigemptyset sigaddset times sysconf], + [], + [AC_MSG_ERROR([missing one of the library functions required for compilation])]) +AC_CHECK_FUNCS([strsignal isatty getopt_long]) if test x"${ac_cv_func_getopt_long}" = xno; then AC_LIBOBJ([getopt]) AC_LIBOBJ([getopt1]) - AC_CHECK_HEADERS([libintl.h]) AC_CHECK_FUNCS([memset]) fi @@ -163,42 +209,56 @@ if test "${readline}" = yes; then for READLINELIBS in "-lreadline" "-lreadline -lcurses" "-lreadline -ltermcap" error; do if test "${READLINELIBS}" != error; then LIBS="${oldlibs} ${READLINELIBS}" - AC_TRY_LINK([#include - #include <${rl_history_h}> - #include <${rl_readline_h}>], - [using_history(); readline(""); add_history("");], - [break]) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[ + #include + #include <${rl_history_h}> + #include <${rl_readline_h}> + ]], + [[using_history(); readline(""); add_history("");]])], + [break]) fi done LIBS=${oldlibs} if test "${READLINELIBS}" != error; then - AC_DEFINE([HAVE_READLINE], - [1], - [Define if you have the GNU readline library.]) + AC_DEFINE( + [HAVE_READLINE], + [1], + [Define if you have the GNU readline library.]) AC_SUBST([READLINELIBS]) AC_MSG_RESULT([${READLINELIBS}]) else AC_MSG_RESULT([no suitable libraries found, readline support disabled]) READLINELIBS="" + readline=no fi fi +if test "${readline}" = no; then + CPPFLAGS=${old_CPPFLAGS} + LDFLAGS=${old_LDFLAGS} +fi + # Check for the availability of the `rand48' family of random number # generation functions. have_rand48=yes -AC_CHECK_FUNCS([srand48 lrand48 seed48], - [], - [have_rand48=no - AC_CHECK_FUNCS([rand srand], - [], - [AC_MSG_ERROR([missing library functions for random number generation])])]) +AC_CHECK_FUNCS( + [srand48 lrand48 seed48], + [], + [have_rand48=no + AC_CHECK_FUNCS( + [rand srand], + [], + [AC_MSG_ERROR([missing library functions for random number generation])])]) if test "${have_rand48}" = yes; then - AC_DEFINE([HAVE_RAND48], - [1], - [Define if you have the `rand48' family of random number generation functions.]) + AC_DEFINE( + [HAVE_RAND48], + [1], + [Define if you have the `rand48' family of random number generation functions.]) fi diff --git a/lbtt/doc/lbtt.texi b/lbtt/doc/lbtt.texi index ae5b65b91..1d0064048 100644 --- a/lbtt/doc/lbtt.texi +++ b/lbtt/doc/lbtt.texi @@ -64,7 +64,7 @@ under the above conditions for modified versions. @title @command{lbtt} @subtitle LTL-to-B@"uchi Translator Testbench -@subtitle @today, @command{lbtt} Versions 1.0.x +@subtitle @today, @command{lbtt} Versions 1.1.x @author Heikki Tauriainen <@email{heikki.tauriainen@@hut.fi}> @page @vskip 0pt plus 1filll @@ -103,8 +103,8 @@ under the above conditions for modified versions. for translating propositional linear temporal logic formulas into B@"uchi automata. -This is edition 1.0.2 of the @command{lbtt} documentation. This edition -applies to @command{lbtt} versions 1.0.x. +This is edition 1.1.0 of the @command{lbtt} documentation. This edition +applies to @command{lbtt} versions 1.1.x. @command{lbtt} is free software, you may change and redistribute it under the terms of the GNU General Public License. @command{lbtt} @@ -148,7 +148,7 @@ comes with NO WARRANTY. See @ref{Copying} for details. @chapter Overview @cindex model checking -@command{lbtt} is a tool for automatic testing of programs which translate +@command{lbtt} is a tool for testing programs that translate formulas expressed in propositional linear temporal logic (@dfn{LTL}) into B@"uchi automata. These finite-state automata over infinite words are used e.g.@: in automata-theoretic model checking @@ -163,7 +163,7 @@ where they can help in detecting errors in the specifications of finite-state hardware or software systems. Usually the model checking procedure involves first composing an automaton with a formal model of a given system, and the result of the -composition reveals whether any execution path of the system violates +composition reveals whether any computation path of the system violates some property that the automaton represents. (For an introduction to model checking techniques in general, see, for example, @@ -174,12 +174,14 @@ for example, [CGP99].) @end iftex -The property to be model checked is often specified as an +The property to be model checked can be specified as an LTL formula, and the B@"uchi automaton used for model checking is obtained -automatically from the formula with a translation algorithm. (Detailed -descriptions and optimization techniques for such algorithms can be found, for -example, in +automatically from the formula with a translation algorithm. +(For descriptions and optimization techniques for such algorithms, see the +references, for example, @ifnottex +@ref{[VW86]}, +@ref{[Isl94]}, @ref{[GPVW95]}, @ref{[Cou99]} @ref{[DGV99]}, @@ -187,20 +189,33 @@ example, in @ref{[SB00]}, @ref{[EH00]}, @ref{[EWS01]}, -@ref{[GO01]}.) +@ref{[GO01]}, +@ref{[Gei01]}, +@ref{[Sch01]}, +@ref{[Wol01]}, +@ref{[Ete02]}, +@ref{[GL02]}, +@ref{[GSB02]}, +@ref{[Thi02]}, +@ref{[Fri03]}, +@ref{[GO03]}, +@ref{[Lat03]}, +@ref{[ST03]}.) @end ifnottex @iftex -[GPVW95, Cou99, DGV99, Ete99, SB00, EH00, EWS01, GO01].) +[VW86, Isl94, GPVW95, Cou99, DGV99, Ete99, SB00, EH00, EWS01, GO01, +Gei01, Sch01, Wol01, Ete02, GL02, GSB02, Thi02, Fri03, GO03, Lat03, ST03].) @end iftex -In practice, the implementation correctness of such a translation algorithm -is crucial to the soundness of the model checking procedure. +In practice, ensuring the correctness of the implementation of such a +translation algorithm is crucial to guarantee the soundness of the +implementation of a model checking procedure. -The goal of @command{lbtt} is to assist in the correct implementation of -LTL-to-B@"uchi translation algorithms by providing an automated +The goal of @command{lbtt} is to assist implementing LTL-to-B@"uchi +translation algorithms correctly by providing an automated testing environment for LTL-to-B@"uchi translators. Testing consists of running LTL-to-B@"uchi translators on randomly generated (or user-specified) LTL formulas as input and then performing simple consistency checks on the -resulting automata to test whether the translators seem to operate correctly +resulting automata to test whether the translators seem to function correctly in practice. (See @ifnottex @ref{[TH02]} @@ -208,9 +223,9 @@ in practice. (See @iftex [TH02] @end iftex -for more information on the theory behind the testing methods.) If the tests -suggest an implementation -error in a translator, @command{lbtt} can generate sample data which causes +for more information on the theory behind the testing methods.) If the test +results suggest that there is an error in an implementation, +@command{lbtt} can generate sample data which causes a test failure and which may also be useful for debugging the implementation. @@ -218,12 +233,12 @@ Additionally, the testing environment can be used for very basic profiling of different LTL-to-B@"uchi translators to evaluate their performance. @noindent -@emph{ -Note: although @command{lbtt} might be able to detect implementation errors -in an LTL-to-B@"uchi translator, it is only a testing tool and is therefore -incapable of formally proving any translation algorithm implementation to be -correct. Therefore, the test results should never be used as the sole basis -for any formal conclusions about the correctness of an implementation. +@emph{Note: although @command{lbtt} might be able to detect inconsistent +behavior in an LTL-to-B@"uchi translator, it is only a testing tool and is +therefore incapable of formally proving any translation algorithm +implementation to be correct. Therefore, the test results should never be used +as the sole basis for any formal conclusions about the correctness of an +implementation. } @@ -289,12 +304,12 @@ read LTL formulas from a file @cindex state space Additionally, some of the tests make use of randomly generated -``state spaces'', which are basically directed labelled graphs with labels on +``state spaces'', which are basically directed labeled graphs with labels on nodes with the additional requirement of having at least one arc leaving each node. The label of each node is a subset of a finite collection of atomic propositions @cindex atomic proposition -(representing a set of conditions which may or may not hold in a +(an uninterpreted set of assertions which may or may not hold in a state), which occur also in the LTL formulas used in the tests. The following sections describe how @command{lbtt} generates input for the @@ -589,9 +604,9 @@ number of @emph{different} atomic propositions.) @cindex operators, priorities for Priorities for the Boolean constants and atomic propositions. The priority of a symbol determines the relative likelihood of its occurrence in a generated -formula. The higher the priority of a symbol, the more likely it will occur -(with respect to the other symbols) in a generated formula; a zero priority -will exclude the symbol altogether. +formula. The higher the priority of a symbol, the more likely it is that the +symbol will occur (with respect to the other symbols) in a generated formula; a +zero priority will exclude the symbol altogether. @item Priorities for the logical and temporal operators. @@ -602,7 +617,8 @@ Priorities for the logical and temporal operators. Note that the priorities for atomic symbols (Boolean constants and atomic propositions) and the priorities of the logical and temporal operators are independent, i.e., changing the priority of an atomic symbol does not affect -the occurrence likelihood of any logical or temporal operator and vice versa. +the likelihood of the occurrence of any logical or temporal operator and vice +versa. @ifnottex For further details, see @ref{The formula generation algorithm} for a @@ -878,6 +894,16 @@ distribution for a given combination of operator priorities; see the (@pxref{--showoperatordistribution,,@samp{--showoperatordistribution} command line option}) for more information. +See also the web page@* +@ifinfo +@url{http://www.tcs.hut.fi/Software/lbtt/formulaoptions.php} +@end ifinfo +@ifnotinfo +<@uref{http://www.tcs.hut.fi/Software/lbtt/formulaoptions.php>} +@end ifnotinfo +for an interface to a small database for adjusting the operator priorities +towards certain simple distributions. + @node Random state spaces, , Random LTL formulas, Random input generation @@ -891,7 +917,7 @@ model checking result cross comparison test (@pxref{Model checking result cross-comparison test}) and the model checking result consistency check (@pxref{Model checking result consistency check}). -The state spaces are directed labelled graphs, each node of which is labelled +The state spaces are directed labeled graphs, each node of which is labeled with a randomly chosen set of atomic propositions (the propositions that hold in the state corresponding to the graph node). In addition, each state of the state space always has at least one successor. @@ -1009,11 +1035,11 @@ $|\it{AP}|$. @math{|AP|}. @end ifnottex -In practice, you should always start testing using only very small state spaces +In practice, testing should be started using only very small state spaces (say, with only 10--50 states and a small density) regardless of the particular algorithm chosen for generating the state spaces. The size of the state spaces -can then be increased if @command{lbtt}'s memory consumption stays within -acceptable limits. +can then be increased if @command{lbtt}'s memory consumption and the time spent +running the tests stay within acceptable limits. @@ -1242,7 +1268,7 @@ section. @cindex B@"uchi automata intersection emptiness check @cindex tests, B@"uchi automata intersection emptiness check -The semantics of LTL guarantee that no model of an LTL formula +The semantics of LTL guarantees that no model of an LTL formula can be the model of the negation of the same formula. In terms of B@"uchi automata, this implies that the languages accepted by automata constructed from two complementary LTL formulas should be disjoint. @@ -1271,7 +1297,7 @@ to be tested and defines additional testing parameters. if the configuration file cannot be processed successfully, @command{lbtt} exits with an error message. -After reading the configuration file @command{lbtt} starts tests on the +After reading the configuration file, @command{lbtt} starts tests on the LTL-to-B@"uchi translators listed in the configuration file (for details about the testing procedure, see @ref{Test methods}). The program exits after a predetermined number of test rounds. @@ -1331,10 +1357,10 @@ format of the configuration file is @noindent Section and option names are case-insensitive. Values can be numbers, strings or truth values (@samp{yes} and @samp{no}, or equivalently, -@samp{true} and @samp{false}). String values are case-sensitive and must be -always enclosed in quotes (@samp{"}). -The backslash (@samp{\}) is treated as an escape character (to be used -e.g.@: if the string itself contains quotes; use @samp{\\} to get a backslash). +@samp{true} and @samp{false}). String values are case-sensitive and are subject +to common quoting and escaping rules (i.e., string values containing white +space should be enclosed in quotes, or the white space characters should be +escaped with @samp{\}). @cindex comments in configuration file @cindex configuration file, comments @@ -1346,14 +1372,14 @@ configuration file. @cindex configuration file, minimal requirements @cindex minimal requirements for configuration files -The configuration file must contain at least one @samp{Algorithm} +The configuration file must contain at least one @samp{Translator} section specifying an LTL-to-B@"uchi translator. The other sections are optional and can be used to override the default testing parameters. @menu -* Algorithm section:: Each LTL-to-B@"uchi translator to be +* Translator section:: Each LTL-to-B@"uchi translator to be tested requires a separate - @samp{Algorithm} section in the + @samp{Translator} section in the configuration file. * GlobalOptions section:: Options for changing the general behavior of @command{lbtt}. @@ -1365,16 +1391,24 @@ are optional and can be used to override the default testing parameters. @end menu -@node Algorithm section, GlobalOptions section, Configuration file, Configuration file -@subsection The @samp{Algorithm} section +@node Translator section, GlobalOptions section, Configuration file, Configuration file +@subsection The @samp{Translator} section @cindex configuration file, @samp{Algorithm} section +@cindex configuration file, @samp{Implementation} section +@cindex configuration file, @samp{Translator} section @cindex @samp{Algorithm} section (configuration file) +@cindex @samp{Implementation} section (configuration file) +@cindex @samp{Translator} section (configuration file) @cindex configuration file, minimal requirements Each LTL-to-B@"uchi translator to be tested requires a separate -@samp{Algorithm} section in the configuration file; there must be at least -one such section in the file. The translators are assumed to be accessible +@samp{Translator} +section@footnote{The @samp{Algorithm} and @samp{Implementation} keywords are +recognized as aliases of the @samp{Translator} keyword.} in the configuration +file; there must be at least one such section in the file. + +The translators are assumed to be accessible through external executable files. Therefore, this section must at a minimum specify the full file name of the executable to run in order to invoke the translator; see @ref{Translator interface}, for information about the @@ -1384,10 +1418,11 @@ LTL-to-B@"uchi translators. @cindex LTL-to-B@"uchi translators, identifiers @cindex identifiers for LTL-to-B@"uchi translators Translators specified in the configuration file are given unique -integer identifiers in the order they are listed in the configuration file, -starting from zero. These numbers must be used when referring to the +integer identifiers in the order they are listed in the file, +starting from zero. These numbers can be used when referring to the different translators when using @command{lbtt}'s internal commands -(@pxref{Analyzing test results}). +(@pxref{Analyzing test results}). Alternatively, the translators can be +referred to using the names specified in the configuration file. The following options (in alphabetical order) are available within this section: @@ -1398,29 +1433,35 @@ section: @cindex LTL-to-B@"uchi translators, enabling @cindex disabling LTL-to-B@"uchi translators @cindex LTL-to-B@"uchi translators, disabling -@findex Enabled @r{[}Algorithm@r{]} +@findex Enabled @r{[}Translator@r{]} This option determines whether the translator should be initially included in or excluded from the tests. The default value is @samp{Yes}. The translator can be enabled or disabled during testing with @command{lbtt}'s internal commands (@pxref{Test control commands}). @item Name = @var{STRING} -@findex Name @r{[}Algorithm@r{]} -This option can be used to specify a textual identifier for the LTL-to-B@"uchi -translator. This identifier will then be used when displaying various messages -concerning the implementation. (If no name has been explicitly given for the -translator, @command{lbtt} assigns the translator a name of the form -@samp{Algorithm @var{n}}, where @var{n} is the running integer identifier for -the translators.) +@findex Name @r{[}Translator@r{]} +This option can be used to specify a unique textual identifier for the +LTL-to-B@"uchi translator. @command{lbtt} will use this identifier when +displaying various messages concerning the implementation; the identifier can +also be used to refer to the implementation when working with @command{lbtt}'s +internal commands (@pxref{Analyzing test results}). (If no name has been +explicitly given for the translator, @command{lbtt} assigns the translator a +name of the +form @samp{Algorithm @var{n}}, where @var{n} is the running integer identifier +for the translators.) + +The identifier @samp{lbtt} is reserved for @command{lbtt}'s internal model +checking algorithm (@pxref{Model checking result cross-comparison test}). @item Parameters = @var{STRING} -@findex Parameters @r{[}Algorithm@r{]} +@findex Parameters @r{[}Translator@r{]} This option can be used to specify any additional parameters that should -be passed to the translator executable whenever running it. (The default -value of this option is the empty string.) +be passed to the translator executable whenever running it. (The parameter +string defaults to the empty string if the option is not used.) @item Path = @var{STRING} -@findex Path @r{[}Algorithm@r{]} +@findex Path @r{[}Translator@r{]} This option must be given a value for each translator specified in the configuration file. The value should be the complete file name of the program which is used to run the translator. @@ -1429,7 +1470,7 @@ which is used to run the translator. -@node GlobalOptions section, FormulaOptions section, Algorithm section, Configuration file +@node GlobalOptions section, FormulaOptions section, Translator section, Configuration file @subsection The @samp{GlobalOptions} section @cindex configuration file, @samp{GlobalOption} section @@ -1460,16 +1501,32 @@ This option can be used to enable or disable the model checking result consistency check (@pxref{Model checking result consistency check}). The test is enabled by default. -@item @anchor{Interactivity modes}Interactive = Always @r{|} OnError @r{|} Never +@item @anchor{Interactivity modes}Interactive = @var{MODE-LIST} @cindex interactivity modes @findex Interactive @r{[}GlobalOptions@r{]} -The interactivity mode determines whether @command{lbtt} should pause to wait -for user input between test rounds. A value of @samp{Always} causes -@command{lbtt} to pause unconditionally after each test round, @samp{OnError} -will interrupt testing only after failed test rounds, and @samp{Never} will -simply run all tests without interruption. +This option determines when @command{lbtt} should pause to wait for user input +between test rounds. The @var{MODE-LIST} is a comma-separated list of the +following modes (with no spaces in between the modes): +@table @samp +@item Always +Pause unconditionally after each test round. -The default value for this option is @samp{Always}. +@item OnError +Pause testing only after failed test rounds. + +@item Never +Run all tests without interruption. + +@item OnBreak +Pause testing when requested by the user (for example, after receiving a break +signal from the keyboard). If this mode is not specified, @samp{lbtt} will +respond to break signals by aborting. +@end table + +(Since the first three interactivity modes are mutually exclusive, it does not +make sense to combine these modes with each other.) The default mode list +consists of the value @samp{Always}, that is, testing is paused after +every test round, and signalling a break will abort testing. @item IntersectionCheck = @var{TRUTH-VALUE} @itemx IntersectionTest = @var{TRUTH-VALUE} @@ -1501,6 +1558,18 @@ model checking tests is enabled.) The @samp{Rounds} option can be used to specify the number of test rounds to run; the default value is 10. +@item @anchor{Timeouts}TranslatorTimeout = @var{TIME-SPECIFICATION} +@findex TranslatorTimeout @r{[}GlobalOptions@r{]} +@cindex timeouts for translators +This option can be used to specify a time limit (in wall-clock time) after +which the execution of a translator is aborted if it fails to produce a +result within the given limit. A timeout is considered a test failure. The time +specification is of the form +@samp{@r{[}@var{hours}@r{]}h@r{[}@var{minutes}@r{]}min@r{[}@var{seconds}@r{]}s} +where @var{hours}, @var{minutes} and @var{seconds} specify the time limit in +the obvious way (time units having value 0 can be omitted). For example, +a limit of @samp{1h30min} sets the limit at one hour and thirty minutes. + @item Verbosity = @var{INTEGER} @findex Verbosity @r{[}GlobalOptions@r{]} @cindex verbosity, changing @@ -1680,8 +1749,7 @@ The following table illustrates the effects of the ! <> p0 @r{No} Nor@r{:} ! <> p0 NNF@r{:} (false V ! p0) - @r{* only if} AbbreviatedOperators=Yes -} + @r{* only if} AbbreviatedOperators=Yes} @end smallformat @item PropositionPriority = @var{INTEGER} @@ -1727,11 +1795,13 @@ but with state spaces of different size.) Priority of the temporal ``(weak) release'' operator (@samp{V}). @item Size = @var{INTEGER} +@itemx Size = @var{MINIMUM-SIZE}-@var{MAXIMUM-SIZE} @itemx Size = @var{MINIMUM-SIZE}...@var{MAXIMUM-SIZE} @findex Size @r{[}FormulaOptions@r{]} This option defines how many nodes are allowed in the parse tree of each randomly generated LTL formula. If the size is given as an interval (by -separating the bounds with three dots with no white space in between), +separating the bounds with @samp{-} or @samp{...} with no white space in +between), @command{lbtt} chooses the size of each formula randomly in the interval using a uniform random distribution. The default size is 5. @@ -1868,6 +1938,7 @@ used to initialize the random number generator for the random LTL formula generation algorithm. @item Size = @var{INTEGER} +@itemx Size = @var{MINIMUM-SIZE}-@var{MAXIMUM-SIZE} @itemx Size = @var{MINIMUM-SIZE}...@var{MAXIMUM-SIZE} @findex Size @r{[}StateSpaceOptions@r{]} This option sets the number of states in the generated state spaces. If the @@ -1906,20 +1977,20 @@ imaginary LTL-to-B@"uchi translators. @smallexample # Sample configuration file for lbtt -Algorithm +Translator @{ - Name = "Translator 1" - Path = "~/lbtt/bin/t-1" # location of the translator + Name = Translator\ 1 + Path = /home/lbtt-user/bin/t-1 # location of the translator # executable Enabled = Yes @} -Algorithm +Translator @{ Name = "Translator 2" - Path = "~/lbtt/bin/t-2" + Path = /home/lbtt-user/bin/t-2 Parameters = "-x -y 3 -v 0" # parameters to be passed to the - # `~/lbtt/bin/t-2' executable + # executable Enabled = Yes @} @@ -1927,9 +1998,10 @@ GlobalOptions @{ Rounds = 100 # 100 test rounds - Interactive = OnError # pause only in case of an error + Interactive = OnError,OnBreak # pause testing in case of an error + # or when receiving a break signal - Verbosity = 1 # suppress most of the output + Verbosity = 1 # show only numeric statistics ComparisonTest = Yes # enable all tests except the ConsistencyTest = Yes # B@"uchi automata intersection @@ -1938,24 +2010,29 @@ GlobalOptions ModelCheck = Local # perform the tests only in a # single state of each state # space + + TranslatorTimeout = 30s # abort the execution of a + # translator if it fails to give + # a result in 30 seconds @} FormulaOptions @{ AbbreviatedOperators = Yes # formula generation mode GenerateMode = Normal - OutputMode = Normal + OutputMode = NNF # rewrite formulas in negation + # normal form before passing + # them to the translators - ChangeInterval = 1 # new formula after each test - # round + ChangeInterval = 1 # new formula after each round RandomSeed = 4632912 # random seed - Size = 5...15 # 5 to 15 nodes in the parse + Size = 5-15 # 5 to 15 nodes in the parse # tree of each formula - Propositions = 3 # max. 3 different propositions - # in each LTL formula + Propositions = 3 # allow at most three different + # propositions in each LTL formula PropositionPriority = 50 # priorities for propositional TruePriority = 1 # symbols @@ -1964,10 +2041,12 @@ FormulaOptions AndPriority = 10 # priorities for some logical OrPriority = 10 # operators NotPriority = 10 + EquivalencePriority = 5 NextPriority = 5 # priorities for some temporal UntilPriority = 5 # operators ReleasePriority = 5 + FinallyPriority = 2 DefaultOperatorPriority = 0 # disable all the remaining # operators @@ -1986,8 +2065,8 @@ StatespaceOptions Size = 50 # 50 states in each state space - Propositions = 3 # 3 propositions in each state - # of each state space + Propositions = 3 # three propositions in each + # state of each state space EdgeProbability = 0.1 # approximate probability of # having a transition between @@ -1997,7 +2076,6 @@ StatespaceOptions # atomic proposition is true in # a state @} - @end smallexample @@ -2046,14 +2124,29 @@ file @samp{config} in the current working directory. @cindex LTL formula, reading from a file @cindex file formats, formula input file for @command{lbtt} This option instructs @command{lbtt} to read the LTL formulas used in the tests -from a file instead of generating them randomly. The file should contain -LTL formulas separated from each other with white space. Currently the -only supported format for the formulas is the same prefix notation that -@command{lbtt} uses for its output files (@pxref{Format for LTL formulas}, -for details). +from a file instead of generating them randomly. The file should contain a +list of formulas, each on its own line in the file. The formulas can be +specified either in @command{lbtt}'s own prefix notation +(@pxref{Format for LTL formulas}; also the infix notation used in output +messages is supported) or in a variety of formats found in +some LTL-to-B@"uchi translator implementations (Spin, LTL2BA, LTL2AUT, +Temporal Massage Parlor, Wring, Spot, LBT), however with the restriction that +all atomic propositions should have names of the form +@samp{p@var{N}} for some nonnegative integer @var{N}. -If this option is used, all LTL formula generation parameters in the command -line or in the configuration file are ignored. +@cindex operators, precedence in input files +(When using one of the alternative +formats, it is recommended to use parentheses to avoid possible ambiguities in +the precedence and associativity of the various operators; in @command{lbtt}, +the unary operators have the highest precedence, @samp{/\} has higher +precedence than @samp{\/}, which in turn has higher precedence than any of +@samp{->}, @samp{<->} or @samp{xor}, and the binary temporal operators have +the lowest precedence. All binary logical operators are left-associative; +all binary temporal operators are nonassociative.) + +If this option is used, all command line or configuration file parameters +affecting the generation of random LTL formulas (excluding their mode of +output) are ignored. @item --h @itemx --help @@ -2074,7 +2167,8 @@ encountered during testing. By default no log will be created. @cindex enabling and disabling tests @cindex tests, profiling LTL-to-B@"uchi translators This option can be used as a shorthand for disabling all B@"uchi automata -correctness tests. The test report generated at the end of testing then shows +correctness tests. +The test report generated at the end of testing then shows only the running times of each tested LTL-to-B@"uchi translator and the sizes of the generated automata. @@ -2084,8 +2178,7 @@ of the generated automata. @vindex --silent @cindex suppressing output These options suppress any messages that are normally displayed during -testing. These options also imply the @samp{--nopause} option, i.e., all -tests will be run without interruption. Use the @samp{--logfile} option (see +testing. Use the @samp{--logfile} option (see above) with these options to save a test failure report into a log file. @item @anchor{--showconfig}--showconfig @@ -2114,7 +2207,9 @@ configuration information when the program starts. This option can be used to skip the first @var{NUMBER-OF-ROUNDS} test rounds, i.e., begin testing from round @var{NUMBER-OF-ROUNDS}+1. -@item --version +@item -V +@itemx --version +@vindex -V @vindex --version This option displays the version of the @command{lbtt} executable. @@ -2130,7 +2225,7 @@ values specified in the @samp{GlobalOptions} section of the configuration file. @table @samp -@item --comparisontest +@item --comparisontest@r{[}=yes | no@r{]} @itemx --nocomparisontest @cindex tests, enabling and disabling @cindex enabling and disabling tests @@ -2139,7 +2234,7 @@ file. These options enable or disable the model checking result cross-comparison test (@pxref{Model checking result cross-comparison test}). -@item --consistencytest +@item --consistencytest@r{[}=yes | no@r{]} @itemx --noconsistencytest @cindex tests, enabling and disabling @cindex enabling and disabling tests @@ -2153,10 +2248,10 @@ These options enable or disable the model checking result consistency check @cindex LTL-to-B@"uchi translators, disabling @vindex --disable This option can be used to exclude some implementations from the tests by -specifying a comma-separated list of integers (the implementation identifiers). -The implementations are numbered in the order in which they appear in the -configuration file, starting from zero. (The @samp{--showconfig} -option, see @ref{Special options}, can be used to obtain a list of the +specifying a comma-separated list of implementation names or their numeric +identifiers. (The implementations are numbered in the order in which they +appear in the configuration file, starting from zero. Use the +@samp{--showconfig} option, see @ref{Special options}, to obtain a list of the implementations specified in the configuration file, together with their identifiers.) @@ -2177,14 +2272,19 @@ checking result cross-comparison test and the model checking result consistency check. Using a global check increases the number of possible tests. -@item --interactive=always | onerror | never +@item --interactive@r{[}=@var{MODE-LIST}@r{]} +@itemx --pause@r{[}=@var{MODE-LIST}@r{]} @vindex --interactive @cindex interactivity modes -This option can be used to override whether @command{lbtt} should pause -between test rounds to wait for user input. +These options can be used to override whether @command{lbtt} should pause +between test rounds to wait for user input. The optional @var{MODE-LIST} is a +comma-separated list of interactivity modes (@samp{Always}, @samp{OnError}, +@samp{Never}, @samp{OnBreak}) with no spaces in between +(@pxref{Interactivity modes}, for the mode descriptions). If omitted, the +mode list defaults to @samp{Always}. -@item --intersectiontest -@item --nointersectiontest +@item --intersectiontest@r{[}=yes | no@r{]} +@itemx --nointersectiontest @cindex tests, enabling and disabling @cindex enabling and disabling tests @vindex --intersectiontest @@ -2208,29 +2308,19 @@ check. @cindex global model checking This option can be used to select the model checking mode. -@item --nopause -@vindex --nopause -@cindex interactivity modes -This option forces @command{lbtt} to run all tests without interruption. - -@item --pause +@item --pause@r{[}=@var{MODE-LIST}@r{]} @vindex --pause -@cindex interactivity modes -This option forces @command{lbtt} to pause between each test round to -wait for user commands. - -@item --pauseonerror -@vindex --pauseonerror -@cindex interactivity modes -Using this option will cause testing to be paused each time an error occurs -during testing, -giving the user an opportunity to analyze the error situation using -@command{lbtt}'s internal commands before proceeding to the next test round. +See @samp{--interactive}. @item --rounds=@var{NUMBER-OF-ROUNDS} @vindex --rounds This option can be used to override the number of test rounds to run. +@item --translatortimeout=@var{TIME-SPECIFICATION} +@vindex --translatortimeout +This option can be used to override the running time limit (in wall-clock +time) for translators (@pxref{Timeouts}, for more information). + @item --verbosity=@var{INTEGER} @vindex --verbosity @cindex verbosity, changing @@ -2259,7 +2349,7 @@ configuration file. @cindex operators, priorities for @table @samp -@item --abbreviatedoperators +@item --abbreviatedoperators@r{[}=yes | no@r{]} @itemx --noabbreviatedoperators @vindex --abbreviatedoperators @vindex --noabbreviatedoperators @@ -2338,6 +2428,7 @@ This option gives a seed value for generating random numbers used by the random LTL formula generation algorithm. @item --formulasize=@var{INTEGER} +@itemx --formulasize=@var{MINIMUM-SIZE}-@var{MAXIMUM-SIZE} @itemx --formulasize=@var{MINIMUM-SIZE}...@var{MAXIMUM-SIZE} @vindex --formulasize This option sets the size of the random LTL formulas generated for the tests. @@ -2510,6 +2601,7 @@ This option gives a seed value for generating random numbers required by the random state space generation algorithm. @item --statespacesize=@var{INTEGER} +@itemx --statespacesize=@var{MINIMUM-SIZE}-@var{MAXIMUM-SIZE} @itemx --statespacesize=@var{MINIMUM-SIZE}...@var{MAXIMUM-SIZE} @vindex --statespacesize This option can be used to change the size of the generated state spaces. @@ -2528,11 +2620,12 @@ spaces.) @node Interpreting the output, Analyzing test results, Invocation, Top @chapter Interpreting the output -This chapter briefly intoduces the most typical messages that -@command{lbtt} outputs during testing when running in its default output -verbosity mode (3). In lower verbosity modes some (or all) of these -messages will be suppressed; higher verbosity modes show information about -@command{lbtt}'s internal behavior. +This chapter briefly introduces the most typical messages that +@command{lbtt} outputs during testing. Most of the examples in this section +illustrate the output when @command{lbtt} is running in its default output +verbosity mode (3). In lower verbosity modes some (or in verbosity mode 0, all) +of these messages will be suppressed; in higher verbosity modes, some +additional information about @command{lbtt}'s internal behavior is shown. @menu * Configuration information:: The current configuration is shown @@ -2548,7 +2641,8 @@ messages will be suppressed; higher verbosity modes show information about @cindex configuration information -Before starting tests, @command{lbtt} shows a short summary of the current +Before starting tests, @command{lbtt} outputs (in verbosity modes 2 and above) +a summary of the current program configuration as obtained by reading the program configuration file and interpreting the command line parameters. The same summary can be obtained without running any tests by using the @@ -2590,6 +2684,7 @@ Program configuration: 1000 test rounds. Testing will be interrupted in case of an error. + Signalling a break will interrupt testing. Using global model checking for tests. Writing error log to `error.log'. @@ -2597,13 +2692,15 @@ Program configuration: 0: `Implementation 0' 1: `Implementation 1' + Timeout for translators is set to 30 seconds. + Enabled tests: Model checking result cross-comparison test Model checking result consistency check B@"uchi automata intersection emptiness check Random state spaces: - Random connected graphs (50 states, 5 atomic propositions) + Random graphs (50 states, 5 atomic propositions) New state space will be generated after every 5th round. Random seed: 98 Random edge probability: 0.10 @@ -2617,14 +2714,38 @@ Program configuration: false (5); propositions (90); true (5) Operators used for random LTL formula generation: operator ! /\ U V X \/ - priority 10 20 20 20 10 20 + priority 10 10 20 20 10 20 + @end smallexample @node Test round messages, Test statistics, Configuration information, Interpreting the output @section Test round messages +In verbosity modes 1 and 2, @command{lbtt} reports numeric statistics on the +generated automata in tabular form. Each row of this table contains the +following information (in this order): +@itemize +@item +number of the current test round (verbosity mode 1 only); +@item +numeric identifier of an implementation; +@item +formula identifier (@samp{+} or @samp{-}); +@item +time consumed when generating an automaton from the formula using the +implementation; +@item +number of states, transitions and acceptance conditions in the automaton; +@item +number of states and transitions in the product automaton +@item +number of accepting cycles in the state space (see below), and +@item +result of the consistency check (verbosity mode 2 only). +@end itemize + The following example shows a fragment of the output that @command{lbtt} might -produce during a test round: +produce during a test round when running in the default verbosity mode 3. @cindex tests, output example @@ -2681,15 +2802,15 @@ configuration; if the state space size is allowed to vary in an interval, space.) @item -@cindex operators, precedence -Information about a random LTL formula and its negation. Note that in the -printed output, it is assumed (to remove some parentheses) that all unary -formula operators have strictly higher precedence than binary operators. +@cindex operators, precedence in output messages +Information about a random LTL formula and its negation. To simplify the +notation, it is assumed that all unary formula operators have higher +precedence than binary operators. @item Information about the B@"uchi automaton that `Implementation 0' generated from the positive LTL formula (number of states, transitions and acceptance -conditions). +conditions, and the amount of user time elapsed in generating the automaton). @item Information about the synchronous product of the state space and the @@ -2703,11 +2824,6 @@ space contains no states with an infinite path beginning from the state such that the B@"uchi automaton accepts the temporal interpretation of the path (the infinite sequence of state labels on the path). -(If the implementation used for translating the positive formula -@samp{((p1 <-> p0) U (p0 \/ ! p3))} into a B@"uchi automaton is known to be -correct, the result would then also indicate that no infinite path in the state -space satisfies the formula.) - @item The model checking process is repeated using the negated formula as input for the LTL-to-B@"uchi translator `Implementation 0'. @@ -2715,7 +2831,7 @@ input for the LTL-to-B@"uchi translator `Implementation 0'. @item @command{lbtt} performs the model checking result consistency check (@pxref{Model checking result consistency check}) using the model checking -results computed for the positive and the negative formula. In this case, +results computed for the positive and the negative formula. In this example, the result consistency check fails in 75 states of the state space. This implies that `Implementation 0' failed to translate one (or both) of the formulas into a B@"uchi automaton correctly. @@ -2729,7 +2845,7 @@ the B@"uchi automata intersection emptiness test (@pxref{Automata intersection emptiness check}). The model checking result cross-comparison test might result in the following -output: +output (shown in verbosity modes greater than 1): @smallformat @t{ Model checking result cross-comparison:} @@ -2754,8 +2870,7 @@ the test is @samp{N/A}. @cindex tests, against internal model checking algorithm If using enumerated or randomly generated paths as state spaces, the model checking results are also compared against those given by -@command{lbtt}'s internal model checking algorithm. In the output, the -internal implementation has the name @samp{lbtt} and no numeric identifier. +@command{lbtt}'s internal model checking algorithm. A similar convention is used to report failures in the B@"uchi automata intersection emptiness check. However, because this test is always performed @@ -2789,7 +2904,7 @@ format specified above. @cindex tests, statistics At the end of testing, @command{lbtt} outputs some simple statistics computed -over all tests. If using an error log file +over all tests in verbosity modes 2 and above. If using an error log file (@pxref{--logfile,,@samp{--logfile} command line option}), the statistics will be stored also in the log file. These statistics can be also accessed during interactive testing by using the internal command @samp{statistics} @@ -2858,8 +2973,8 @@ Number of failures in the B@"uchi automata intersection emptiness check implementations. @end itemize -Note that the pairwise inconsistency results form a symmetric matrix (although -@command{lbtt} might display the matrix in several parts), which means that +Note that the pairwise inconsistency results form a symmetric matrix (possibly +shown in several parts), which means that the same information is repeated on both sides of the matrix diagonal. @end itemize @@ -2879,8 +2994,8 @@ analyze test results. To use the internal commands, @command{lbtt} must be started in one of its interactive modes (@pxref{Interactivity modes}). Depending on the mode, -@command{lbtt} may occasionally pause (either after each test round or only in -case a test failure is detected) between test rounds to wait for user input by +@command{lbtt} may occasionally pause (for example, after each test round, or +when a test failure is detected) between test rounds to wait for user input by showing a prompt of the form @smallexample @@ -2919,7 +3034,7 @@ command unambiguously (for example, @samp{h} could be used in place of the @samp{help} command). @cindex commands, entering lists of numbers -Some of the commands accept or require lists of implementation or state +Some of the commands expect lists of implementation or state identifiers as parameters. The lists can be specified as comma-separated numbers (for example, @samp{8}) or intervals (for example, @samp{3-11}) with no white space between the commas and the numbers or intervals that @@ -2932,6 +3047,11 @@ or equal to 5, together with information about state 8, states 14 to 18 @samp{*} symbol can be used as a shorthand for all identifiers in the available range. +@command{lbtt} also recognizes the symbolic names of implementations (defined +in the configuration file) in implementation identifier lists. The names can be +used in place of the numeric identifiers. Quotes or the escape character +(@samp{\}) should be used to handle white space in identifiers. + @cindex LTL formula, identifiers in commands @cindex commands, LTL formula identifiers Some of the commands require a formula identifier as a parameter for choosing @@ -2949,13 +3069,12 @@ respectively. @cindex commands, invoking external programs @cindex commands, writing output to a pipe -Optionally, the output can be written to a pipe (to be processed by an external -program) instead of a file. This is done by ending the command line with -@samp{| @var{command}}, where @var{command} is the command line for the -external program intended to process the output produced by @command{lbtt}. +Optionally, the output can be handed over to an external program by ending the +command line with @samp{| @var{command}}, where @var{command} is the command +line used for invoking the external program. For example, the output of the (@command{lbtt}'s internal) command can be piped to a pager application if the entire output does not fit on the screen by -itself. Using the pipe construct @emph{without} specifying any internal command +itself. Using the pipe construct without specifying any internal command will simply invoke the external program. @@ -2971,9 +3090,9 @@ general conventions for using the commands. The @samp{help} command can be optionally given a command name as a parameter to access command-specific help. -In command-specific help, arguments in angle brackets (@samp{<}, @samp{>}) +In command-specific help, arguments in angle brackets (@r{<}, @r{>}) denote obligatory command parameters, while arguments in square brackets -(@samp{[}, @samp{]}) are optional. A vertical bar (@samp{|}) denotes +(@r{[}, @r{]}) are optional. A vertical bar (@r{|}) denotes selection between several alternatives. Arguments in double quotes should be entered literally (without the quotes themselves). @@ -2992,18 +3111,18 @@ verbosity of @command{lbtt}'s output messages. @item continue @r{[}@var{number-of-rounds}@r{]} @kindex continue Continue testing. If no argument is given, testing will be interrupted again -when the current interactivity mode requires it +when mandated by the current interactivity mode (@pxref{Interactivity modes}). The optional argument @var{number-of-rounds} can be used to specify a number of rounds to run; testing is then interrupted again after the given number of test rounds (or in case of a new -test failure if this is required by the current interactivity mode). +test failure if mandated by the current interactivity mode). @item disable @r{[}@var{implementation-id-list}@r{]} @kindex disable @cindex disabling LTL-to-B@"uchi translators @cindex LTL-to-B@"uchi translators, disabling -Disable testing of a list of implementations or all implementations if -no list of implementations is specified. @command{lbtt} will not include these +Disable testing of a list of implementations (all implementations if +no list of implementations is specified). @command{lbtt} will not include these implementations in the tests in subsequent test rounds. (See @ref{Command conventions}, for the syntax used for the list of implementations.) @@ -3052,20 +3171,22 @@ current test round. @table @samp @item algorithms +@itemx implementations +@itemx translators @kindex algorithms +@kindex implementations +@kindex translators Show a list of implementations declared in the program configuration file and -whether they are currently enabled for testing. The list also shows the -numeric identifiers of the implementations. These identifiers -should be used with commands that accept or require implementation identifier -lists as arguments. +tell whether they are currently enabled for testing. The list also shows the +numeric identifiers of the implementations. -@item buchi @r{[}+ @r{|} -@r{]} @var{implementation-id} @r{[}@var{state-id-list} | dot@r{]} +@item buchi @r{[``}+@r{''} @r{|} @r{``}-@r{'']} @r{<}@var{implementation-id}@r{>} @r{[}@var{state-id-list} | @r{``}dot@r{'']} @kindex buchi Display information about the structure of the B@"uchi automaton generated by the implementation @var{implementation-id} from the positive (@samp{+}) or negative (@samp{-}) LTL formula used in the current test round. The implementation identifier may be optionally followed by a list of state -identifiers to display only certain states of the automaton (see +identifiers to display specific states of the automaton (see @ref{Command conventions}, for details on how the list should be formatted), or the keyword @samp{dot} to display the automaton in a format that can be given as input for the @samp{dot} tool of the @@ -3079,7 +3200,7 @@ GraphViz graph visualization package @end iftex to obtain a graphical representation of the automaton. -@item evaluate @r{[}+ @r{|} -@r{]} @r{[}@var{implementation-id-list}@r{]} @r{[}@var{state-id-list}@r{]} +@item evaluate @r{[``}+@r{''} @r{|} @r{``}-@r{'']} @r{[}@var{implementation-id-list}@r{]} @r{[}@var{state-id-list}@r{]} @kindex evaluate Display the model checking results for the positive (@samp{+}) or the negative (@samp{-}) formula computed using a given set of implementations for @@ -3097,22 +3218,25 @@ as input for the @samp{resultanalysis} command (@pxref{Failure analysis commands}). Note 1: Observe that the model checking results shown do not follow the -``traditional'' semantics of LTL, by which a formula is usually considered to +``universal'' semantics of LTL (common in model checking), by which a formula +is usually considered to hold in a set of infinite paths beginning from a state only if @emph{all} paths in the set are accepted by the B@"uchi automaton constructed from the -formula to be model checked. Instead, @command{lbtt} will mark the result as -true if even @emph{one} of these paths is accepted by the automaton. +formula to be model checked. Instead, @command{lbtt} will mark the result +true if @emph{any} of these paths is accepted by the automaton. Note 2: If using random or enumerated paths as state spaces, @command{lbtt} -accepts also the symbol @samp{p} in the implementation identifier list. This -symbol can be used for accessing the model checking results computed using -@command{lbtt}'s internal ``path'' model checking algorithm. +accepts also the identifier @samp{lbtt} in the implementation identifier list. +This identifier can be used for accessing the model checking results computed +using @command{lbtt}'s internal model checking algorithm for paths. -@item formula @r{[}+ @r{|} -@r{]} +@item formula @r{[``}+@r{''} @r{|} @r{``}-@r{'']} @r{[``}normal@r{''} @r{|} @r{``}nnf@r{'']} @kindex formula @cindex LTL formula, displaying with user command Display the positive (@samp{+}) or the negative (@samp{-}) LTL formula used -for tests in the current test round. +for tests in the current test round either in the form in which it was +generated (@samp{normal} -- the default) or in negation normal form +(@samp{nnf}). @item inconsistencies @r{[}@var{implementation-id-list}@r{]} @kindex inconsistencies @@ -3125,14 +3249,14 @@ The state identifiers can then be used as input for the @item results @r{[}@var{implementation-id-list}@r{]} @kindex results -Display test results for each implementation in the list (or all -implementations if the list is omitted) in a format very similar to test -messages shown when running @command{lbtt} in its default verbosity mode 3. +Display test results (in the current test round) for each implementation in the +list (or all +implementations if the list is omitted). For more information about the output, see @ref{Test round messages}; see @ref{Command conventions}, for information on how to specify the implementations. -@item statespace @r{[}@var{state-id-list} @r{|} dot@r{]} +@item statespace @r{[}@var{state-id-list} @r{|} @r{``}dot@r{'']} @kindex statespace @cindex state space, displaying with an user command Display information about the structure of the state space used for model @@ -3149,7 +3273,7 @@ the GraphViz graph visualization package @iftex [GViz] @end iftex -to obtain a graphical representation of the state space. +that can be used to obtain a graphical representation of the state space. @item @anchor{statistics}statistics @kindex statistics @@ -3173,7 +3297,7 @@ automata correctness tests. The second part describes the conventions that @subsection Alphabetical list of failure analysis commands @table @samp -@item buchianalysis @var{implementation-id} @var{implementation-id} +@item buchianalysis @r{<}@var{implementation-id}@r{>} @r{<}@var{implementation-id}@r{>} @kindex buchianalysis @cindex B@"uchi automata intersection emptiness check, failure analysis @cindex tests, failure analysis @@ -3194,9 +3318,9 @@ be equal if one of the tested implementations failed the check against itself.) A failure in the B@"uchi automata intersection emptiness check implies that there exists an input sequence over subsets of atomic propositions that is accepted by both automata included in the analysis. @command{lbtt} examines the -intersection of the automata to find a witness of such an input, model -checks the positive formula in the witness, and tells which one of the automata -is likely to be incorrect according to the following rules: +intersection of the automata to find a witness of such an input, checks whether +this witness is a model of the positive formula, and tells which one of the +automata is likely to be incorrect according to the following rules: @itemize @bullet @item @@ -3208,7 +3332,7 @@ If the witness is not a model for the positive formula, then the automaton constructed from the positive formula probably accepts the witness incorrectly. @end itemize -@item consistencyanalysis @var{implementation-id} @r{[}@var{state-id}@r{]} +@item consistencyanalysis @r{<}@var{implementation-id}@r{>} @r{[}@var{state-id}@r{]} @kindex consistencyanalysis @cindex model checking result consistency check, failure analysis @cindex tests, failure analysis @@ -3218,10 +3342,10 @@ Analyze a failure in the model checking result consistency check (@pxref{Model checking result consistency check}). The @var{implementation-id} parameter chooses the implementation to analyze. In addition, the optional @var{state-id} parameter can be used to specify a -state space state in which to perform the analysis (use the +state (in the state space) in which to perform the analysis (use the @samp{inconsistencies} command, @ref{Data display commands}, to see a list -of all state space states in which the check failed). If the state identifier -is not present, @command{lbtt} will try to find a state where +of all states in which the check failed). If the state identifier +is omitted, @command{lbtt} will try to find a state where the check failed. @cindex witness @@ -3229,11 +3353,11 @@ A failure in the model checking result consistency check implies the existence of a witness (i.e., a path in the state space used for the tests in the current test round) whose temporal interpretation is not accepted by either of two automata constructed from two complementary LTL formulas. In the analysis, -@command{lbtt} finds such a witness, model checks the positive formula in the -witness separately, and tells which one of the automata seems to reject the -witness incorrectly. +@command{lbtt} finds such a witness, checks separately whether it is a model of +the positive formula, and then tells which one of the automata seems to reject +the witness incorrectly. -@item resultanalysis @r{[}+ @r{|} -@r{]} @var{implementation-id} @var{implementation-id} @r{[}@var{state-id}@r{]} +@item resultanalysis @r{[``}+@r{''} @r{|} @r{``}-@r{'']} @r{<}@var{implementation-id}@r{>} @r{<}@var{implementation-id}@r{>} @r{[}@var{state-id}@r{]} @kindex resultanalysis @cindex model checking result cross-comparison test, failure analysis @cindex tests, failure analysis @@ -3243,8 +3367,9 @@ Analyze a failure in the model checking result cross-comparison test (@pxref{Model checking result cross-comparison test}) between two implementations on either the positive (@samp{+}) or the negative (@samp{-}) LTL formula used in the current test round. The implementation -identifiers can be optionally followed by a state space state identifier -to specify a state in which the analysis should be performed. (Suitable +identifiers can be optionally followed by an identifier of a state in the +state space to specify a state in which the analysis should be performed. +(Suitable state identifiers can be found by looking for inconsistencies in the model checking results accessible with the @samp{evaluate} command, @ref{Data display commands}; by omitting the state identifier, @@ -3252,9 +3377,9 @@ checking results accessible with the @samp{evaluate} command, checking result comparison failed between the implementations.) If using randomly generated or enumerated paths as state spaces, @command{lbtt} -also accepts the symbol @samp{p} in place of either of the implementation -identifiers. This instructs @command{lbtt} to perform the analysis against -@command{lbtt}'s internal model checking algorithm. +also accepts the identifier @samp{lbtt} in place of either of the +implementation identifiers. This instructs @command{lbtt} to perform the +analysis against @command{lbtt}'s internal model checking algorithm. @cindex witness A failure in the model checking result cross-comparison test suggests that @@ -3275,25 +3400,32 @@ All of the above analysis commands use @command{lbtt}'s internal model checking algorithm to determine which one of the two automata involved in each test is incorrect by checking whether an LTL formula holds in a witness path extracted from the state space used in the current test round or from the -intersection of two B@"uchi automata. The witness path is a linear sequence of -states that ends in a loop, and is represented in two parts as an initial +intersection of two B@"uchi automata. The witness path is a sequence of +consecutive states that ends in a loop, and is represented in two parts as an +initial ``prefix'' (which may be empty) and a ``cycle'' that is considered to repeat -itself indefinitely. The witness might look as follows: +itself indefinitely. The witness might, for example, look as follows: @smallexample Execution M: - prefix: < s0 @{p0, p4@}, - s2 @{p1, p2, p3, p4@} > - cycle: < s34 @{p0, p1, p2, p3, p4@}, - s42 @{p4@}, - s44 @{p1, p4@} > + prefix: + s3 @{p0,p2,p4@} --> s4 + cycle: + s4 @{p1,p3@} --> s5 + s5 @{p3@} --> s6 + s6 @{p1,p2,p3@} --> s7 + s7 @{p3,p4@} --> s8 + s8 @{p1@} --> s9 + s9 @{@} --> s2 + s2 @{@} --> s3 + s3 @{p0,p2,p4@} --> s4 @end smallexample @noindent In this case, the witness (or ``execution'' as displayed in the output) -@math{M} consists of a prefix of two states followed by a cycle of three -states. Each state is associated with a set of atomic propositions that hold -in the state. +@math{M} consists of a single-state prefix followed by a cycle of eight +states. The atomic propositions that hold in each state are also shown in +the output. (The witness can be considered a small state space @iftex @@ -3307,30 +3439,34 @@ $M = \langle S, \rho, {\cal L} \rangle$ following the definition in @ref{State spaces}; in the example above, @iftex @tex -$S = \{s_0, s_2, s_{34}, s_{42}, s_{44}\}$, +$S = \{s_2, s_3, s_4, s_5, s_6, s_7, s_8, s_9\}$, @end tex @end iftex @ifnottex -@math{S = @{s0, s2, s34, s42, s44@}}, +@math{S = @{s2, s3, s4, s5, s6, s7, s8, s9@}}, @end ifnottex @iftex @tex -$\rho = \{(s_0, s_2), (s_2, s_{34}), (s_{34}, s_{42}), (s_{42}, s_{44}), - (s_{44}, s_{34})\}$, +$\rho = \{(s_2, s_3), (s_3, s_4), (s_4, s_5), (s_5, s_6), (s_6, s_7), + (s_7, s_8), (s_8, s_9), (s_9, s_2)\}$, @end tex @end iftex @ifnottex -@math{R = @{(s0, s2), (s2, s34), (s34, s42), (s42, s44), (s44, s34)@}}, +@math{R = @{(s2, s3), (s3, s4), (s4, s5), (s5, s6), (s6, s7), (s7, s8), (s8, s9), (s9, s2)@}}, @end ifnottex @iftex @tex -${\cal L}(s_0) = \{p_0, p_4\}, {\cal L}(s_2) = \{p_1, p_2, p_3, p_4\}, - {\cal L}(s_{34}) = \{p_0, p_1, p_2, p_3, p_4\}, {\cal L}(s_{42}) = \{p_4\},$ - and ${\cal L}(s_{44}) = \{p_1, p_4\}$.) +${\cal L}(s_2) = {\cal L}(s_9) = \emptyset$, +${\cal L}(s_3) = \{p_0, p_2, p_4\}$, +${\cal L}(s_4) = \{p_1, p_3\}$, +${\cal L}(s_5) = \{p_3\}$, +${\cal L}(s_6) = \{p_1, p_2, p_3\}$, +${\cal L}(s_7) = \{p_3, p_4\}$, and +${\cal L}(s_8) = \{p_1\}$.) @end tex @end iftex @ifnottex -@math{L(s0) = @{p0, p4@}, L(s2) = @{p1, p2, p3, p4@}, L(s34) = @{p0, p1, p2, p3, p4@}, L(s42) = @{p4@},} and @math{L(s44) = @{p1, p4@}}.) +@math{L(s2) = L(s_9) = @{@}, L(s3) = @{p0, p2, p4@}, L(s4) = @{p1, p3@}, L(s5) = @{p3@}, L(s6) = @{p1, p2, p3@}, L(s7) = @{p3, p4@},} and @math{L(s8) = @{p1@}}.) @end ifnottex @@ -3351,21 +3487,14 @@ LTL and might look as follows: @smallexample Analysis of the formula in the execution: - M, |== ((p1 U p4) <-> (! p1 -> [] p4)) : - +-> M, |== (p1 U p4) : - | +-> M, |== p4 - +-> M, |== (! p1 -> [] p4) : - +-> M, |== [] p4 : - +-> M, |== p4 - +-> s0 --> s2 - +-> M, |== p4 - +-> s2 --> s34 - +-> M, |== p4 - +-> s34 --> s42 - +-> M, |== p4 - +-> s42 --> s44 - +-> M, |== p4 - +-> s44 --> s34 + M, |/= ((X p0 U ! p4) <-> p0) : + +-> M, |/= (X p0 U ! p4) : + | +-> M, |/= X p0 : + | | +-> s3 --> s4 + | | +-> M, |/= p0 + | +-> M, |/= ! p4 : + | +-> M, |== p4 + +-> M, |== p0 @end smallexample @noindent @@ -3377,57 +3506,81 @@ subsequence beginning at state @samp{s} of the witness, and the relational symbol @samp{|/=} denotes the opposite. The children of each proof tree node give justification for the claim in their parent node; the children might be further expanded if the claims in them do not directly follow from the -definition of @math{M}. In the presence of temporal operators, the proofs may +definition of +@iftex +@tex +${\cal L}$. +@end tex +@end iftex +@ifnottex +@math{L}. +@end ifnottex +In the presence of temporal operators, the proofs may need to be based also on the structural properties of @math{M}. These are shown as statements of the form @samp{sn --> sm} to indicate that @math{M} contains a transition from the state @samp{sn} to the state @samp{sm} (and, -since the states in @math{M} are connected into a linear sequence, that this -is the @emph{only} transition originating from @samp{sn}). +since the states in @math{M} are connected into a non-branching sequence, that +this is the @emph{only} transition originating from @samp{sn}). In the above example, @command{lbtt} claims that the formula -@samp{((p1 U p4) <-> (! p1 -> [] p4))} holds in the witness presented earlier +@samp{((X p0 U ! p4) <-> p0)} does not hold in the witness presented earlier in this section, and that this follows (by the semantics of logical -equivalence) from the claims that the subformulas @samp{(p1 U p4)} and -@samp{(! p1 -> [] p4)} both hold in the same witness. -@samp{(p1 U p4)} holds in the witness because of the fact that +equivalence) from the claims that the subformula @samp{(X p0 U ! p4)} does not +hold, but the subformula @samp{p0} holds in this witness. +@samp{(X p0 U ! p4)} does not hold in the witness, because neither +@samp{X p0} nor @samp{! p4} holds in the first state of the witness @iftex @tex -$p_4 \in {\cal L}(s_0)$, +($p_4 \in {\cal L}(s_3)$, and $p_0 \notin {\cal L}(s_4)$, where $s_4$ is the +only successor of $s_3$). @end tex @end iftex @ifnottex -@math{p4} is included in @math{L(s0)}, +(@math{p4} is included in @math{L(s3)}, and @math{p0} is not included in +@math{L(s4)}, where @math{s4} is the only successor of @math{s3}). @end ifnottex -and the truth of the implication @samp{(! p1 -> [] p4)} is justified by the -property that +On the other hand, +@samp{p0} holds in the witness because of the fact that @iftex @tex -$p_4$ +$p_0 \in {\cal L}(s_3)$. @end tex @end iftex @ifnottex -@math{p4} +@math{p0} is included in @math{L(s3)}. @end ifnottex -holds in all states of the (only) infinite sequence beginning at the state -@iftex -@tex -$s_0$, -@end tex -@end iftex -@ifnottex -s0, -@end ifnottex -which can be seen from the proof. @node Interfacing with lbtt, References, Analyzing test results, Top -@chapter Interfacing LTL-to-B@"uchi translators with @command{lbtt} +@chapter Interfacing with @command{lbtt} @cindex LTL-to-B@"uchi translators, interfacing with @cindex interfacing LTL-to-B@"uchi translators with @command{lbtt} -This chapter gives the details on how to use @command{lbtt} for +The output generated by @command{lbtt} consists of textual messages +and an optional error log file (@pxref{--logfile,,@samp{--logfile} command line +option}). The format of the output messages is determined by the verbosity +mode; +for more information, see @ref{Interpreting the output}. In addition, +@command{lbtt} returns one of the following three values as its exit +status upon normal termination: +@itemize +@item 0: +@command{lbtt} exited successfully; no errors were detected during testing. + +@item 1: +@command{lbtt} exited successfully; errors were detected during testing. + +@item 2: +An error was found when reading the program configuration or when processing +the command line options. + +@item 3: +@command{lbtt} exited due to an unrecoverable internal error. +@end itemize + +The rest of this chapter gives the details on how to use @command{lbtt} for testing LTL-to-B@"uchi translation algorithm implementations that are not supported by the basic distribution. (See @ref{The lbtt-translate utility} @@ -3446,8 +3599,9 @@ LTL-to-B@"uchi translator implementations to @command{lbtt}.) @end menu -@node Translator interface, Format for LTL formulas, Interfacing with lbtt, Interfacing with lbtt -@section Translator interface requirements + +@node Translator interface, Format for LTL formulas, , Interfacing with lbtt +@section Requirements for translator executables @cindex LTL-to-B@"uchi translators, interface requirements @command{lbtt} assumes each tested LTL-to-B@"uchi translator to be @@ -3478,18 +3632,19 @@ for a description on how these files should be formatted. The translator executable should always create an output file and then return with a zero exit status in case no errors occur during the translation. -@command{lbtt} interprets a missing output file or any non-zero exit status as +@command{lbtt} interprets a missing output file or a nonzero exit status as an error and will not in this case try to run any tests, even if an automaton were successfully saved in an output file. -To start testing the translator, add a new @samp{Algorithm} section for it into +To start testing the translator, add a new @samp{Translator} section for it +into @command{lbtt}'s configuration file (@pxref{Configuration file}), for example @smallexample -Algorithm +Translator @{ Name = "LTL-to-B@"uchi translator" - Path = "~/bin/ltl-to-buchi-translator" + Path = /home/lbtt-user/bin/ltl-to-buchi-translator Parameters = "-x -y -z" Enabled = Yes @} @@ -3591,38 +3746,86 @@ line options (@pxref{Command line options}) to prevent @command{lbtt} expects the B@"uchi automata generated by each LTL-to-B@"uchi translator implementation to be in the format specified below. The format encodes a generalized B@"uchi automaton (a B@"uchi automaton with zero or -more acceptance conditions) with a single initial state and labels on -transitions. For the full formal definition and some general guidelines on how -to convert your automata to support the definition, see @ref{Definitions}. +more acceptance conditions) with a single initial state and labels (guards) on +transitions. For the full formal definition and examples on how +to reduce other definitions into the one used by @command{lbtt}, see +@ref{Definitions}. The output file generated by the translator should contain an @var{automaton} -described using the following grammar: +described using the following grammar +(as before, quoted characters denote the characters themselves, @var{sp} +denotes any nonempty string of white space, lines containing a // are +comments that are not part of the grammar, and @samp{\n} corresponds to the +newline character). @smallexample -@var{automaton} @r{::=} @var{num-states} @var{sp} @var{num-conds} @var{states} +@var{automaton} @r{::=} @var{num-states} @var{sp} @var{cond-specifier} @var{state-list} @var{num-states} @r{::=} @r{[}0@r{---}9@r{]+} -@var{num-conds} @r{::=} @r{[}0@r{---}9@r{]+} +@var{cond-specifier} @r{::=} @r{[}0@r{---}9@r{]+}@r{[}st@r{]}* -@var{states} @r{::=} @var{states} @var{sp} @var{state} - @r{|} @r{// empty} +@var{state-list} @r{::=} @var{state-list} @var{sp} @var{state} + @r{|} @r{// empty} +@end smallexample -@var{state} @r{::=} @var{state-id} @var{sp} @var{initial?} @var{conds} @var{sp} `-1' @var{transitions} @var{sp} `-1' +@noindent +The automaton description begins with a nonnegative number that gives the +number of states in the automaton. If the number of +states is 0, the automaton will not accept any input. If the number is +positive, it should be followed by a @var{cond-specifier} that determines the +number +and placement of acceptance conditions in the automaton. If the number of +acceptance conditions is 0, the automaton accepts an input word if and only if +it has a run on that word according to the definition given in the +Appendix (@pxref{Definitions}). + +The placement of acceptance conditions is specified by concatenating a +string formed from the symbols @samp{s} and @samp{t} to the number of +acceptance conditions (with no white space in between). The interpretation of +this string is as follows: +@itemize +@item +If the string is empty or does not include the symbol @samp{t}, the acceptance +conditions of the automaton are placed exclusively on its states. (This +alternative corresponds to the definition supported by @command{lbtt} 1.0.x.) + +@item +If the string is nonempty but does not include the symbol @samp{s}, the +automaton has acceptance conditions exclusively on its transitions. + +@item +Otherwise, the automaton has acceptance conditions on both states and +transitions. +@end itemize + +The @var{cond-specifier} is followed by a list of the descriptions of states in +the automaton. The format of this list is affected by the choice of the +placement of the acceptance conditions. +More precisely, the choice affects the interpretation of the +@var{cond-list} nonterminal symbol in the following fragment of the grammar: we +indicate this by prefixing the nonterminal with either ``'' or +``'' to denote that the list (together with its terminating @samp{-1}) +should be +omitted in automata that do not associate acceptance conditions with states or +transitions, respectively. + +@smallexample +@var{state} @r{::=} @var{state-id} @var{sp} @var{initial?} @r{}@var{cond-list} @var{transition-list} @var{state-id} @r{::=} @r{[}0@r{---}9@r{]+} @var{initial?} @r{::=} `0' @r{|} `1' -@var{conds} @r{::=} @var{conds} @var{sp} @var{acceptance-set-id} - @r{|} @r{// empty} +@var{cond-list} @r{::=} @var{sp} @var{acceptance-condition-id} @var{cond-list} + @r{|} @var{sp} `-1' -@var{acceptance-set-id} @r{::=} @r{[}0@r{---}9@r{]+} +@var{acceptance-condition-id} @r{::=} @r{[}0@r{---}9@r{]+} -@var{transitions} @r{::=} @var{transitions} @var{sp} @var{transition} - @r{|} @r{// empty} +@var{transition-list} @r{::=} @var{sp} @var{transition} @var{transition-list} + @r{|} @var{sp} `-1' -@var{transition} @r{::=} @var{state-id} @var{sp} @var{guard-formula} +@var{transition} @r{::=} @var{state-id} @r{}@var{cond-list} @var{sp} @var{guard-formula} `\n' @var{guard-formula} @r{::=} `t' @r{// ``true''} @@ -3644,39 +3847,25 @@ described using the following grammar: @r{// exclusive or} @end smallexample @noindent -(The quoted characters denote the characters themselves; @var{sp} denotes any -non-empty string of white space. Lines containing a // are comments and are not -part of the grammar.) -The automaton description begins with two nonnegative numbers, the first one -of which gives the number of states in the automaton, and the second one -tells the number of acceptance conditions in the automaton. If the number of -states is 0, the automaton will not accept any input. If the number of -acceptance conditions is 0, the automaton accepts an input word if and only if -it has a run on that word according to the definition given in the -Appendix (@pxref{Definitions}). +The description of each state begins with a numeric state identifier, which can +be any nonnegative integer. The state identifier should be followed by a number +telling whether the state is initial (@samp{1} if yes). The automaton should +have exactly one initial state. If the automaton has acceptance conditions +associated with its states, this number should then be followed by a list of +acceptance condition identifiers separated by white space. This list should be +terminated with @samp{-1}. -If the number of states is greater than zero, the acceptance condition count -should be followed by a list of states. The description of each state begins -with a numeric state identifier, which can be any nonnegative integer. The -state identifier should be followed by a number telling whether the state is -initial (@samp{1} if yes). The automaton should have exactly one initial state. -This number is then followed by a list of the identifiers of the acceptance -conditions to which the state belongs. This list should be terminated with -@samp{-1}. - -The state and acceptance condition identifiers need not be successive, and the -states or acceptance conditions can be listed in any order. The only -restrictions are that the identifiers of different states and acceptance -conditions should be unique and that the total number of different identifiers -should equal @var{num-states} or @var{num-conds}, -respectively. (The same identifiers can be shared between states and acceptance -conditions, however.) - -Finally, the acceptance condition list of each state should be followed by a -list of transitions (terminated again by @samp{-1}). Each transition consists -of a state identifier (the target state of the transition) and a propositional -formula that encodes the symbols of the alphabet +The state description should be followed by the list of transitions starting +from the state (terminated again by @samp{-1}). Each transition consists of a +state identifier (the target state of the transition), a list of acceptance +condition identifiers (if the automaton has acceptance conditions on +transitions), and a propositional formula @footnote{Although not described +formally in the grammar, the guard formulas can be specified in any +of the formats @command{lbtt} supports in its formula input files +(@pxref{--formulafile,,@samp{--formulafile} command line option}). Note that +the formula always needs to be terminated with a newline, though.} that encodes +the symbols of the alphabet @iftex @tex $2^{AP}$ @@ -3686,17 +3875,32 @@ $2^{AP}$ @math{2^AP} @end ifnottex (where @var{AP} is a finite set of atomic propositions) on which the -automaton is allowed to take the transition. +automaton is allowed to take the transition. The propositional formula should +be terminated with a newline. + +The state and acceptance condition identifiers need not be successive, and the +states or acceptance conditions can be listed in any order. The only +restrictions are that the identifiers of different states and acceptance +conditions should be unique and that the total number of different identifiers +should equal @var{num-states} or @var{num-conds}, +respectively. (The same identifiers can be shared between states and acceptance +conditions, however.) + Note that the output file should always contain a valid automaton description if the LTL-to-B@"uchi translation was successful, even in the case that the resulting automaton is empty (@command{lbtt} interprets a missing automaton description file as an error). -The following example illustrates the file format. +The following examples illustrate the file format. The first example +gives the description of an automaton with acceptance conditions on +states. Note that in this case the @samp{s} is optional for describing the +placement of acceptance conditions; therefore, the automaton files used with +@command{lbtt} 1.0.x are upwards compatible with newer versions of the tool +(provided that each guard of a transition is terminated by a newline). @smallexample -6 2 @r{// an automaton with six states and two acceptance conditions} +6 2s @r{// an automaton with six states and two acc.@ conditions on states} 0 1 -1 @r{// state 0: initial state, no acceptance conditions} 2 p1 @r{// transition to state 2, guard} @samp{p1} 5 p2 @r{// transition to state 5, guard} @samp{p2} @@ -3731,7 +3935,37 @@ The following example illustrates the file format. -1 @r{// end of state 12} @end smallexample +@noindent +The following example illustrates an automaton in which acceptance conditions +are placed on transitions. +@smallexample +4 3t @r{// four states, three acceptance conditions on transitions} +5 0 @r{// state 5: non-initial state} +84 0 -1 p1 @r{// transition to state 84, condition 0, guard} @samp{p1} +27 0 -1 & p1 ! p2 @r{// tr. to state 27, condition 0, guard} @samp{p1 /\ ! p2} +5 -1 t @r{// transition to state 5, no conditions, guard} @samp{true} +-1 @r{// end of state 5} +84 1 @r{// state 84: initial state} +5 1 -1 t @r{// transition to state 5, condition 1, guard} @samp{true} +27 0 -1 p1 @r{// transition to state 27, condition 0, guard} @samp{p1} +-1 @r{// end of state 84} +49 0 @r{// state 49: non-initial state} +5 -1 t @r{// transition to state 5, no conditions, guard} @samp{true} +49 1 4 -1 & p1 ! p2 @r{// tr.@ to state 49, conds.@ 1 and 4, guard} @samp{p1 /\ ! p2} +84 -1 p1 @r{// transition to state 84, no conditions, guard} @samp{p1} +-1 @r{// end of state 49} +27 0 @r{// state 27: non-initial state} +49 -1 & p1 p3 @r{// transition to state 49, no conds., guard} @samp{p1 /\ p3} +-1 @r{// end of state 27} +@end smallexample + +@noindent +Automata with acceptance conditions on both states and transitions can be +specified using a combination of the above two formats, that is, by using +@samp{st} as the acceptance condition placement specifier and including a list +of acceptance conditions both after the value determining the initialness of a +state, and after the identifier of the target state of each transition. @node The lbtt-translate utility, , Format for automata, Interfacing with lbtt @section The @command{lbtt-translate} utility @@ -3746,7 +3980,7 @@ translator algorithm implementations: @itemize @bullet @item @cindex @command{lbt} -@command{lbt} --- a free LTL-to-B@"uchi translation algorithm implementation +@command{lbt} --- an LTL-to-B@"uchi translation algorithm implementation based on the algorithm described in @ifnottex @ref{[GPVW95]}. @@ -3791,26 +4025,25 @@ See <@uref{http://spinroot.com/spin/whatispin.html}> @end ifnotinfo for more information. - @end itemize To use @command{lbtt} for testing the LTL-to-B@"uchi translators included in these tools, you should first install the tool normally by following its -installation instructions. Then add the following @samp{Algorithm} section in +installation instructions. Then add the following @samp{Translator} section in @command{lbtt}'s configuration file: @smallexample -Algorithm +Translator @{ Name = "@r{[@var{name for the implementation}]}" - Path = "@r{[@var{path-to-@command{lbtt-translate}}]}" + Path = "@r{[@var{path to @command{lbtt-translate}}]}" Parameters = "@r{[@var{implementation selector}]} @r{[@var{path to executable}]}" Enabled = Yes @} @end smallexample @noindent -where [@var{path-to-@command{lbtt-translate}}] contains the complete path and +where [@var{path to @command{lbtt-translate}}] contains the complete path and file name of the @command{lbtt-translate} tool executable, [@var{implementation selector}] is either of the options @samp{--lbt} or @samp{--spin}, and [@var{path to executable}] is the full path @@ -3822,7 +4055,9 @@ LTL formula operators available for generating random LTL formulas with @command{lbtt}. See the documentation of each translator for information about which operators are supported, and then change the parameters in @command{lbtt}'s configuration file accordingly to disable the unsupported -operators. +operators (or instruct @command{lbtt} to read the formulas from an external +file by invoking @command{lbtt} with the +@ref{--formulafile,,@samp{--formulafile} command line option}). The @command{lbtt-translate} utility can also be invoked directly from the shell to translate an LTL formula into a B@"uchi automaton using either of the @@ -3840,7 +4075,7 @@ E.@: Clarke Jr., O.@: Grumberg and D.@: Peled. Model checking. The MIT Press, 1999. @item @anchor{[Cou99]} [Cou99] -J.-M. Couvreur. On-the-fly verification of linear temporal logic. In +J.-M.@: Couvreur. On-the-fly verification of linear temporal logic. In @i{Proceedings of the World Congress on Formal Methods in the Development of Computing Systems (FM'99), volume I}, volume 1708 of @i{Lecture Notes in Computer Science}, pages 253---271. Springer-Verlag, 1999. @@ -3855,33 +4090,70 @@ of @i{Lecture Notes in Computer Science}, pages 249---260. Springer-Verlag, @item @anchor{[EH00]} [EH00] K.@: Etessami and G.@: Holzmann. Optimizing B@"uchi automata. In @i{Proceedings of the 11th International Conference on Concurrency Theory -(CONCUR'2000)}, volume 1877 of @i{Lecture Notes in Computer Science}, +(CONCUR 2000)}, volume 1877 of @i{Lecture Notes in Computer Science}, pages 153---167. Springer-Verlag, 2000. @item @anchor{[Ete99]} [Ete99] K.@: Etessami. Stutter-invariant languages, omega-automata, and temporal -logic. In @i{Proceedings of the 11th Conference on Computer Aided Verification -(CAV'99)}, volume 1633 of @i{Lecture Notes in Computer Science}, pages -236---248. Springer-Verlag, 1999. +logic. In @i{Proceedings of the 11th International Conference on Computer Aided +Verification (CAV'99)}, volume 1633 of @i{Lecture Notes in Computer Science}, +pages 236---248. Springer-Verlag, 1999. + +@item @anchor{[Ete02]} [Ete02] +K.@: Etessami. A hierarchy of polynomial-time computable simulations for +automata. In @i{Proceedings of the 13th International Conference on +Concurrency Theory (CONCUR 2002)}, volume 2421 of +@i{Lecture Notes in Computer Science}, pages 131---144. Springer-Verlag, 2002. @item @anchor{[EWS01]} [EWS01] K.@: Etessami, Th.@: Wilke and R.@: Schuller. Fair simulation relations, parity games, and state space reduction for B@"uchi automata. In @i{Proceedings of the 28th International Colloquium on Automata, Languages and -Programming (ICALP'2001)}, volume 2076 of +Programming (ICALP 2001)}, volume 2076 of @i{Lecture Notes in Computer Science}, pages 694---707. Springer-Verlag, 2001. +@item @anchor{[Fri03]} [Fri03] +C.@: Fritz. Constructing B@"uchi automata from linear temporal logic using +simulation relations for alternating B@"uchi automata. In +@i{Proceedings of the 8th International Conference on Implementation and +Application of Automata (CIAA 2003)}, volume 2759 of +@i{Lecture Notes in Computer Science}, pages 35---48. Springer-Verlag, 2003. + @item @anchor{[GO01]} [GO01] P.@: Gastin and D.@: Oddoux. Fast LTL to B@"uchi automata translation. In @i{Proceedings of the 13th International Conference on Computer -Aided Verification (CAV'01)}, volume 2102 of @i{Lecture Notes in Computer +Aided Verification (CAV 2001)}, volume 2102 of @i{Lecture Notes in Computer Science}, pages 53---65. Springer-Verlag, 2001. +@item @anchor{[GO03]} [GO03] +P.@: Gastin and D.@: Oddoux. LTL with past and two-way weak alternating +automata. In @i{Proceedings of the 28th International Symposium on Mathematical +Foundations of Computer Science (MFCS 2003)}, volume 2747 of +@i{Lecture Notes in Computer Science}, pages 439---448. Springer-Verlag, 2003. + +@item @anchor{[Gei01]} [Gei01] +M.@: C.@: W.@: Geilen. On the construction of monitors for temporal logic +properties. @i{Electronic Notes for Theoretical Computer Science}, 55(2), 2001. + @item @anchor{[GPVW95]} [GPVW95] R.@: Gerth, D.@: Peled, M.@: Y.@: Vardi and P.@: Wolper. Simple on-the-fly automatic verification of linear temporal logic. In -@i{Proceedings of 15th Workshop Protocol Specification, Testing, and -Verification (PSTV'95)}, pages 3---18. Chapman & Hall, 1995. +@i{Proceedings of 15th IFIP WG6.1 International Symposium on Protocol +Specification, Testing, and Verification (PSTV'95)}, pages 3---18. +Chapman & Hall, 1995. + +@item @anchor{[GL02]} [GL02] +D.@: Giannakopoulou and F.@: Lerda. From states to transitions: Improving +translation of LTL formulae to B@"uchi automata. In +@i{Proceedings of the 22nd IFIP WG6.1 International Conference on Formal +Techniques for Networked and Distributed Systems (FORTE 2002)}, volume 2529 of +@i{Lecture Notes in Computer Science}, pages 308---326. Springer-Verlag, 2002. + +@item @anchor{[GSB02]} [GSB02] +S.@: Gurumurthy, F.@: Somenzi and R.@: Bloem. Fair simulation minimization. +In @i{Proceedings of the 14th International Conference on Computer Aided +Verification (CAV 2002)}, volume 2404 of @i{Lecture Notes in Computer Science}, +pages 610---624. Springer-Verlag, 2002. @item @anchor{[GViz]} [GViz] GraphViz - open source graph drawing software. See @@ -3893,7 +4165,7 @@ GraphViz - open source graph drawing software. See @end ifnotinfo @item @anchor{[Hol97]} [Hol97] -G. Holzmann. The model checker +G.@: J.@: Holzmann. The model checker @ifnottex SPIN. @end ifnottex @@ -3904,14 +4176,40 @@ SPIN. @end iftex @i{IEEE Transactions on Software Engineering}, 23(5):279---295, 1997. +@item @anchor{[Isl94]} [Isl94] +A.@: Isli. Mapping an LPTL formula into a B@"uchi alternating automaton +accepting its models. In @i{Temporal Logic: Proceedings of the ICTL Workshop}, +pages 85---90. Research Report MPI-I-94-230, Max-Planck-Institut f@"ur +Informatik, 1994. + +@item @anchor{[Lat03]} [Lat03] +T.@: Latvala. Efficient model checking of safety properties. In +@i{Proceedings of the 10th Spin Workshop on Model Checking of Software +(SPIN 2003)}, volume 2648 of @i{Lecture Notes in Computer Science}, pages +74---88. Springer-Verlag, 2003. + +@item @anchor{[Sch01]} [Sch01] +K.@: Schneider. Improving automata generation for linear temporal logic by +considering the automaton hierarchy. In @i{Proceedings of the 8th International +Conference on Logic for Programming, Artificial Intelligence and Reasoning +(LPAR 2001)}, volume 2250 of @i{Lecture Notes in Computer Science}, pages +39---54. Springer-Verlag, 2001. + +@item @anchor{[ST03]} [ST03] +R.@: Sebastiani and S.@: Tonetta. ``More deterministic'' vs.@: ``smaller'' +B@"uchi automata for efficient LTL model checking. In +@i{Proceedings of the 12th Advanced Research Working Conference on Correct +Hardware Design and Verification Methods (CHARME 2003)}, volume 2860 of +@i{Lecture Notes in Computer Science}, pages 126---140. Springer-Verlag, 2003. + @item @anchor{[SB00]} [SB00] F.@: Somenzi and R.@: Bloem. Efficient B@"uchi automata from LTL formulae. In @i{Proceedings of the 12th International Conference on Computer Aided -Verification (CAV'00)}, volume 1855 of @i{Lecture Notes in Computer Science}, +Verification (CAV 2000)}, volume 1855 of @i{Lecture Notes in Computer Science}, pages 247---263. Springer-Verlag, 2000. @item @anchor{[Tau00]} [Tau00] -H. Tauriainen. Automated testing of B@"uchi automata translators +H.@: Tauriainen. Automated testing of B@"uchi automata translators for linear temporal logic. Research report A66, Laboratory for Theoretical Computer Science, Helsinki University of Technology, Espoo, Finland, 2000. Available on the WWW at @@ -3926,11 +4224,15 @@ Computer Science, Helsinki University of Technology, Espoo, Finland, @end iftex @item @anchor{[TH02]} [TH02] -H. Tauriainen and K. Heljanko. Testing LTL formula translation into B@"uchi +H.@: Tauriainen and K.@: Heljanko. Testing LTL formula translation into B@"uchi automata. @i{International Journal on Software Tools for Technology Transfer (STTT)} 4(1):57---70, 2002. +@item @anchor{[Thi02]} [Thi02] +X.@: Thirioux. Simple and efficient translation from LTL formulas to B"uchi +automata. @i{Electronic Notes in Theoretical Computer Science}, 66(2), 2002. + @item @anchor{[Var96]} [Var96] M.@: Y.@: Vardi. An automata-theoretic approach to linear temporal logic. In @i{Logics for Concurrency: Structure versus Automata}, volume 1043 of @@ -3939,10 +4241,17 @@ In @i{Logics for Concurrency: Structure versus Automata}, volume 1043 of @item @anchor{[VW86]} [VW86] M.@: Y.@: Vardi and P.@: Wolper. An automata-theoretic approach to -automatic program verification. In @i{Proceedings of the 1st IEEE +automatic program verification. In @i{Proceedings of the First IEEE Symposium on Logic in Computer Science (LICS'86)}, pages 332---344. IEEE Computer Society Press, 1986. +@item @anchor{[Wol01]} [Wol01] +P.@: Wolper. Constructing automata from temporal logic formulas: A tutorial. +In @i{Lectures on Formal Methods and Performance Analysis: First EEF/Euro +Summer School on Trends in Computer Science, Revised Lectures}, volume 2090 +of @i{Lecture Notes in Computer Science}, pages 261---277. Springer-Verlag, +2001. + @end table @@ -4368,14 +4677,21 @@ conditions. @appendixsubsec Formal definition of generalized automata -Formally, a generalized B@"uchi automaton can be represented as a tuple +Formally, a generalized B@"uchi automaton can be represented as a +tuple@footnote{This definition differs from those commonly found in +the literature by specifying the acceptance conditions in terms of a separate +set that is independent of the other components of the automaton, together with +an +explicit labeling function for the states. This is to allow the definition to +correspond more accurately to the automata that can be described in +input files.} @iftex @tex -$\langle \Sigma, Q, \Delta, q^0, \cal{F}\rangle$, +$\langle \Sigma, Q, \Delta, q_I, \cal{F}, \lambda\rangle$, @end tex @end iftex @ifnottex -@math{}, +@math{}, @end ifnottex where @@ -4389,7 +4705,7 @@ $\Sigma$ @ifnottex @math{S} @end ifnottex -is the @emph{alphabet} +is the finite @emph{alphabet} @iftex @tex ($\Sigma = 2^{AP}$ in this case), @@ -4400,42 +4716,55 @@ is the @emph{alphabet} @end ifnottex @item -@math{Q} is the set of @emph{states}, +@math{Q} is the finite set of @emph{states}, @item @iftex @tex -$\Delta \subseteq Q \times 2^\Sigma \times Q$ +$\Delta \subseteq Q \times 2^\Sigma \times 2^{\cal{F}} \times Q$ @end tex @end iftex @ifnottex -@math{R} (a subset of @math{Q x 2^S x Q}) +@math{R} (a subset of @math{Q x 2^S x 2^F x Q}) @end ifnottex -is the -@emph{transition relation}, +is the set of @emph{transitions} (each of which consists of four components +called the @emph{start state}, the @emph{guard}, the +@emph{acceptance component}, and the @emph{target state}, respectively), @item @iftex @tex -$q^0$ +$q_I$ @end tex @end iftex @ifnottex @math{q} @end ifnottex -is the @emph{initial state}, and +is the @emph{initial state}, @item @iftex @tex -${\cal F} \subseteq 2^Q$ +${\cal F} = \{f_1,f_2,\ldots,f_n\}$ (for some finite $n$) @end tex @end iftex @ifnottex -@math{F}, a collection of subsets of @math{Q}, +@math{F = @{f1, f2, ..., fn@} (for some finite @math{n})} @end ifnottex -is the set of @emph{acceptance conditions}. (A ``nongeneralized'' -B@"uchi automaton has exactly one acceptance condition.) +is the set of @emph{acceptance conditions} (a ``nongeneralized'' +B@"uchi automaton has exactly one acceptance condition), and + +@item +@iftex +@tex +$\lambda: Q \rightarrow 2^{\cal F}$ +@end tex +@end iftex +@ifnottex +@math{L: Q -> 2^F} +@end ifnottex +is a @emph{labeling function} that associates each state of the automaton +with a set of acceptance conditions. @end itemize @@ -4457,19 +4786,22 @@ $2^{AP}$ @ifnottex @math{2^AP} @end ifnottex -is an infinite sequence of states +is an infinite sequence of pairs of states and transitions @iftex @tex -$\langle q_0, q_1, q_2, \ldots \rangle \in Q^\omega$ +$\langle (q_0, t_0), (q_1, t_1), (q_2, t_2), \ldots \rangle \in +(Q\times\Delta)^\omega$ @end tex @end iftex @ifnottex -@math{} (where each @math{q(i)} is a state in @math{Q}) +@math{<(q(0),t(0)), (q(1),t(1)), (q(2),t(2)) ...>} +(where each @math{q(i)} is a state in @math{Q} and each @math{t(i)} is a +transition in @math{R}) @end ifnottex such that @iftex @tex -$q_0 = q^0$ +$q_0 = q_I$ @end tex @end iftex @ifnottex @@ -4484,25 +4816,24 @@ $i \geq 0$, @ifnottex @math{i >= 0}, @end ifnottex -there is a triple @iftex @tex -$\langle q_i, X, q_{i+1}\rangle \in \Delta$ +$t_i = \langle q_i, X_i, Y_i, q_{i+1}\rangle \in \Delta$ @end tex @end iftex @ifnottex -@math{} in @math{R} +@math{t(i) = } in @math{R} @end ifnottex such that @iftex @tex -$x_i \in X$. +$x_i \in X_i$. @end tex @end iftex @ifnottex -@math{x(i)} belongs to @math{X}. +@math{x(i)} belongs to @math{X(i)}. @end ifnottex -Because +(Because the relation @iftex @tex $\Delta$ @@ -4514,11 +4845,11 @@ $\Delta$ is not necessarily a function from @iftex @tex -$Q \times 2^\Sigma$ +$Q \times 2^\Sigma \times 2^{\cal{F}}$ @end tex @end iftex @ifnottex -@math{Q x 2^S} +@math{Q x 2^S x 2^F} @end ifnottex to @iftex @@ -4529,38 +4860,63 @@ $Q$, @ifnottex @math{Q}, @end ifnottex -the automaton may have many runs on the same input. +the automaton may have many runs on the same input.) A run @iftex @tex -$\langle q_0, q_1, q_2, \ldots\rangle$ +$\langle (q_0,t_0), (q_1,t_1), (q_2,t_2), \ldots\rangle$ @end tex @end iftex @ifnottex -@math{} +@math{<(q(0),t(0)), (q(1),t(1)), (q(2),t(2)), ...>} @end ifnottex -is @emph{accepting} if and only if additionally -for each acceptance condition +(where @iftex @tex -$F_j \in {@cal F}$, +$t_i = \langle q_i,X_i,Y_i,q_{i+1}\rangle\in\Delta$ for all $i$) @end tex @end iftex @ifnottex -@math{C(j)} in @math{F}, +@math{t(i) = < q(i), X(i), Y(i), q(i+1) >} for all @math{i}) @end ifnottex -there is a state +is @emph{accepting} if and only if additionally, for each acceptance condition @iftex @tex -$q_j \in F_j$ +$f \in {@cal F}$, @end tex @end iftex @ifnottex -@math{q(j)} in @math{C(j)} +@math{f} in @math{F}, @end ifnottex -that occurs infinitely often in the run. The automaton @emph{accepts} an -infinite sequence +@iftex +@tex +$f \in \lambda(q_i)$ +@end tex +@end iftex +@ifnottex +@math{f} is in @math{L(q(i))} +@end ifnottex +or +@iftex +@tex +$f \in Y_i$ +@end tex +@end iftex +@ifnottex +@math{f} is in @math{Y(i)} +@end ifnottex +for infinitely many +@iftex +@tex +$i$. +@end tex +@end iftex +@ifnottex +@math{i}. +@end ifnottex + +The automaton @emph{accepts} an infinite sequence @iftex @tex $\langle x_0, x_1, x_2, \ldots\rangle \in 2^{AP}$ @@ -4573,9 +4929,7 @@ if and only if the automaton has at least one accepting run on this sequence. @appendixsubsec Transition label encoding -In practice, a transition label of a B@"uchi automaton can be expressed as a -propositional formula, since these formulas readily encode sets of subsets of -the alphabet +When working with automata on words over the alphabet @iftex @tex $2^{AP}$, @@ -4584,8 +4938,9 @@ $2^{AP}$, @ifnottex @math{2^AP}, @end ifnottex -namely, the models of the formula. A transition can then be seen as a rule -``if in state +the guards of transitions can be expressed as propositional formulas by +identifying a set of symbols from this alphabet with the set of models of a +propositional formula. A transition can then be seen as a rule ``if in state @iftex @tex $q_i$ @@ -4626,32 +4981,74 @@ Many LTL-to-B@"uchi translation algorithms presented in the literature @iftex [GPVW95]) @end iftex -use a slightly different definition for generalized B@"uchi automata, -which permits a B@"uchi automaton to have several initial states and -places the labels on states instead of transitions. However, these B@"uchi -automata can be easily converted into an equivalent B@"uchi automaton in the -above format with the following steps (we assume here that -each state of the automaton is labelled with a set of LTL formulas that -should hold in that state): +are based on a slightly different definition for generalized B@"uchi automata, +where the automata can have several initial states, acceptance is determined +using a family of sets of states, and the guards of transitions are replaced +with an additional state labeling that associates a set of LTL +formulas with each state. These automata can easily be described using the +above definition through the following steps: @enumerate @item -Add a new state (with an empty label) into the automaton and add transitions -from it to each initial state of the original automaton. Make the new state -the (only) initial state of the automaton. +Add a new state (associated with an empty set of LTL formulas) into the +automaton and add transitions from it to each initial state of the original +automaton. Make the new state the (only) initial state of the automaton. @item For each state of the (modified) automaton, construct a conjunction of all -propositional -constraints (i.e., all formulas with no temporal operators) in the label of -the state and copy the conjunction onto each transition coming into the -state. Then remove all labels from the states. -@end enumerate +propositional constraints (all formulas with no temporal operators) +associated with the state and make the conjunction the guard of each transition +coming into the state (the acceptance component of each transition remains +empty). Then remove the association between states and sets of formulas. -Conversions from other definitions can be handled in a similar way. In -some cases (e.g., if the acceptance conditions are subsets of transitions -instead of subsets of states), the conversion may also require making copies -of some states and then adjusting the transition labels appropriately. +@item +If +@iftex +@tex +$Q_1,Q_2,\ldots,Q_k \in 2^Q$ +@end tex +@end iftex +@ifnottex +@math{Q1, Q2, ..., Qk} (subsets of @math{Q}) +@end ifnottex +are the sets of states determining acceptance in the original automaton, +let +@iftex +@tex +$f_i = Q_i$ +@end tex +@end iftex +@ifnottex +@math{fi = Qi} +@end ifnottex +for all +@iftex +@tex +$1 \leq i \leq k$, +@end tex +@end iftex +@ifnottex +@math{1 <= 1 <= k}, +@end ifnottex +and let +@iftex +@tex +$\lambda(q) = \{f_i\mid q \in f_i\}$ +@end tex +@end iftex +@ifnottex +@math{L(q) = @{fi | q is a member of fi@}} +@end ifnottex +for all states +@iftex +@tex +$q$. +@end tex +@end iftex +@ifnottex +@math{q}. +@end ifnottex +@end enumerate @@ -4664,7 +5061,7 @@ model checking result cross-comparison test (@pxref{Model checking result cross-comparison test}) and the model checking result consistency check (@pxref{Model checking result consistency check}). Formally, -the state spaces are Kripke structures with a total transition +the state spaces are (finite) Kripke structures with a total transition relation, i.e., directed graphs with a set of atomic propositions attached to each state, with each state having at least one immediate successor (which may be the state itself). The precise @@ -4685,7 +5082,7 @@ where @itemize @bullet @item -@math{S} is the set of @emph{states}, +@math{S} is the finite set of @emph{states}, @item @iftex @@ -4705,7 +5102,7 @@ ${\cal L}: S \rightarrow 2^{AP}$ @end tex @end iftex @ifnottex -@math{L} (a function from @math{S} to @math{2^AP}) +@math{L: S -> 2^AP} @end ifnottex is the @emph{labeling function} which maps each state to a set of atomic propositions that hold in the state. diff --git a/lbtt/src/BuchiAutomaton.cc b/lbtt/src/BuchiAutomaton.cc index d63667dd8..1b6c6d18e 100644 --- a/lbtt/src/BuchiAutomaton.cc +++ b/lbtt/src/BuchiAutomaton.cc @@ -92,7 +92,9 @@ BuchiAutomaton::BuchiAutomaton(const BuchiAutomaton& automaton) : ++transition) connect(state, static_cast(*transition)->targetNode(), - static_cast(*transition)->guard()); + static_cast(*transition)->guard(), + static_cast(*transition) + ->acceptanceSets()); operator[](state).acceptanceSets().copy(automaton[state].acceptanceSets(), number_of_acceptance_sets); @@ -129,7 +131,9 @@ BuchiAutomaton& BuchiAutomaton::operator=(const BuchiAutomaton& automaton) ++transition) connect(state, static_cast(*transition)->targetNode(), - static_cast(*transition)->guard()); + static_cast(*transition)->guard(), + static_cast(*transition) + ->acceptanceSets()); operator[](state).acceptanceSets().copy (automaton[state].acceptanceSets(), number_of_acceptance_sets); @@ -190,113 +194,13 @@ BuchiAutomaton::size_type BuchiAutomaton::expand(size_type node_count) return nodes.size() - 1; } -/* ========================================================================= */ -BuchiAutomaton* BuchiAutomaton::regularize() const -/* ---------------------------------------------------------------------------- - * - * Description: Converts a generalized Büchi automaton (i.e., an automaton - * with any number of accepting state sets) into an automaton - * with only one set of accepting states. - * - * Arguments: None. - * - * Returns: A pointer to an equivalent BuchiAutomaton with exactly one - * set of accepting states. - * - * ------------------------------------------------------------------------- */ -{ - /* - * If `this' automaton already has exactly one set of accepting states, - * return a copy of `this' automaton. - */ - - if (number_of_acceptance_sets == 1) - return new BuchiAutomaton(*this); - - /* - * Otherwise construct the result using a depth-first search in `this' - * automaton. - */ - - typedef pair ExpandedState; - - BuchiAutomaton* result_automaton = new BuchiAutomaton(0, 0, 1); - - if (empty()) - return result_automaton; - - stack > - states_to_process; - - map, ALLOC(size_type) > - state_mapping; - - const GraphEdgeContainer* transitions; - - size_type result_source_state, result_target_state; - map, ALLOC(size_type) > - ::const_iterator check_state; - - ExpandedState state = make_pair(initial_state, 0); - - states_to_process.push(state); - state_mapping[state] = result_automaton->expand(); - - while (!states_to_process.empty()) - { - state = states_to_process.top(); - states_to_process.pop(); - - result_source_state = state_mapping[state]; - transitions = &operator[](state.first).edges(); - - if (number_of_acceptance_sets == 0 - || operator[](state.first).acceptanceSets().test(state.second)) - { - if (state.second == 0) - (*result_automaton)[result_source_state].acceptanceSets().setBit(0); - - if (number_of_acceptance_sets > 0) - { - ++state.second; - state.second %= number_of_acceptance_sets; - } - } - - for (GraphEdgeContainer::const_iterator transition = transitions->begin(); - transition != transitions->end(); - ++transition) - { - state.first = (*transition)->targetNode(); - - check_state = state_mapping.find(state); - - if (check_state == state_mapping.end()) - { - result_target_state = result_automaton->expand(); - state_mapping[state] = result_target_state; - states_to_process.push(state); - } - else - result_target_state = check_state->second; - - result_automaton->connect(result_source_state, result_target_state, - static_cast - (*transition)->guard()); - } - } - - return result_automaton; -} - /* ========================================================================= */ void BuchiAutomaton::read(istream& input_stream) /* ---------------------------------------------------------------------------- * * Description: Reads an automaton description (which may represent a * generalized Büchi automaton) from a stream and stores it - * into the automaton object, converting it to a regular - * Büchi automaton if necessary. + * into the automaton object. * * Argument: input_stream -- A reference to an input stream. * @@ -312,20 +216,63 @@ void BuchiAutomaton::read(istream& input_stream) try { - /* - * Read in the number of states in the generalized Büchi automaton. - */ + /* Read the number of states in the generalized Büchi automaton. */ einput_stream >> number_of_states; - /* - * If the automaton is empty, do nothing. - */ + /* If the automaton is empty, do nothing. */ if (number_of_states == 0) return; - einput_stream >> number_of_acceptance_sets; + /* + * Determine the number and placement of acceptance sets. + * (Acceptance sets are described using strings described by the regular + * expression [0-9]+(s|S|t|T)*, where the [0-9]+ part corresponds to the + * number of the sets, and the (s|S|t|T)* part corresponds to the + * placement of the sets -- s or S for states, t or T for transitions. + * To retain compatibility with lbtt 1.0.x, the acceptance set placement + * defaults to acceptance sets on states if is not given explicitly.) + */ + + bool acceptance_sets_on_states = false; + bool acceptance_sets_on_transitions = false; + + string tok; + einput_stream >> tok; + + string::size_type s_pos = string::npos; + string::size_type t_pos = string::npos; + string::size_type pos = tok.find_first_not_of("0123456789"); + if (pos == 0) + throw AutomatonParseException + ("invalid specification for acceptance sets"); + + number_of_acceptance_sets = strtoul(tok.substr(0, pos).c_str(), 0, 10); + + for ( ; pos < tok.length(); ++pos) + { + if (tok[pos] == 's' || tok[pos] == 'S') + { + s_pos = pos; + acceptance_sets_on_states = true; + } + else if (tok[pos] == 't' || tok[pos] == 'T') + { + t_pos = pos; + acceptance_sets_on_transitions = true; + } + else + throw AutomatonParseException + ("invalid specification for acceptance sets"); + } + if (s_pos == string::npos && t_pos == string::npos) + { + acceptance_sets_on_states = true; + acceptance_sets_on_transitions = false; + } + + BitArray acc_sets(number_of_acceptance_sets); /* * Allocate space for the regular Büchi automaton that will be constructed @@ -446,36 +393,41 @@ void BuchiAutomaton::read(istream& input_stream) operator[](current_state).acceptanceSets().clear (number_of_acceptance_sets); - while (1) + if (acceptance_sets_on_states) { - einput_stream >> acceptance_set_mapping.first; - - if (acceptance_set_mapping.first == -1) - break; - - acceptance_set_finder = - acceptance_set_map.insert(acceptance_set_mapping); - - if (!acceptance_set_finder.second) - acceptance_set = (acceptance_set_finder.first)->second; - else + while (1) { - if (acceptance_set_mapping.second >= number_of_acceptance_sets) - throw AutomatonParseException("number of acceptance sets " - "does not match automaton state " - "definitions"); + einput_stream >> acceptance_set_mapping.first; - acceptance_set = acceptance_set_mapping.second; - ++acceptance_set_mapping.second; - } + if (acceptance_set_mapping.first == -1) + break; - operator[](current_state).acceptanceSets().setBit(acceptance_set); + acceptance_set_finder = + acceptance_set_map.insert(acceptance_set_mapping); + + if (!acceptance_set_finder.second) + acceptance_set = (acceptance_set_finder.first)->second; + else + { + if (acceptance_set_mapping.second >= number_of_acceptance_sets) + throw AutomatonParseException("number of acceptance sets " + "does not match automaton state " + "definitions"); + + acceptance_set = acceptance_set_mapping.second; + ++acceptance_set_mapping.second; + } + + operator[](current_state).acceptanceSets().setBit(acceptance_set); + } } /* * Process the transitions from the state to other states. Read a * target state id and add a mapping for it in the translation table if - * necessary. Then, read the propositional formula guarding the + * necessary. If the automaton is allowed to have acceptance sets + * associated with transitions, read an additional list of acceptance + * sets. Finally, read the propositional formula guarding the * transition and connect the current state to the target using the * guard formula. */ @@ -501,6 +453,42 @@ void BuchiAutomaton::read(istream& input_stream) state_mapping.second++; } + acc_sets.clear(number_of_acceptance_sets); + + /* + * If automata with acceptance sets on transitions are accepted, read + * the acceptance sets associated with the transition. + */ + + if (acceptance_sets_on_transitions) + { + while (1) + { + einput_stream >> acceptance_set_mapping.first; + + if (acceptance_set_mapping.first == -1) + break; + + acceptance_set_finder = + acceptance_set_map.insert(acceptance_set_mapping); + + if (!acceptance_set_finder.second) + acceptance_set = (acceptance_set_finder.first)->second; + else + { + if (acceptance_set_mapping.second >= number_of_acceptance_sets) + throw AutomatonParseException("number of acceptance sets " + "does not match automaton state " + "definitions"); + + acceptance_set = acceptance_set_mapping.second; + ++acceptance_set_mapping.second; + } + + acc_sets.setBit(acceptance_set); + } + } + try { guard = ::Ltl::LtlFormula::read(input_stream); @@ -516,7 +504,7 @@ void BuchiAutomaton::read(istream& input_stream) throw AutomatonParseException("illegal operators in guard formula"); } - connect(current_state, neighbor_state, guard); + connect(current_state, neighbor_state, guard, acc_sets); } processed_states.setBit(current_state); @@ -544,297 +532,6 @@ void BuchiAutomaton::read(istream& input_stream) } } -/* ========================================================================= */ -BuchiAutomaton* BuchiAutomaton::intersect - (const BuchiAutomaton& a1, const BuchiAutomaton& a2, - map, ALLOC(StateIdPair) >* - intersection_state_mapping) -/* ---------------------------------------------------------------------------- - * - * Description: Computes the intersection of two Büchi automata and returns - * a pointer to the intersection of the two automata. - * - * Arguments: a1, a2 -- References to two constant - * Büchi automata. - * intersection_state_mapping -- An (optional) pointer to a - * map which can be used to find - * out the state identifiers of - * the original automata to - * which a particular state in - * the intersection corresponds. - * - * Returns: A newly allocated BuchiAutomaton representing the - * intersection of the two automata. - * - * ------------------------------------------------------------------------- */ -{ - if (intersection_state_mapping != 0) - intersection_state_mapping->clear(); - - /* - * If either of the original automata is empty, the intersection is also - * empty. - */ - - if (a1.empty() || a2.empty()) - return new BuchiAutomaton(0, 0, 0); - - BuchiAutomaton* automaton; - - /* - * Determine the number of acceptance sets in the intersection. - */ - - const bool a1_has_no_acceptance_sets = (a1.number_of_acceptance_sets == 0); - const bool a2_has_no_acceptance_sets = (a2.number_of_acceptance_sets == 0); - - unsigned long int number_of_intersection_acceptance_sets; - - if (a1_has_no_acceptance_sets && a2_has_no_acceptance_sets) - number_of_intersection_acceptance_sets = 0; - else - number_of_intersection_acceptance_sets = a1.number_of_acceptance_sets - + a2.number_of_acceptance_sets; - - automaton = new BuchiAutomaton(1, 0, number_of_intersection_acceptance_sets); - - /* - * A stack for processing pairs of states of the original automata. - */ - - stack > - unprocessed_states; - - /* - * `state_mapping' maps pairs of states of the original automata to the - * states of the new automaton. - */ - - map, ALLOC(size_type) > - state_mapping; - - size_type first_free_id = 1; /* First free identifier for a - * new state in the intersection - * automaton. - */ - - const StateIdPair* state_pair; /* Pointer to pair of two state - * identifiers of the original - * automata. - */ - - size_type intersect_state; /* `Current' state in the - * intersection automaton. - */ - - bool intersect_state_valid; /* True if the current state has - * been determined by using the - * mapping. - */ - - BitArray* intersect_acceptance_sets; /* Pointers for accessing the */ - const BitArray* acceptance_sets; /* acceptance sets of the new - * and the original automata. - */ - - const GraphEdgeContainer* transitions1; /* Pointers for accessing the */ - const GraphEdgeContainer* transitions2; /* transitions of the two - * original automata. - */ - - ::Ltl::LtlFormula* guard1; /* Pointers for accessing the */ - ::Ltl::LtlFormula* guard2; /* guard formulas of the */ - ::Ltl::LtlFormula* new_guard; /* transitions of the - * automata. - */ - - /* - * Insert the initial state into the state mapping. - */ - - state_mapping.insert(make_pair(make_pair(a1.initial_state, a2.initial_state), - 0)); - unprocessed_states.push(&(state_mapping.begin()->first)); - - /* - * Adjust the acceptance sets of the initial state of the intersection. - */ - - intersect_acceptance_sets = &((*automaton)[0].acceptanceSets()); - - if (!a1_has_no_acceptance_sets) - { - acceptance_sets = &(a1[a1.initial_state].acceptanceSets()); - - for (unsigned long int accept_set = 0; - accept_set < a1.number_of_acceptance_sets; - accept_set++) - { - if (acceptance_sets->test(accept_set)) - intersect_acceptance_sets->setBit(accept_set); - } - } - - if (!a2_has_no_acceptance_sets) - { - acceptance_sets = &(a2[a2.initial_state].acceptanceSets()); - - for (unsigned long int accept_set = 0; - accept_set < a2.number_of_acceptance_sets; - accept_set++) - { - if (acceptance_sets->test(accept_set)) - intersect_acceptance_sets->setBit(a1.number_of_acceptance_sets - + accept_set); - } - } - - /* - * Pop pairs of states of the two original automata until all states have - * been processed. - */ - - try - { - while (!unprocessed_states.empty()) - { - if (::user_break) - throw UserBreakException(); - - intersect_state_valid = false; - state_pair = unprocessed_states.top(); - unprocessed_states.pop(); - - /* - * Loop through the transitions of the two original automata. If the - * conjunction of the guard formulae of any two transitions is - * satisfiable, insert a new transition into the intersection automaton. - * Create new states in the intersection automaton if necessary. - */ - - transitions1 = &a1[state_pair->first].edges(); - transitions2 = &a2[state_pair->second].edges(); - - for (GraphEdgeContainer::const_iterator tr1 = transitions1->begin(); - tr1 != transitions1->end(); - ++tr1) - { - guard1 = &(static_cast(*tr1)->guard()); - - for (GraphEdgeContainer::const_iterator tr2 = transitions2->begin(); - tr2 != transitions2->end(); - ++tr2) - { - guard2 = &(static_cast(*tr2)->guard()); - - new_guard = &Ltl::And::construct(*guard1, *guard2); - - if (new_guard->satisfiable()) - { - /* - * Determine the `current' state of the intersection automaton. - */ - - if (!intersect_state_valid) - { - intersect_state = state_mapping[*state_pair]; - intersect_state_valid = true; - } - - /* - * Test whether the state pair pointed to by the two transitions - * is already in the intersection. - */ - - pair, - ALLOC(size_type) >::iterator, bool> - check_state; - - check_state - = state_mapping.insert(make_pair(make_pair((*tr1)->targetNode(), - (*tr2)->targetNode()), - first_free_id)); - - if (check_state.second) /* insertion occurred? */ - { - automaton->expand(); - - /* - * Determine the acceptance sets to which the new state in the - * intersection automaton belongs. - */ - - intersect_acceptance_sets - = &((*automaton)[first_free_id].acceptanceSets()); - - if (!a1_has_no_acceptance_sets) - { - acceptance_sets = &(a1[check_state.first->first.first]. - acceptanceSets()); - - for (unsigned long int accept_set = 0; - accept_set < a1.number_of_acceptance_sets; - accept_set++) - { - if (acceptance_sets->test(accept_set)) - intersect_acceptance_sets->setBit(accept_set); - } - } - - if (!a2_has_no_acceptance_sets) - { - acceptance_sets = &(a2[check_state.first->first.second]. - acceptanceSets()); - - for (unsigned long int accept_set = 0; - accept_set < a2.number_of_acceptance_sets; - accept_set++) - { - if (acceptance_sets->test(accept_set)) - intersect_acceptance_sets->setBit - (a1.number_of_acceptance_sets + accept_set); - } - } - - /* - * Connect the `current' state of the intersection automaton to - * the new state. - */ - - automaton->connect(intersect_state, first_free_id, new_guard); - first_free_id++; - unprocessed_states.push(&(check_state.first->first)); - } - else - automaton->connect(intersect_state, check_state.first->second, - new_guard); - } - else - ::Ltl::LtlFormula::destruct(new_guard); - } - } - } - - if (intersection_state_mapping != 0) - { - for (map, ALLOC(size_type) > - ::const_iterator mapping = state_mapping.begin(); - mapping != state_mapping.end(); - ++mapping) - intersection_state_mapping->insert(make_pair(mapping->second, - mapping->first)); - } - } - catch (...) - { - delete automaton; - throw; - } - - return automaton; -} - /* ========================================================================= */ void BuchiAutomaton::print (ostream& stream, const int indent, const GraphOutputFormat fmt) const @@ -876,7 +573,7 @@ void BuchiAutomaton::print << " transitions.\n" + string(indent, ' ') + "The automaton has " << number_of_acceptance_sets - << " sets of accepting states.\n" + string(indent, ' ') + << " acceptance sets.\n" + string(indent, ' ') + "The reachable part of the automaton contains\n" + string(indent + 4, ' ') << reachable_part_statistics.first @@ -937,7 +634,8 @@ void BuchiAutomaton::print ++transition) { estream << string(indent + 2, ' ') + 'n' << state; - (*transition)->print(stream, indent, fmt); + static_cast(*transition) + ->print(stream, indent, fmt, number_of_acceptance_sets); estream << ";\n"; } } @@ -960,16 +658,23 @@ void BuchiAutomaton::print /* ========================================================================= */ void BuchiAutomaton::BuchiTransition::print - (ostream& stream, const int indent, const GraphOutputFormat fmt) const + (ostream& stream, const int indent, const GraphOutputFormat fmt, + const unsigned long int number_of_acceptance_sets) const /* ---------------------------------------------------------------------------- * * Description: Writes information about a transition between two states of * a Büchi automaton. * - * Arguments: stream -- A reference to an output stream. - * indent -- Number of spaces to leave to the left of output. - * fmt -- Determines the format of output. - * + * Arguments: stream -- A reference to an output + * stream. + * indent -- Number of spaces to leave to + * the left of output. + * fmt -- Determines the format of + * output. + * number_of_acceptance_sets -- Number of acceptance sets in + * the automaton to which the + * transition belongs. + * * Returns: Nothing. * * ------------------------------------------------------------------------- */ @@ -977,11 +682,9 @@ void BuchiAutomaton::BuchiTransition::print Exceptional_ostream estream(&stream, ios::failbit | ios::badbit); if (fmt == NORMAL) - { estream << string(indent, ' ') + "Transition to state " << targetNode() - << " [ guard: " << *guard_formula << " ]\n"; - } + << " [ acc.: "; else if (fmt == DOT) { string formula(StringUtil::toString(*guard_formula)); @@ -1002,9 +705,33 @@ void BuchiAutomaton::BuchiTransition::print else estream << formula[i]; } - estream << "\",fontsize=10,fontname=\"Courier-Bold\"]"; + + estream << "\\n"; } + estream << '{'; + bool first_printed = false; + for (unsigned long int accept_set = 0; + accept_set < number_of_acceptance_sets; + ++accept_set) + { + if (acceptance_sets[accept_set]) + { + if (first_printed) + estream << ", "; + else + first_printed = true; + + estream << accept_set; + } + } + estream << '}'; + + if (fmt == NORMAL) + estream << ", guard: " << *guard_formula << " ]\n"; + else if (fmt == DOT) + estream << "\",fontsize=10,fontname=\"Courier-Bold\"]"; + estream.flush(); } @@ -1069,7 +796,8 @@ void BuchiAutomaton::BuchiState::print GraphEdgeContainer::const_iterator edge; for (edge = edges().begin(); edge != edges().end(); ++edge) - (*edge)->print(stream, indent); + static_cast(*edge) + ->print(stream, indent, fmt, number_of_acceptance_sets); } else estream << string(indent, ' ') + "No transitions to other states.\n"; diff --git a/lbtt/src/BuchiAutomaton.h b/lbtt/src/BuchiAutomaton.h index dcd034bd8..cec62163c 100644 --- a/lbtt/src/BuchiAutomaton.h +++ b/lbtt/src/BuchiAutomaton.h @@ -21,6 +21,7 @@ #define BUCHIAUTOMATON_H #include +#include #include #include #include @@ -38,8 +39,7 @@ namespace Graph /****************************************************************************** * - * A class for representing Büchi automata with a single set of accepting - * states. + * A class for representing generalized Büchi automata. * *****************************************************************************/ @@ -139,15 +139,19 @@ public: void connect /* Connects two states */ (const size_type father, /* of the automaton with */ const size_type child); /* an unguarded - * transition. + * transition with no + * associated acceptance + * sets. */ void connect /* Connects two states */ (const size_type father, const size_type child, /* of the automaton with */ - ::Ltl::LtlFormula& guard); /* a transition guarded */ - void connect /* by a propositional */ - (const size_type father, const size_type child, /* formula. */ - ::Ltl::LtlFormula* guard); + ::Ltl::LtlFormula& guard, /* a transition guarded */ + const BitArray& acc_sets); /* by a propositional */ + void connect /* formula. */ + (const size_type father, const size_type child, + ::Ltl::LtlFormula* guard, + const BitArray& acc_sets); /* `disconnect' inherited from Graph */ @@ -167,12 +171,6 @@ public: * automaton. */ - BuchiAutomaton* regularize() const; /* Converts a generalized - * automaton to an - * automaton with one set - * of accepting states. - */ - void read(istream& input_stream); /* Reads the automaton * from a stream. */ @@ -181,17 +179,10 @@ public: (ostream& stream = cout, /* about the automaton */ const int indent = 0, /* to a stream in */ const GraphOutputFormat fmt = NORMAL) const; /* various formats - * (determined by the - * `fmt' argument). + * (determined by the + * `fmt' argument). */ - static BuchiAutomaton* intersect /* Computes the */ - (const BuchiAutomaton& a1, /* intersection of two */ - const BuchiAutomaton& a2, /* Büchi automata. */ - map, ALLOC(StateIdPair) >* - intersection_state_mapping = 0); - /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ class AutomatonParseException; /* Class for reporting @@ -227,14 +218,12 @@ class BuchiAutomaton::BuchiTransition : public Graph::Edge public: BuchiTransition /* Constructor. */ (const size_type target, - ::Ltl::LtlFormula* formula); + ::Ltl::LtlFormula* formula, + const BitArray& acc_sets, + unsigned long int num_acc_sets); ~BuchiTransition(); /* Destructor. */ -private: - -public: - /* `targetNode' inherited from Graph::Edge */ bool enabled /* These functions test */ @@ -249,14 +238,24 @@ public: * propositional formula * guarding the transition. */ + + BitArray& acceptanceSets(); /* Returns the */ + const BitArray& acceptanceSets() const; /* acceptance sets + * associated with the + * the transition. + */ + + void print /* Writes information */ + (ostream& stream, /* about the transition */ + const int indent, /* (as a plain graph */ + const GraphOutputFormat fmt) const; /* edge) to a stream. */ void print /* Writes information */ - (ostream& stream = cout, /* about the transition */ - const int indent = 0, /* to a stream in */ - const GraphOutputFormat fmt = NORMAL) const; /* various formats - * (determined by the - * `fmt' argument). - */ + (ostream& stream, /* about the transition */ + const int indent, /* to a stream in */ + const GraphOutputFormat fmt, /* various formats */ + const unsigned long int /* (determined by the */ + number_of_acceptance_sets) const; /* `fmt' argument). */ private: BuchiTransition(const BuchiTransition&); /* Prevent copying and */ @@ -281,6 +280,11 @@ private: * formula guarding the * transition. */ + + BitArray acceptance_sets; /* Acceptance sets + * associated with the + * transition. + */ }; @@ -372,7 +376,8 @@ inline void BuchiAutomaton::connect * * Description: Connects two states of a BuchiAutomaton to each other with an * unguarded transition (actually, a transition with a guard - * that is always true). + * that is always true) with an empty set of acceptance + * conditions. * * Arguments: father -- Source state identifier. * child -- Target state identifier. @@ -381,50 +386,59 @@ inline void BuchiAutomaton::connect * * ------------------------------------------------------------------------- */ { - connect(father, child, &(::Ltl::True::construct())); + BitArray acc_sets(number_of_acceptance_sets); + acc_sets.clear(number_of_acceptance_sets); + connect(father, child, &(::Ltl::True::construct()), acc_sets); } /* ========================================================================= */ inline void BuchiAutomaton::connect - (const size_type father, const size_type child, ::Ltl::LtlFormula& guard) + (const size_type father, const size_type child, ::Ltl::LtlFormula& guard, + const BitArray& acc_sets) /* ---------------------------------------------------------------------------- * * Description: Connects two states of a BuchiAutomaton to each other, using * a LtlFormula (which is actually a propositional formula) to * guard the transition between the states. * - * Arguments: father -- Source state. - * child -- Target state. - * guard -- A reference to an LtlFormula (a propositional - * formula) guarding the transition. + * Arguments: father -- Source state. + * child -- Target state. + * guard -- A reference to an LtlFormula (a propositional + * formula) guarding the transition. + * acc_sets -- A reference to a BitArray giving the + * acceptance sets associated with the transition. * * Returns: Nothing. * * ------------------------------------------------------------------------- */ { - connect(father, child, guard.clone()); + connect(father, child, guard.clone(), acc_sets); } /* ========================================================================= */ inline void BuchiAutomaton::connect - (const size_type father, const size_type child, ::Ltl::LtlFormula* guard) + (const size_type father, const size_type child, ::Ltl::LtlFormula* guard, + const BitArray& acc_sets) /* ---------------------------------------------------------------------------- * * Description: Connects two states of a BuchiAutomaton to each other, using * a LtlFormula (which is actually a propositional formula) to * guard the transition between the states. * - * Arguments: father -- Source state. - * child -- Target state. - * guard -- A pointer to an LtlFormula (a propositional - * formula) guarding the transition. The transition - * will "own" the guard formula. + * Arguments: father -- Source state. + * child -- Target state. + * guard -- A pointer to an LtlFormula (a propositional + * formula) guarding the transition. The + * transition will "own" the guard formula. + * acc_sets -- A reference to a BitArray giving the acceptance + * sets associated with the transition. * * Returns: Nothing. * * ------------------------------------------------------------------------- */ { - BuchiTransition* new_buchi_transition = new BuchiTransition(child, guard); + BuchiTransition* new_buchi_transition + = new BuchiTransition(child, guard, acc_sets, number_of_acceptance_sets); try { @@ -512,39 +526,28 @@ inline istream& operator>>(istream& stream, BuchiAutomaton& automaton) /* ========================================================================= */ inline BuchiAutomaton::BuchiTransition::BuchiTransition - (const size_type target, ::Ltl::LtlFormula* formula) : - Edge(target), guard_formula(formula) + (const size_type target, ::Ltl::LtlFormula* formula, + const BitArray& acc_sets, unsigned long int num_acc_sets) : + Edge(target), guard_formula(formula), acceptance_sets(num_acc_sets) /* ---------------------------------------------------------------------------- * * Description: Constructor for class BuchiAutomaton::BuchiTransition. * Initializes a new transition to a BuchiState, guarded by an * LtlFormula (which is actually a propositional formula). * - * Arguments: target -- Identifier of the target state of the automaton. - * formula -- A pointer to a propositional formula guarding - * the transition. - * - * Returns: Nothing. - * - * ------------------------------------------------------------------------- */ -{ -} - -/* ========================================================================= */ -inline BuchiAutomaton::BuchiTransition::BuchiTransition - (const BuchiTransition& transition) : - Edge(transition), guard_formula(transition.guard_formula->clone()) -/* ---------------------------------------------------------------------------- - * - * Description: Copy constructor for class BuchiAutomaton::BuchiTransition. - * Creates a copy of a BuchiTransition object. - * - * Arguments: transition -- BuchiTransition to be copied. + * Arguments: target -- Identifier of the target state of the + * automaton. + * formula -- A pointer to a propositional formula guarding + * the transition. + * acc_sets -- A reference to a constant BitArray containing + * the acceptance sets associated with the + * transition. * * Returns: Nothing. * * ------------------------------------------------------------------------- */ { + acceptance_sets.copy(acc_sets, num_acc_sets); } /* ========================================================================= */ @@ -688,6 +691,59 @@ inline ::Ltl::LtlFormula& BuchiAutomaton::BuchiTransition::guard() const return *guard_formula; } +/* ========================================================================= */ +inline BitArray& BuchiAutomaton::BuchiTransition::acceptanceSets() +/* ---------------------------------------------------------------------------- + * + * Description: Returns the acceptance sets associated with a + * BuchiTransition. + * + * Arguments: None. + * + * Returns: A reference to the BitArray storing the acceptance sets + * associated with the transition. + * + * ------------------------------------------------------------------------- */ +{ + return acceptance_sets; +} + +/* ========================================================================= */ +inline const BitArray& BuchiAutomaton::BuchiTransition::acceptanceSets() const +/* ---------------------------------------------------------------------------- + * + * Description: Returns the acceptance sets associated with a + * BuchiTransition. + * + * Arguments: None. + * + * Returns: A constant reference to the BitArray storing the acceptance + * sets associated with the transition. + * + * ------------------------------------------------------------------------- */ +{ + return acceptance_sets; +} + +/* ========================================================================= */ +inline void BuchiAutomaton::BuchiTransition::print + (ostream& stream, const int indent, const GraphOutputFormat fmt) const +/* ---------------------------------------------------------------------------- + * + * Description: Writes information about a transition (as a plain graph edge + * without any associated information) to a stream. + * + * Arguments: stream -- A reference to an output stream. + * indent -- Number of spaces to leave to the left of output. + * fmt -- Determines the output format of the transition. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + Graph::Edge::print(stream, indent, fmt); +} + /****************************************************************************** diff --git a/lbtt/src/BuchiProduct.cc b/lbtt/src/BuchiProduct.cc new file mode 100644 index 000000000..c5f5c51d5 --- /dev/null +++ b/lbtt/src/BuchiProduct.cc @@ -0,0 +1,98 @@ +/* + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 + * Heikki Tauriainen + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "BuchiProduct.h" + +namespace Graph +{ + +/****************************************************************************** + * + * Static member definitions for class BuchiProduct. + * + *****************************************************************************/ + +map< ::Ltl::LtlFormula*, BuchiProduct::SatisfiabilityMapping, + less< ::Ltl::LtlFormula*>, ALLOC(BuchiProduct::SatisfiabilityMapping) > + BuchiProduct::sat_cache; + + + +/****************************************************************************** + * + * Function definitions for class BuchiProduct. + * + *****************************************************************************/ + +/* ========================================================================= */ +bool BuchiProduct::synchronizable + (const Graph::Edge& transition_1, + const Graph::Edge& transition_2) +/* ---------------------------------------------------------------------------- + * + * Description: Tests whether two transitions of two Büchi automata are + * synchronizable by checking whether the conjunction of their + * guard formulas is satisfiable. + * + * Arguments: transition_1, -- Constant references to the transitions. + * transition_2 + * + * Returns: true iff the transitions are synchronizable. The result is + * also stored into `this->sat_cache' for later reference. + * + * ------------------------------------------------------------------------- */ +{ + using ::Ltl::LtlFormula; + using ::Ltl::And; + + LtlFormula* guard_1 = &static_cast + (transition_1).guard(); + LtlFormula* guard_2 = &static_cast + (transition_2).guard(); + + if (guard_2 > guard_1) + { + LtlFormula* swap_guard = guard_2; + guard_2 = guard_1; + guard_1 = swap_guard; + } + + map, + ALLOC(SatisfiabilityMapping) >::iterator + sat_cache_element = sat_cache.find(guard_1); + + if (sat_cache_element == sat_cache.end()) + sat_cache_element = sat_cache.insert + (make_pair(guard_1, SatisfiabilityMapping())).first; + else + { + SatisfiabilityMapping::const_iterator sat_result + = sat_cache_element->second.find(guard_2); + if (sat_result != sat_cache_element->second.end()) + return sat_result->second; + } + + LtlFormula* f = &And::construct(*guard_1, *guard_2); + const bool result = f->satisfiable(); + LtlFormula::destruct(f); + sat_cache_element->second.insert(make_pair(guard_2, result)); + return result; +} + +} diff --git a/lbtt/src/BuchiProduct.h b/lbtt/src/BuchiProduct.h new file mode 100644 index 000000000..43200f521 --- /dev/null +++ b/lbtt/src/BuchiProduct.h @@ -0,0 +1,512 @@ +/* + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 + * Heikki Tauriainen + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef BUCHIPRODUCT_H +#define BUCHIPRODUCT_H + +#include +#include +#include +#include +#include +#include +#include "BitArray.h" +#include "BuchiAutomaton.h" +#include "EdgeContainer.h" +#include "Graph.h" +#include "LtlFormula.h" + +using namespace std; + +namespace Graph +{ + +/****************************************************************************** + * + * A class with operations for checking the intersection of two Büchi automata + * (represented as two BuchiAutomaton objects) for emptiness. + * + *****************************************************************************/ + +class BuchiProduct +{ +public: + BuchiProduct /* Constructor. */ + (const Graph& a1, + const Graph& a2); + + /* default copy constructor */ + + ~BuchiProduct(); /* Destructor. */ + + /* default assignment operator */ + + bool empty() const; /* Tells whether the + * intersection of the + * Büchi automata + * associated with the + * product object is + * (trivially) empty. + */ + + unsigned long int numberOfAcceptanceSets() const; /* Tells the number of + * acceptance sets in the + * intersection of the + * automata associated with + * the object. + */ + + const BuchiAutomaton::BuchiState& firstComponent /* Mappings between an */ + (const Graph::size_type /* intersection state */ + state_id) const; /* identifier and states */ + const BuchiAutomaton::BuchiState& secondComponent /* of the underlying */ + (const Graph::size_type /* automata. */ + state_id) const; + + void mergeAcceptanceInformation /* Merges the acceptance */ + (const Graph::Node& state1, /* sets associated with */ + const Graph::Node& state2, /* a pair of states into */ + BitArray& acceptance_sets) const; /* a collection of sets. */ + + void mergeAcceptanceInformation /* Merges the acceptance */ + (const Graph::Edge& /* sets associated with */ + transition1, /* a pair of */ + const Graph::Edge& /* transitions into a */ + transition2, /* collection of sets. */ + BitArray& acceptance_sets) const; + + void validateEdgeIterators /* Ensures that a pair */ + (const Graph::Node& /* of transition */ + state_1, /* iterators points to a */ + const Graph::Node& /* transition beginning */ + state_2, /* from a given state in */ + GraphEdgeContainer::const_iterator& /* the intersection of */ + transition_1, /* two Büchi automata. */ + GraphEdgeContainer::const_iterator& + transition_2); + + void incrementEdgeIterators /* Updates a pair of */ + (const Graph::Node& /* transition iterators */ + state_1, /* to make them point to */ + const Graph::Node& /* the "next" transition */ + state_2, /* starting from a given */ + GraphEdgeContainer::const_iterator& /* state in the */ + transition_1, /* intersection of two */ + GraphEdgeContainer::const_iterator& /* Büchi automata. */ + transition_2); + + static void clearSatisfiabilityCache(); /* Clears information about + * the satisfiability of + * the guards of product + * transitions. + */ +private: + void mergeAcceptanceInformation /* Bitwise or between */ + (const BitArray& sets1, const BitArray& sets2, /* two "component" */ + BitArray& result) const; /* acceptance set + * vectors and a result + * vector. + */ + + + + bool synchronizable /* Tests whether a pair */ + (const Graph::Edge& /* of transitions of two */ + transition_1, /* Büchi automata is */ + const Graph::Edge& /* synchronizable. */ + transition_2); + + const BuchiAutomaton& automaton_1; /* Automata associated */ + const BuchiAutomaton& automaton_2; /* with the BuchiProduct */ + /* object. */ + + const unsigned long int /* Number of acceptance */ + number_of_acceptance_sets; /* sets in the + * intersection of the + * automata. + */ + + typedef map< ::Ltl::LtlFormula*, bool, /* Type definition for */ + less< ::Ltl::LtlFormula*>, /* storing information */ + ALLOC(bool) > /* about the */ + SatisfiabilityMapping; /* satisfiability of the + * guards of product + * transitions. + */ + + static map< ::Ltl::LtlFormula*, /* Result cache for */ + SatisfiabilityMapping, /* satisfiability tests. */ + less< ::Ltl::LtlFormula*>, + ALLOC(SatisfiabilityMapping) > + sat_cache; +}; + + + +/****************************************************************************** + * + * Inline function definitions for class BuchiProduct. + * + *****************************************************************************/ + +/* ========================================================================= */ +inline BuchiProduct::BuchiProduct + (const Graph& a1, const Graph& a2) : + automaton_1(static_cast(a1)), + automaton_2(static_cast(a2)), + number_of_acceptance_sets(static_cast(a1) + .numberOfAcceptanceSets() + + static_cast(a2) + .numberOfAcceptanceSets()) +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class BuchiProduct. Initializes a new object + * with operations for checking the emptiness of two Büchi + * automata. + * + * Arguments: a1, a2 -- Constant references to two + * Graph objects, assumed to be + * BüchiAutomaton objects to which to apply the + * operations. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +inline BuchiProduct::~BuchiProduct() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class BuchiProduct. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +inline bool BuchiProduct::empty() const +/* ---------------------------------------------------------------------------- + * + * Description: Tells whether the intersection of the Büchi automata + * associated with a BuchiProduct object is (trivially) empty. + * + * Arguments: None. + * + * Returns: true iff either of the automata associated with the + * BuchiProduct object is (trivially) empty (i.e., whether + * either of the automata has no states). + * + * ------------------------------------------------------------------------- */ +{ + return (automaton_1.empty() || automaton_2.empty()); +} + + +/* ========================================================================= */ +inline unsigned long int BuchiProduct::numberOfAcceptanceSets() const +/* ---------------------------------------------------------------------------- + * + * Description: Tells the number of acceptance sets in the intersection of + * the two Büchi automata associated with a BuchiProduct object. + * + * Arguments: None. + * + * Returns: The number of acceptance sets in the intersection. + * + * ------------------------------------------------------------------------- */ +{ + return number_of_acceptance_sets; +} + +/* ========================================================================= */ +inline const BuchiAutomaton::BuchiState& BuchiProduct::firstComponent + (const Graph::size_type state_id) const +/* ---------------------------------------------------------------------------- + * + * Description: Function for accessing states of the "first" component + * automaton in the intersection of two Büchi automata. + * + * Argument: state_id -- Identifier of a state in the component + * automaton. + * + * Returns: A constant reference to a state in the component automaton. + * + * ------------------------------------------------------------------------- */ +{ + return automaton_1[state_id]; +} + +/* ========================================================================= */ +inline const BuchiAutomaton::BuchiState& BuchiProduct::secondComponent + (const Graph::size_type state_id) const +/* ---------------------------------------------------------------------------- + * + * Description: Function for accessing states of the "second" component + * automaton in the intersection of two Büchi automata. + * + * Argument: state_id -- Identifier of a state in the component + * automaton. + * + * Returns: A constant reference to a state in the component automaton. + * + * ------------------------------------------------------------------------- */ +{ + return automaton_2[state_id]; +} + +/* ========================================================================= */ +inline void BuchiProduct::mergeAcceptanceInformation + (const Graph::Node& state1, + const Graph::Node& state2, + BitArray& acceptance_sets) const +/* ---------------------------------------------------------------------------- + * + * Description: Merges the acceptance sets associated with a pair of states + * of two Büchi automata into a collection of sets. + * + * Arguments: state1, state2 -- Constant references to the states of the + * automata. + * acceptance_sets -- A reference to a BitArray for storing + * the result. The BitArray is assumed to + * have capacity for + * `this->number_of_acceptance_sets' bits. + * + * Returns: Nothing. Let n=`this->automaton_1.numberOfAcceptanceSets()'; + * after the operation, `acceptance_sets[i] = true' holds if + * either + * 0 <= i < n and + * `state1.acceptanceSets().test(i) == true' + * or 0 <= i - n < `this->automaton_2.numberOfAcceptanceSets()' + * and `state2.acceptanceSets().test(i - n) == true'. + * + * ------------------------------------------------------------------------- */ +{ + mergeAcceptanceInformation + (static_cast(state1).acceptanceSets(), + static_cast(state2).acceptanceSets(), + acceptance_sets); +} + +/* ========================================================================= */ +inline void BuchiProduct::mergeAcceptanceInformation + (const Graph::Edge& transition1, + const Graph::Edge& transition2, + BitArray& acceptance_sets) const +/* ---------------------------------------------------------------------------- + * + * Description: Merges the acceptance sets associated with a pair of + * transitions of two Büchi automata into a collection of + * acceptance sets. + * + * Arguments: transition1, -- Constant references to the transitions + * transition2 of the automata. + * acceptance_sets -- A reference to a BitArray for storing + * the result. The BitArray is assumed to + * have capacity for + * `this->number_of_acceptance_sets' bits. + * + * Returns: Nothing. Let n=`this->automaton_1.numberOfAcceptanceSets()'; + * after the operation, `acceptance_sets[i] = true' holds if + * either + * 0 <= i < n and + * `transition1.acceptanceSets().test(i) == true' + * or 0 <= i - n < `this->automaton_2.numberOfAcceptanceSets()' + * and `transition2.acceptanceSets().test(i - n) == true'. + * + * ------------------------------------------------------------------------- */ +{ + mergeAcceptanceInformation + (static_cast(transition1) + .acceptanceSets(), + static_cast(transition2) + .acceptanceSets(), + acceptance_sets); +} + +/* ========================================================================= */ +inline void BuchiProduct::mergeAcceptanceInformation + (const BitArray& sets1, const BitArray& sets2, BitArray& result) const +/* ---------------------------------------------------------------------------- + * + * Description: Bitwise or between two acceptance set vectors and a result + * vector. + * + * Arguments: sets1, -- Constant references to two BitArrays having (at + * sets2 least) capacities + * `automaton_1.numberOfAcceptanceSets()' and + * `automaton_2.numberOfAcceptanceSets()', + * respectively. + * result -- A BitArray for storing the result, assumed to + * have room for at least + * `this->number_of_acceptance_sets' bits. + * + * Returns: Nothing. Let n=`this->automaton_1.numberOfAcceptanceSets()'; + * after the operation, `result[i] = true' holds if + * either + * 0 <= i < n and `sets1[i] == true' + * or 0 <= i - n < `this->automaton_2.numberOfAcceptanceSets()' + * and `sets2[i - n] == true'. + * + * ------------------------------------------------------------------------- */ +{ + const unsigned long int shift + = automaton_1.numberOfAcceptanceSets(); + unsigned long int acceptance_set; + for (acceptance_set = 0; acceptance_set < shift; ++acceptance_set) + { + if (sets1[acceptance_set]) + result.setBit(acceptance_set); + } + for ( ; acceptance_set < number_of_acceptance_sets; ++acceptance_set) + { + if (sets2[acceptance_set - shift]) + result.setBit(acceptance_set); + } +} + +/* ========================================================================= */ +inline void BuchiProduct::validateEdgeIterators + (const Graph::Node& state_1, + const Graph::Node& state_2, + GraphEdgeContainer::const_iterator& transition_1, + GraphEdgeContainer::const_iterator& transition_2) +/* ---------------------------------------------------------------------------- + * + * Description: Checks whether a pair of transition iterators corresponds to + * a transition beginning from a state in the intersection of + * two Büchi automata; if this is not the case, increments the + * iterators to make them point to a valid transition beginning + * from the state in the intersection (or to the "end" of the + * collection of transitions beginning from the state if no + * valid transition can be found by incrementing the iterators). + * + * Arguments: state_1, -- These variables determine the state in + * state_2 the intersection automaton; `state_1' and + * `state_2' should both be references to + * BuchiAutomaton::BuchiState objects. + * transition_1, -- References to the transition iterators. + * transition_2 Initially, `transition_1' and + * `transition_2' should point to two + * transitions starting from `state_1' and + * `state_2', respectively. + * + * Returns: Nothing. Upon return, `transition_1' and `transition_2' will + * either equal `state_1.edges().end()' and + * `state_2.edges().end()', respectively, or they will point to + * a pair of transitions beginning from `state_1' and `state_2' + * such that this pair of transitions corresponds to a + * transition starting from the intersection state determined by + * `state_1' and `state_2'. + * + * ------------------------------------------------------------------------- */ +{ + const GraphEdgeContainer& transitions_1 = state_1.edges(); + const GraphEdgeContainer& transitions_2 = state_2.edges(); + + if (transition_1 == transitions_1.end()) + { + transition_2 = transitions_2.end(); + return; + } + if (transition_2 == transitions_2.end()) + { + transition_1 = transitions_1.end(); + return; + } + + if (!synchronizable(**transition_1, **transition_2)) + incrementEdgeIterators(state_1, state_2, transition_1, transition_2); +} + +/* ========================================================================= */ +inline void BuchiProduct::incrementEdgeIterators + (const Graph::Node& state_1, + const Graph::Node& state_2, + GraphEdgeContainer::const_iterator& transition_1, + GraphEdgeContainer::const_iterator& transition_2) +/* ---------------------------------------------------------------------------- + * + * Description: Increments a pair of transition iterators to point to the + * "next" transition beginning from a state in the intersection + * of two Büchi automata. If no "next" transition exists, makes + * the iterators point to the "end" of the collection of + * transitions beginning from the state. + * + * Arguments: state_1, -- These variables determine the state in + * state_2 the intersection automaton; `state_1' and + * `state_2' should both be references to + * BuchiAutomaton::BuchiState objects. + * transition_1, -- References to the transition iterators. + * transition_2 Initially, `transition_1' and + * `transition_2' should point to two + * transitions starting from `state_1' and + * `state_2', respectively. + * + * Returns: Nothing. Upon return, `transition_1' and `transition_2' will + * either equal `state_1.edges().end()' and + * `state_2.edges().end()', respectively, or they will point to + * a pair of transitions beginning from `state_1' and `state_2' + * such that this pair of transitions corresponds to a + * transition starting from the intersection state determined by + * `state_1' and `state_2'. + * + * ------------------------------------------------------------------------- */ +{ + const GraphEdgeContainer& transitions_1 = state_1.edges(); + const GraphEdgeContainer& transitions_2 = state_2.edges(); + + do + { + ++transition_2; + if (transition_2 == transitions_2.end()) + { + ++transition_1; + if (transition_1 == transitions_1.end()) + return; + transition_2 = transitions_2.begin(); + } + } + while (!synchronizable(**transition_1, **transition_2)); +} + +/* ========================================================================= */ +inline void BuchiProduct::clearSatisfiabilityCache() +/* ---------------------------------------------------------------------------- + * + * Description: Clears information about the satisfiability of the guard + * formulas of product transitions. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + sat_cache.clear(); +} + +} + +#endif /* !BUCHIPRODUCT_H */ diff --git a/lbtt/src/Config-lex.ll b/lbtt/src/Config-lex.ll index aa60a08f3..145126a89 100644 --- a/lbtt/src/Config-lex.ll +++ b/lbtt/src/Config-lex.ll @@ -19,11 +19,6 @@ %{ #include -#include -#include -#include -#include -#include #include "Configuration.h" #include "Config-parse.h" @@ -34,225 +29,102 @@ extern int config_file_line_number; %option case-insensitive %option never-interactive %option noyywrap +%option nounput + +%x ATTR EQ VAL + +SQSTR [^\'\n]* +DQSTR ([^\"\\\n]|\\.)* +UQC [^\'\"\\ \t\n] +UQSTR ({UQC}+|\\.)({UQC}*|\\.)* +OKVAL \'{SQSTR}\'|\"{DQSTR}\"|{UQSTR} %% -[ \t]* { /* Skip whitespace. */ } -"#"[^\n]* { /* Skip comments. */ } +<*>[ \t]* { /* Skip whitespace everywhere. */ } +<*>"#".*$ { /* Skip comments everywhere. */ } -"\n" { /* Skip newlines, but update the line number. */ +"\n" { /* Skip newlines, but update the line number. */ config_file_line_number++; } -"{" { return CFG_LBRACE; } -"}" { return CFG_RBRACE; } -"=" { return CFG_EQUALS; } - -algorithm { return CFG_ALGORITHM; } -enabled { return CFG_ENABLED; } -name { return CFG_NAME; } -parameters { return CFG_PARAMETERS; } -path { return CFG_PROGRAMPATH; } +"{" { BEGIN(ATTR); return CFG_LBRACE; } +algorithm|implementation|translator { return CFG_ALGORITHM; } globaloptions { return CFG_GLOBALOPTIONS; } -comparisoncheck { return CFG_COMPARISONTEST; } -comparisontest { return CFG_COMPARISONTEST; } -consistencycheck { return CFG_CONSISTENCYTEST; } -consistencytest { return CFG_CONSISTENCYTEST; } -interactive { return CFG_INTERACTIVE; } -intersectioncheck { return CFG_INTERSECTIONTEST; } -intersectiontest { return CFG_INTERSECTIONTEST; } -modelcheck { return CFG_MODELCHECK; } -rounds { return CFG_ROUNDS; } -verbosity { return CFG_VERBOSITY; } - statespaceoptions { return CFG_STATESPACEOPTIONS; } -edgeprobability { return CFG_EDGEPROBABILITY; } -propositions { return CFG_PROPOSITIONS; } -size { return CFG_SIZE; } -truthprobability { return CFG_TRUTHPROBABILITY; } -changeinterval { return CFG_CHANGEINTERVAL; } -randomseed { return CFG_RANDOMSEED; } - formulaoptions { return CFG_FORMULAOPTIONS; } -abbreviatedoperators { return CFG_ABBREVIATEDOPERATORS; } -andpriority { return CFG_ANDPRIORITY; } -beforepriority { return CFG_BEFOREPRIORITY; } -defaultoperatorpriority { return CFG_DEFAULTOPERATORPRIORITY; } -equivalencepriority { return CFG_EQUIVALENCEPRIORITY; } -falsepriority { return CFG_FALSEPRIORITY; } -finallypriority { return CFG_FINALLYPRIORITY; } -generatemode { return CFG_GENERATEMODE; } -globallypriority { return CFG_GLOBALLYPRIORITY; } -implicationpriority { return CFG_IMPLICATIONPRIORITY; } -nextpriority { return CFG_NEXTPRIORITY; } -notpriority { return CFG_NOTPRIORITY; } -orpriority { return CFG_ORPRIORITY; } -outputmode { return CFG_OUTPUTMODE; } -propositionpriority { return CFG_PROPOSITIONPRIORITY; } -releasepriority { return CFG_RELEASEPRIORITY; } -strongreleasepriority { return CFG_STRONGRELEASEPRIORITY; } -truepriority { return CFG_TRUEPRIORITY; } -untilpriority { return CFG_UNTILPRIORITY; } -weakuntilpriority { return CFG_WEAKUNTILPRIORITY; } -xorpriority { return CFG_XORPRIORITY; } -true|yes { - yylval.truth_value = true; - return CFG_TRUTH_VALUE; +[^ \t\n]+ { return CFG_UNKNOWN; } + +enabled { BEGIN(EQ); return CFG_ENABLED; } +name { BEGIN(EQ); return CFG_NAME; } +parameters { BEGIN(EQ); return CFG_PARAMETERS; } +path { BEGIN(EQ); return CFG_PROGRAMPATH; } + +comparisoncheck { BEGIN(EQ); return CFG_COMPARISONTEST; } +comparisontest { BEGIN(EQ); return CFG_COMPARISONTEST; } +consistencycheck { BEGIN(EQ); return CFG_CONSISTENCYTEST; } +consistencytest { BEGIN(EQ); return CFG_CONSISTENCYTEST; } +interactive { BEGIN(EQ); return CFG_INTERACTIVE; } +intersectioncheck { BEGIN(EQ); return CFG_INTERSECTIONTEST; } +intersectiontest { BEGIN(EQ); return CFG_INTERSECTIONTEST; } +modelcheck { BEGIN(EQ); return CFG_MODELCHECK; } +rounds { BEGIN(EQ); return CFG_ROUNDS; } +translatortimeout { BEGIN(EQ); return CFG_TRANSLATORTIMEOUT; } +verbosity { BEGIN(EQ); return CFG_VERBOSITY; } + +edgeprobability { BEGIN(EQ); return CFG_EDGEPROBABILITY; } +propositions { BEGIN(EQ); return CFG_PROPOSITIONS; } +size { BEGIN(EQ); return CFG_SIZE; } +truthprobability { BEGIN(EQ); return CFG_TRUTHPROBABILITY; } +changeinterval { BEGIN(EQ); return CFG_CHANGEINTERVAL; } +randomseed { BEGIN(EQ); return CFG_RANDOMSEED; } + +abbreviatedoperators { BEGIN(EQ); return CFG_ABBREVIATEDOPERATORS; } +andpriority { BEGIN(EQ); return CFG_ANDPRIORITY; } +beforepriority { BEGIN(EQ); return CFG_BEFOREPRIORITY; } +defaultoperatorpriority { + BEGIN(EQ); return CFG_DEFAULTOPERATORPRIORITY; + } +equivalencepriority { BEGIN(EQ); return CFG_EQUIVALENCEPRIORITY; } +falsepriority { BEGIN(EQ); return CFG_FALSEPRIORITY; } +finallypriority { BEGIN(EQ); return CFG_FINALLYPRIORITY; } +generatemode { BEGIN(EQ); return CFG_GENERATEMODE; } +globallypriority { BEGIN(EQ); return CFG_GLOBALLYPRIORITY; } +implicationpriority { BEGIN(EQ); return CFG_IMPLICATIONPRIORITY; } +nextpriority { BEGIN(EQ); return CFG_NEXTPRIORITY; } +notpriority { BEGIN(EQ); return CFG_NOTPRIORITY; } +orpriority { BEGIN(EQ); return CFG_ORPRIORITY; } +outputmode { BEGIN(EQ); return CFG_OUTPUTMODE; } +propositionpriority { BEGIN(EQ); return CFG_PROPOSITIONPRIORITY; } +releasepriority { BEGIN(EQ); return CFG_RELEASEPRIORITY; } +strongreleasepriority { BEGIN(EQ); return CFG_STRONGRELEASEPRIORITY; } +truepriority { BEGIN(EQ); return CFG_TRUEPRIORITY; } +untilpriority { BEGIN(EQ); return CFG_UNTILPRIORITY; } +weakuntilpriority { BEGIN(EQ); return CFG_WEAKUNTILPRIORITY; } +xorpriority { BEGIN(EQ); return CFG_XORPRIORITY; } + +"}" { BEGIN(INITIAL); return CFG_RBRACE; } + +"="?[^= \t\n]* { return CFG_UNKNOWN; } + +"=" { BEGIN(VAL); return CFG_EQUALS; } + +. { return CFG_UNKNOWN; } + +\\|{OKVAL}+(\\)? { + yylval.value = yytext; + BEGIN(ATTR); + return CFG_VALUE; } -false|no { - yylval.truth_value = false; - return CFG_TRUTH_VALUE; - } - -always { - yylval.interactivity_value = - Configuration::ALWAYS; - return CFG_INTERACTIVITY_VALUE; - } - -never { - yylval.interactivity_value = - Configuration::NEVER; - return CFG_INTERACTIVITY_VALUE; - } - -onerror { - yylval.interactivity_value = - Configuration::ONERROR; - return CFG_INTERACTIVITY_VALUE; - } - -normal { - yylval.formula_mode_value = - Configuration::NORMAL; - return CFG_FORMULA_MODE_VALUE; - } - -nnf { - yylval.formula_mode_value = Configuration::NNF; - return CFG_FORMULA_MODE_VALUE; - } - -local { - yylval.product_type_value = Configuration::LOCAL; - return CFG_PRODUCT_TYPE_VALUE; - } - -global { - yylval.product_type_value = - Configuration::GLOBAL; - return CFG_PRODUCT_TYPE_VALUE; - } - -randomgraph { - yylval.statespace_mode_value - = Configuration::RANDOMGRAPH; - return CFG_STATESPACE_MODE_VALUE; - } - -randomconnectedgraph { - yylval.statespace_mode_value - = Configuration::RANDOMCONNECTEDGRAPH; - return CFG_STATESPACE_MODE_VALUE; - } - -randompath { - yylval.statespace_mode_value - = Configuration::RANDOMPATH; - return CFG_STATESPACE_MODE_VALUE; - } - -enumeratedpath { - yylval.statespace_mode_value - = Configuration::ENUMERATEDPATH; - return CFG_STATESPACE_MODE_VALUE; - } - - -"-"?[0-9]+"...""-"?[0-9]+ { - char* dot_ptr; - yylval.integer_interval.min - = strtol(yytext, &dot_ptr, 10); - - if (yylval.integer_interval.min == LONG_MIN - || yylval.integer_interval.min == LONG_MAX) - throw Configuration::ConfigurationException - (config_file_line_number, - "integer out of range"); - - dot_ptr += 3; - yylval.integer_interval.max - = strtol(dot_ptr, 0, 10); - - if (yylval.integer_interval.max == LONG_MIN - || yylval.integer_interval.max == LONG_MAX) - throw Configuration::ConfigurationException - (config_file_line_number, - "integer out of range"); - - return CFG_INTEGER_INTERVAL; - } - -"-"?[0-9]+ { - yylval.integer = strtol(yytext, 0, 10); - if (yylval.integer == LONG_MIN - || yylval.integer == LONG_MAX) - throw Configuration::ConfigurationException - (config_file_line_number, - "integer out of range"); - return CFG_INTEGER; - } - -"-"?[0-9]*"."[0-9]+ { - yylval.real = strtod(yytext, 0); - - if (yylval.real == HUGE_VAL - || yylval.real == -HUGE_VAL) - throw Configuration::ConfigurationException - (config_file_line_number, - "real number out of range"); - return CFG_REAL; - } - -\"([^\n\"\\]*(\\[^\n])?)*\" { - unsigned long int len = strlen(yytext); - bool escape = false; - yylval.str = new string; - for (unsigned long int i = 1; i < len - 1; i++) - { - if (!escape && yytext[i] == '\\') - escape = true; - else - { - escape = false; - (*yylval.str) += yytext[i]; - } - } - return CFG_STRING_CONSTANT; - } - -. { - return CFG_UNKNOWN; +{OKVAL}*(\'{SQSTR}|\"{DQSTR})(\\)? { + throw Configuration::ConfigurationException + (config_file_line_number, + "unmatched quotes"); } +"\n" { return CFG_UNKNOWN; } %% - -/* ========================================================================= */ -int getCharacter() -/* ---------------------------------------------------------------------------- - * - * Description: Reads the next character from the lexer input stream. - * - * Arguments: None. - * - * Returns: The next character in the lexer input stream or EOF if there - * are no more characters to read. - * - * ------------------------------------------------------------------------- */ -{ - return yyinput(); -} diff --git a/lbtt/src/Config-parse.yy b/lbtt/src/Config-parse.yy index b51a8c942..43e228fcb 100644 --- a/lbtt/src/Config-parse.yy +++ b/lbtt/src/Config-parse.yy @@ -20,11 +20,13 @@ %{ #include -#include #include #include "Configuration.h" #include "StringUtil.h" +using namespace ::StringUtil; + + /****************************************************************************** @@ -33,12 +35,12 @@ * *****************************************************************************/ -static Configuration::AlgorithmInformation /* Stores all the */ - algorithm_information; /* information in a - * single `Algorithm' - * block in the - * configuration file. - */ +static string algorithm_name, algorithm_path, /* Implementation */ + algorithm_parameters; /* attributes read from */ +static bool algorithm_enabled; /* an `Algorithm' block + * in the configuration + * file. + */ static int algorithm_begin_line; /* Input file line number * denoting the beginning @@ -53,14 +55,6 @@ static int expected_token; /* Type of a token to be * configuration file. */ -static int current_block_type; /* Type of the current - * configuration block. - */ - -static int current_option_type; /* Type of the current - * option name. - */ - static Configuration* parser_cfg; /* Pointer to a * Configuration data * structure in which @@ -100,11 +94,6 @@ extern int yylex(); /* Reads the next token * the lexer). */ -extern int getCharacter(); /* Returns the next - * character in the lexer - * input stream. - */ - /****************************************************************************** @@ -114,7 +103,7 @@ extern int getCharacter(); /* Returns the next *****************************************************************************/ /* ========================================================================= */ -void yyerror(char* error_message) +void yyerror(const char* error_message) /* ---------------------------------------------------------------------------- * * Description: Function for reporting parse errors. @@ -127,26 +116,19 @@ void yyerror(char* error_message) * * ------------------------------------------------------------------------- */ { - string unknown_token(yytext); - int c; - - do - { - c = getCharacter(); - if (c != EOF && c != ' ' && c != '\t' && c != '\n') - unknown_token += static_cast(c); - } - while (c != EOF && c != ' ' && c != '\t' && c != '\n'); - + const string unknown_token(yytext); string msg; switch (expected_token) { case CFG_BLOCK_ID : - msg = string("unrecognized block identifier (`") + unknown_token + "')"; + msg = "`" + unknown_token + "' is not a valid block identifier"; break; case CFG_OPTION_ID : - msg = string("unrecognized option identifier (`") + unknown_token + "')"; + if (!unknown_token.empty()) + msg = "`" + unknown_token + "' is not a valid option identifier"; + else + msg = "'}' expected at the end of block"; break; case CFG_LBRACE : msg = "`{' expected after block identifier"; @@ -154,33 +136,8 @@ void yyerror(char* error_message) case CFG_EQUALS : msg = "`=' expected after option identifier"; break; - case CFG_TRUTH_VALUE : - msg = "truth value expected as option argument"; - break; - case CFG_INTERACTIVITY_VALUE : - msg = "interactivity mode expected as option argument"; - break; - case CFG_FORMULA_MODE_VALUE : - msg = "formula generation mode expected as option argument"; - break; - case CFG_STATESPACE_MODE_VALUE : - msg = "state space generation mode expected as option argument"; - break; - case CFG_PRODUCT_TYPE_VALUE : - msg = "model checking mode expected as option argument"; - break; - case CFG_INTEGER : - msg = "nonnegative integer expected as option argument"; - break; - case CFG_INTEGER_INTERVAL : - msg = "nonnegative integer or an integer interval expected as option" - " argument"; - break; - case CFG_REAL : - msg = "nonnegative real number expected as option argument"; - break; - case CFG_STRING_CONSTANT : - msg = "string constant expected as option argument"; + case CFG_VALUE : + msg = "value for option expected"; break; default : msg = error_message; @@ -190,92 +147,6 @@ void yyerror(char* error_message) throw Configuration::ConfigurationException(config_file_line_number, msg); } - - -/****************************************************************************** - * - * Functions for performing various bound checks for the values of different - * options in the configuration file. - * - *****************************************************************************/ - -/* ========================================================================= */ -void checkIntegerRange - (long int value, const struct Configuration::IntegerRange& range, - bool show_line_number_if_error = true) -/* ---------------------------------------------------------------------------- - * - * Description: Checks that a value given to a configuration is within the - * acceptable range. - * - * Arguments: value -- Integer value for a - * configuration option. - * range -- A reference to a constant - * struct - * Configuration::IntegerRange. - * show_line_number_if_error -- If the value is not within the - * specified range, this - * parameter determines whether a - * configuration file line number - * is shown together with the - * error message. - * - * Returns: Nothing. - * - * ------------------------------------------------------------------------- */ -{ - if (value < range.min || value > range.max) - throw Configuration::ConfigurationException - ((show_line_number_if_error ? config_file_line_number : -1), - range.error_message); -} - -/* ========================================================================= */ -void checkProbability(double value, bool show_line_number_if_error = true) -/* ---------------------------------------------------------------------------- - * - * Description: Checks whether a probability value specified in the program - * configuration is between 0.0 and 1.0 inclusive. - * - * Argument: value -- A value supposed to denote a - * probability. - * show_line_number_if_error -- If the value is not within the - * specified range, this - * parameter determines whether a - * configuration file line number - * is shown together with the - * error message. - * - * Returns: Nothing. - * - * ------------------------------------------------------------------------- */ -{ - if (value < 0.0 || value > 1.0) - throw Configuration::ConfigurationException - ((show_line_number_if_error ? config_file_line_number : -1), - "probability must be between 0.0 and 1.0 inclusive"); -} - -/* ========================================================================= */ -static inline bool isLocked(int option) -/* ---------------------------------------------------------------------------- - * - * Description: Checks whether the value of a configuration option can be - * initialized from the configuration file. (This should not be - * done if the option was present on the program command line.) - * - * Argument: option -- The command line option. - * - * Returns: `true' if the value of the option has been overridden in the - * command line. - * - * ------------------------------------------------------------------------- */ -{ - return (parser_cfg->locked_options.find(make_pair(current_block_type, - option)) - != parser_cfg->locked_options.end()); -} - %} @@ -286,39 +157,19 @@ static inline bool isLocked(int option) * *****************************************************************************/ -/* Data types for configuration file option values. */ +/* Data type for configuration file option values. */ %union { - bool truth_value; - - long int integer; - - Configuration::InteractionMode interactivity_value; - - Configuration::FormulaMode formula_mode_value; - - Configuration::StateSpaceMode statespace_mode_value; - - Configuration::ProductMode product_type_value; - - double real; - - string *str; - - struct - { - long int min; - long int max; - } integer_interval; + const char* value; } /* Keywords. */ %token CFG_ALGORITHM CFG_ENABLED CFG_NAME CFG_PARAMETERS CFG_PROGRAMPATH -%token CFG_GLOBALOPTIONS CFG_COMPARISONTEST CFG_CONSISTENCYTEST - CFG_INTERACTIVE CFG_INTERSECTIONTEST CFG_MODELCHECK CFG_ROUNDS +%token CFG_GLOBALOPTIONS CFG_COMPARISONTEST CFG_CONSISTENCYTEST CFG_INTERACTIVE + CFG_INTERSECTIONTEST CFG_MODELCHECK CFG_ROUNDS CFG_TRANSLATORTIMEOUT CFG_VERBOSITY %token CFG_STATESPACEOPTIONS CFG_EDGEPROBABILITY CFG_PROPOSITIONS CFG_SIZE @@ -332,44 +183,19 @@ static inline bool isLocked(int option) CFG_RELEASEPRIORITY CFG_STRONGRELEASEPRIORITY CFG_TRUEPRIORITY CFG_UNTILPRIORITY CFG_WEAKUNTILPRIORITY CFG_XORPRIORITY -/* Boolean constants. */ - -%token CFG_TRUTH_VALUE - -/* Interactivity mode constants. */ - -%token CFG_INTERACTIVITY_VALUE - -/* Formula output/generation mode constants. */ - -%token CFG_FORMULA_MODE_VALUE - -/* Statespace generation mode constants. */ - -%token CFG_STATESPACE_MODE_VALUE - -/* Constants controlling the product space computation. */ - -%token CFG_PRODUCT_TYPE_VALUE - -/* Numbers. */ - -%token CFG_INTEGER -%token CFG_REAL - -/* Intervals of integers. */ - -%token CFG_INTEGER_INTERVAL - -/* String constants. */ - -%token CFG_STRING_CONSTANT - /* Punctuation symbols. */ %token CFG_LBRACE CFG_RBRACE CFG_EQUALS + +/* Block and option identifiers. */ + %token CFG_BLOCK_ID CFG_OPTION_ID +/* Uninterpreted option values. */ + +%token CFG_VALUE +%type equals_value + /* The `unknown' token. */ %token CFG_UNKNOWN @@ -387,11 +213,16 @@ static inline bool isLocked(int option) configuration_file: configuration_blocks ; +equals_value: { expected_token = CFG_EQUALS; } + CFG_EQUALS + { expected_token = CFG_VALUE; } + CFG_VALUE + { $$ = $4; } + ; + configuration_blocks: /* empty */ | configuration_blocks - { - expected_token = CFG_BLOCK_ID; - } + { expected_token = CFG_BLOCK_ID; } configuration_block ; @@ -403,947 +234,398 @@ configuration_block: algorithm_option_block algorithm_option_block: CFG_ALGORITHM { - current_block_type = CFG_ALGORITHM; - + algorithm_name = ""; + algorithm_path = ""; + algorithm_parameters = ""; + algorithm_enabled = true; algorithm_begin_line = config_file_line_number; - - algorithm_information.name = 0; - algorithm_information.path_to_program = 0; - algorithm_information.extra_parameters = 0; - algorithm_information.enabled = true; - expected_token = CFG_LBRACE; } CFG_LBRACE algorithm_options CFG_RBRACE { - if (algorithm_information.name == 0) - { - algorithm_information.name = - new string("Algorithm "); - algorithm_information.name->append - (::StringUtil::toString - (parser_cfg->algorithms.size())); - } - parser_cfg->algorithms.push_back - (algorithm_information); - if (algorithm_information.path_to_program == 0) - { - throw Configuration::ConfigurationException - (::StringUtil::toString - (algorithm_begin_line) - + "--" - + ::StringUtil::toString - (config_file_line_number), - "missing path to executable (`" - + *(algorithm_information.name) - + "')"); - } + parser_cfg->registerAlgorithm + (algorithm_name, algorithm_path, + algorithm_parameters, algorithm_enabled, + algorithm_begin_line); } ; algorithm_options: /* empty */ | algorithm_options - { - expected_token = CFG_OPTION_ID; - } + { expected_token = CFG_OPTION_ID; } algorithm_option ; -algorithm_option: CFG_ENABLED +algorithm_option: CFG_ENABLED equals_value { - current_option_type = CFG_ENABLED; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_TRUTH_VALUE; - } - CFG_TRUTH_VALUE - { - algorithm_information.enabled = $5; - } - - | CFG_NAME - { - current_option_type = CFG_NAME; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_STRING_CONSTANT; - } - CFG_STRING_CONSTANT - { - algorithm_information.name = $5; + parser_cfg->readTruthValue + (algorithm_enabled, + $2); } - | CFG_PARAMETERS + | CFG_NAME equals_value + { algorithm_name = unquoteString($2); } + + | CFG_PARAMETERS equals_value { - current_option_type = CFG_PARAMETERS; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_STRING_CONSTANT; - } - CFG_STRING_CONSTANT - { - algorithm_information.extra_parameters = $5; + algorithm_parameters = unquoteString($2); } - | CFG_PROGRAMPATH - { - current_option_type = CFG_PROGRAMPATH; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_STRING_CONSTANT; - } - CFG_STRING_CONSTANT - { - algorithm_information.path_to_program = $5; - } + | CFG_PROGRAMPATH equals_value + { algorithm_path = unquoteString($2); } ; global_option_block: CFG_GLOBALOPTIONS - { - current_block_type = CFG_GLOBALOPTIONS; - expected_token = CFG_LBRACE; - } + { expected_token = CFG_LBRACE; } CFG_LBRACE global_options CFG_RBRACE ; global_options: /* empty */ | global_options - { - expected_token = CFG_OPTION_ID; - } + { expected_token = CFG_OPTION_ID; } global_option ; -global_option: CFG_COMPARISONTEST +global_option: CFG_COMPARISONTEST equals_value { - current_option_type = CFG_COMPARISONTEST; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_TRUTH_VALUE; - } - CFG_TRUTH_VALUE - { - if (!isLocked(CFG_COMPARISONTEST)) - parser_cfg->global_options.do_comp_test = $5; - } - - | CFG_CONSISTENCYTEST - { - current_option_type = CFG_CONSISTENCYTEST; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_TRUTH_VALUE; - } - CFG_TRUTH_VALUE - { - if (!isLocked(CFG_CONSISTENCYTEST)) - parser_cfg->global_options.do_cons_test = $5; - } - - | CFG_INTERACTIVE - { - current_option_type = CFG_INTERACTIVE; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTERACTIVITY_VALUE; - } - CFG_INTERACTIVITY_VALUE - { - if (!isLocked(CFG_INTERACTIVE)) - parser_cfg->global_options.interactive = $5; + parser_cfg->readTruthValue + (parser_cfg->global_options. + do_comp_test, + $2); } - | CFG_INTERSECTIONTEST + | CFG_CONSISTENCYTEST equals_value { - current_option_type = CFG_INTERSECTIONTEST; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_TRUTH_VALUE; - } - CFG_TRUTH_VALUE - { - if (!isLocked(CFG_INTERSECTIONTEST)) - parser_cfg->global_options.do_intr_test = $5; + parser_cfg->readTruthValue + (parser_cfg->global_options. + do_cons_test, + $2); } - | CFG_MODELCHECK - { - current_option_type = CFG_MODELCHECK; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_PRODUCT_TYPE_VALUE; - } - CFG_PRODUCT_TYPE_VALUE - { - if (!isLocked(CFG_MODELCHECK)) - parser_cfg->global_options.product_mode = $5; - } + | CFG_INTERACTIVE equals_value + { parser_cfg->readInteractivity($2); } - | CFG_ROUNDS + | CFG_INTERSECTIONTEST equals_value { - current_option_type = CFG_ROUNDS; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_ROUNDS)) - { - checkIntegerRange - ($5, Configuration::ROUND_COUNT_RANGE, - true); - parser_cfg->global_options.number_of_rounds - = $5; - } + parser_cfg->readTruthValue + (parser_cfg->global_options. + do_intr_test, + $2); } - | CFG_VERBOSITY + | CFG_MODELCHECK equals_value + { parser_cfg->readProductType($2); } + + | CFG_ROUNDS equals_value { - current_option_type = CFG_VERBOSITY; - expected_token = CFG_EQUALS; + parser_cfg->readInteger + (parser_cfg->global_options.number_of_rounds, + $2, + Configuration::ROUND_COUNT_RANGE); } - CFG_EQUALS + + | CFG_TRANSLATORTIMEOUT equals_value + { parser_cfg->readTranslatorTimeout($2); } + + | CFG_VERBOSITY equals_value { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_VERBOSITY)) - { - checkIntegerRange - ($5, Configuration::VERBOSITY_RANGE, true); - parser_cfg->global_options.verbosity = $5; - } + parser_cfg->readInteger + (parser_cfg->global_options.verbosity, + $2, + Configuration::VERBOSITY_RANGE); } ; statespace_option_block: CFG_STATESPACEOPTIONS - { - current_block_type = CFG_STATESPACEOPTIONS; - expected_token = CFG_LBRACE; - } + { expected_token = CFG_LBRACE; } CFG_LBRACE statespace_options CFG_RBRACE ; statespace_options: /* empty */ | statespace_options - { - expected_token = CFG_OPTION_ID; - } + { expected_token = CFG_OPTION_ID; } statespace_option ; -statespace_option: CFG_CHANGEINTERVAL +statespace_option: CFG_CHANGEINTERVAL equals_value { - current_option_type = CFG_CHANGEINTERVAL; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_CHANGEINTERVAL)) - { - checkIntegerRange - ($5, Configuration::GENERATION_RANGE, true); - parser_cfg->global_options. - statespace_change_interval = $5; - } + parser_cfg->readInteger + (parser_cfg->global_options. + statespace_change_interval, + $2); } - | CFG_EDGEPROBABILITY + | CFG_EDGEPROBABILITY equals_value { - current_option_type = CFG_EDGEPROBABILITY; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_REAL; - } - CFG_REAL - { - if (!isLocked(CFG_EDGEPROBABILITY)) - { - checkProbability($5); - parser_cfg->statespace_generator. - edge_probability = $5; - } + parser_cfg->readProbability + (parser_cfg->statespace_generator. + edge_probability, + $2); } - | CFG_GENERATEMODE - { - current_option_type = CFG_GENERATEMODE; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_STATESPACE_MODE_VALUE; - } - CFG_STATESPACE_MODE_VALUE - { - if (!isLocked(CFG_GENERATEMODE)) - parser_cfg->global_options. - statespace_generation_mode = $5; - } + | CFG_GENERATEMODE equals_value + { parser_cfg->readStateSpaceMode($2); } - | CFG_PROPOSITIONS + | CFG_PROPOSITIONS equals_value { - current_option_type = CFG_PROPOSITIONS; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_PROPOSITIONS)) - { - checkIntegerRange - ($5, Configuration::PROPOSITION_COUNT_RANGE, - true); - parser_cfg->statespace_generator. - atoms_per_state = $5; - } + parser_cfg->readInteger + (parser_cfg->statespace_generator. + atoms_per_state, + $2); } - | CFG_RANDOMSEED + | CFG_RANDOMSEED equals_value { - current_option_type = CFG_RANDOMSEED; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_RANDOMSEED)) - parser_cfg->global_options. - statespace_random_seed = $5; + parser_cfg->readInteger + (parser_cfg->global_options. + statespace_random_seed, + $2, + Configuration::RANDOM_SEED_RANGE); } - | CFG_SIZE + | CFG_SIZE equals_value { - current_option_type = CFG_SIZE; - expected_token = CFG_EQUALS; + parser_cfg->readSize + (Configuration::OPT_STATESPACESIZE, + $2); } - CFG_EQUALS - { - expected_token = CFG_INTEGER_INTERVAL; - } - statespace_size - | CFG_TRUTHPROBABILITY + | CFG_TRUTHPROBABILITY equals_value { - current_option_type = CFG_TRUTHPROBABILITY; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_REAL; - } - CFG_REAL - { - if (!isLocked(CFG_TRUTHPROBABILITY)) - { - checkProbability($5); - parser_cfg->statespace_generator. - truth_probability = $5; - } + parser_cfg->readProbability + (parser_cfg->statespace_generator. + truth_probability, + $2); } ; formula_option_block: CFG_FORMULAOPTIONS - { - current_block_type = CFG_FORMULAOPTIONS; - expected_token = CFG_LBRACE; - } + { expected_token = CFG_LBRACE; } CFG_LBRACE formula_options CFG_RBRACE ; formula_options: /* empty */ | formula_options - { - expected_token = CFG_OPTION_ID; - } + { expected_token = CFG_OPTION_ID; } formula_option ; -formula_option: CFG_ABBREVIATEDOPERATORS +formula_option: CFG_ABBREVIATEDOPERATORS equals_value { - current_option_type = CFG_ABBREVIATEDOPERATORS; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_TRUTH_VALUE; - } - CFG_TRUTH_VALUE - { - if (!isLocked(CFG_ABBREVIATEDOPERATORS)) - parser_cfg->formula_options. - allow_abbreviated_operators = $5; + parser_cfg->readTruthValue + (parser_cfg->formula_options. + allow_abbreviated_operators, + $2); } - | CFG_ANDPRIORITY - { - current_option_type = CFG_ANDPRIORITY; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_ANDPRIORITY)) - { - checkIntegerRange - ($5, Configuration::PRIORITY_RANGE, true); - parser_cfg->formula_options.symbol_priority - [::Ltl::LTL_CONJUNCTION] = $5; - } + | CFG_ANDPRIORITY equals_value + { + parser_cfg->readInteger + (parser_cfg->formula_options.symbol_priority + [::Ltl::LTL_CONJUNCTION], + $2, + Configuration::OPERATOR_PRIORITY_RANGE); } - | CFG_BEFOREPRIORITY - { - current_option_type = CFG_BEFOREPRIORITY; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_BEFOREPRIORITY)) - { - checkIntegerRange - ($5, Configuration::PRIORITY_RANGE, true); - parser_cfg->formula_options.symbol_priority - [::Ltl::LTL_BEFORE] = $5; - } + | CFG_BEFOREPRIORITY equals_value + { + parser_cfg->readInteger + (parser_cfg->formula_options.symbol_priority + [::Ltl::LTL_BEFORE], + $2, + Configuration::OPERATOR_PRIORITY_RANGE); } - | CFG_CHANGEINTERVAL + | CFG_CHANGEINTERVAL equals_value { - current_option_type = CFG_CHANGEINTERVAL; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_CHANGEINTERVAL)) - { - checkIntegerRange - ($5, Configuration::GENERATION_RANGE, true); - parser_cfg->global_options. - formula_change_interval = $5; - } + parser_cfg->readInteger + (parser_cfg->global_options. + formula_change_interval, + $2); } - | CFG_DEFAULTOPERATORPRIORITY - { - current_option_type - = CFG_DEFAULTOPERATORPRIORITY; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_DEFAULTOPERATORPRIORITY)) - { - checkIntegerRange - ($5, Configuration::PRIORITY_RANGE, true); - parser_cfg->formula_options. - default_operator_priority = $5; - } + | CFG_DEFAULTOPERATORPRIORITY equals_value + { + parser_cfg->readInteger + (parser_cfg->formula_options. + default_operator_priority, + $2, + Configuration::OPERATOR_PRIORITY_RANGE); } - | CFG_EQUIVALENCEPRIORITY - { - current_option_type = CFG_EQUIVALENCEPRIORITY; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_EQUIVALENCEPRIORITY)) - { - checkIntegerRange - ($5, Configuration::PRIORITY_RANGE, true); - parser_cfg->formula_options.symbol_priority - [::Ltl::LTL_EQUIVALENCE] = $5; - } + | CFG_EQUIVALENCEPRIORITY equals_value + { + parser_cfg->readInteger + (parser_cfg->formula_options.symbol_priority + [::Ltl::LTL_EQUIVALENCE], + $2, + Configuration::OPERATOR_PRIORITY_RANGE); } - | CFG_FALSEPRIORITY - { - current_option_type = CFG_FALSEPRIORITY; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_FALSEPRIORITY)) - { - checkIntegerRange - ($5, Configuration::PRIORITY_RANGE, true); - parser_cfg->formula_options.symbol_priority - [::Ltl::LTL_FALSE] = $5; - } + | CFG_FALSEPRIORITY equals_value + { + parser_cfg->readInteger + (parser_cfg->formula_options.symbol_priority + [::Ltl::LTL_FALSE], + $2, + Configuration::ATOMIC_PRIORITY_RANGE); } - | CFG_FINALLYPRIORITY - { - current_option_type = CFG_FINALLYPRIORITY; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_FINALLYPRIORITY)) - { - checkIntegerRange - ($5, Configuration::PRIORITY_RANGE, true); - parser_cfg->formula_options.symbol_priority - [::Ltl::LTL_FINALLY] = $5; - } + | CFG_FINALLYPRIORITY equals_value + { + parser_cfg->readInteger + (parser_cfg->formula_options.symbol_priority + [::Ltl::LTL_FINALLY], + $2, + Configuration::OPERATOR_PRIORITY_RANGE); } - | CFG_GENERATEMODE + | CFG_GENERATEMODE equals_value { - current_option_type = CFG_GENERATEMODE; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_FORMULA_MODE_VALUE; - } - CFG_FORMULA_MODE_VALUE - { - if (!isLocked(CFG_GENERATEMODE)) - parser_cfg->formula_options.generate_mode - = $5; + parser_cfg->readFormulaMode + (parser_cfg->formula_options.generate_mode, + $2); } - | CFG_GLOBALLYPRIORITY - { - current_option_type = CFG_GLOBALLYPRIORITY; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_GLOBALLYPRIORITY)) - { - checkIntegerRange - ($5, Configuration::PRIORITY_RANGE, true); - parser_cfg->formula_options.symbol_priority - [::Ltl::LTL_GLOBALLY] = $5; - } + | CFG_GLOBALLYPRIORITY equals_value + { + parser_cfg->readInteger + (parser_cfg->formula_options.symbol_priority + [::Ltl::LTL_GLOBALLY], + $2, + Configuration::OPERATOR_PRIORITY_RANGE); } - | CFG_IMPLICATIONPRIORITY - { - current_option_type = CFG_IMPLICATIONPRIORITY; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_IMPLICATIONPRIORITY)) - { - checkIntegerRange - ($5, Configuration::PRIORITY_RANGE, true); - parser_cfg->formula_options.symbol_priority - [::Ltl::LTL_IMPLICATION] = $5; - } + | CFG_IMPLICATIONPRIORITY equals_value + { + parser_cfg->readInteger + (parser_cfg->formula_options.symbol_priority + [::Ltl::LTL_IMPLICATION], + $2, + Configuration::OPERATOR_PRIORITY_RANGE); } - | CFG_NEXTPRIORITY - { - current_option_type = CFG_NEXTPRIORITY; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_NEXTPRIORITY)) - { - checkIntegerRange - ($5, Configuration::PRIORITY_RANGE, true); - parser_cfg->formula_options.symbol_priority - [::Ltl::LTL_NEXT] = $5; - } + | CFG_NEXTPRIORITY equals_value + { + parser_cfg->readInteger + (parser_cfg->formula_options.symbol_priority + [::Ltl::LTL_NEXT], + $2, + Configuration::OPERATOR_PRIORITY_RANGE); } - | CFG_NOTPRIORITY - { - current_option_type = CFG_NOTPRIORITY; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_NOTPRIORITY)) - { - checkIntegerRange - ($5, Configuration::PRIORITY_RANGE, true); - parser_cfg->formula_options.symbol_priority - [::Ltl::LTL_NEGATION] = $5; - } + | CFG_NOTPRIORITY equals_value + { + parser_cfg->readInteger + (parser_cfg->formula_options.symbol_priority + [::Ltl::LTL_NEGATION], + $2, + Configuration::OPERATOR_PRIORITY_RANGE); } - | CFG_ORPRIORITY - { - current_option_type = CFG_ORPRIORITY; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_ORPRIORITY)) - { - checkIntegerRange - ($5, Configuration::PRIORITY_RANGE, true); - parser_cfg->formula_options.symbol_priority - [::Ltl::LTL_DISJUNCTION] = $5; - } + | CFG_ORPRIORITY equals_value + { + parser_cfg->readInteger + (parser_cfg->formula_options.symbol_priority + [::Ltl::LTL_DISJUNCTION], + $2, + Configuration::OPERATOR_PRIORITY_RANGE); } - | CFG_OUTPUTMODE + | CFG_OUTPUTMODE equals_value { - current_option_type = CFG_OUTPUTMODE; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_FORMULA_MODE_VALUE; - } - CFG_FORMULA_MODE_VALUE - { - if (!isLocked(CFG_OUTPUTMODE)) - parser_cfg->formula_options.output_mode = $5; + parser_cfg->readFormulaMode + (parser_cfg->formula_options.output_mode, + $2); } - | CFG_PROPOSITIONPRIORITY - { - current_option_type = CFG_PROPOSITIONPRIORITY; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_PROPOSITIONPRIORITY)) - { - checkIntegerRange - ($5, Configuration::PRIORITY_RANGE, true); - parser_cfg->formula_options.symbol_priority - [::Ltl::LTL_ATOM] = $5; - } + | CFG_PROPOSITIONPRIORITY equals_value + { + parser_cfg->readInteger + (parser_cfg->formula_options.symbol_priority + [::Ltl::LTL_ATOM], + $2, + Configuration::ATOMIC_PRIORITY_RANGE); } - | CFG_PROPOSITIONS + | CFG_PROPOSITIONS equals_value { - current_option_type = CFG_PROPOSITIONS; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_PROPOSITIONS)) - { - checkIntegerRange - ($5, Configuration::PROPOSITION_COUNT_RANGE, - true); - parser_cfg->formula_options. - formula_generator. - number_of_available_variables = $5; - } + parser_cfg->readInteger + (parser_cfg->formula_options. + formula_generator. + number_of_available_variables, + $2); } - | CFG_RANDOMSEED + | CFG_RANDOMSEED equals_value { - current_option_type = CFG_RANDOMSEED; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_RANDOMSEED)) - parser_cfg->global_options.formula_random_seed - = $5; + parser_cfg->readInteger + (parser_cfg->global_options. + formula_random_seed, + $2, + Configuration::RANDOM_SEED_RANGE); } - | CFG_RELEASEPRIORITY - { - current_option_type = CFG_RELEASEPRIORITY; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_RELEASEPRIORITY)) - { - checkIntegerRange - ($5, Configuration::PRIORITY_RANGE, true); - parser_cfg->formula_options.symbol_priority - [::Ltl::LTL_V] = $5; - } + | CFG_RELEASEPRIORITY equals_value + { + parser_cfg->readInteger + (parser_cfg->formula_options.symbol_priority + [::Ltl::LTL_V], + $2, + Configuration::OPERATOR_PRIORITY_RANGE); } - | CFG_SIZE + | CFG_SIZE equals_value { - current_option_type = CFG_SIZE; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER_INTERVAL; - } - formula_size - - | CFG_STRONGRELEASEPRIORITY - { - current_option_type = CFG_STRONGRELEASEPRIORITY; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_STRONGRELEASEPRIORITY)) - { - checkIntegerRange - ($5, Configuration::PRIORITY_RANGE, true); - parser_cfg->formula_options.symbol_priority - [::Ltl::LTL_STRONG_RELEASE] = $5; - } + parser_cfg->readSize + (Configuration::OPT_FORMULASIZE, + $2); } - | CFG_TRUEPRIORITY - { - current_option_type = CFG_TRUEPRIORITY; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_TRUEPRIORITY)) - { - checkIntegerRange - ($5, Configuration::PRIORITY_RANGE, true); - parser_cfg->formula_options.symbol_priority - [::Ltl::LTL_TRUE] = $5; - } + | CFG_STRONGRELEASEPRIORITY equals_value + { + parser_cfg->readInteger + (parser_cfg->formula_options.symbol_priority + [::Ltl::LTL_STRONG_RELEASE], + $2, + Configuration::OPERATOR_PRIORITY_RANGE); } - | CFG_UNTILPRIORITY - { - current_option_type = CFG_UNTILPRIORITY; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_UNTILPRIORITY)) - { - checkIntegerRange - ($5, Configuration::PRIORITY_RANGE, true); - parser_cfg->formula_options.symbol_priority - [::Ltl::LTL_UNTIL] = $5; - } + | CFG_TRUEPRIORITY equals_value + { + parser_cfg->readInteger + (parser_cfg->formula_options.symbol_priority + [::Ltl::LTL_TRUE], + $2, + Configuration::ATOMIC_PRIORITY_RANGE); } - | CFG_WEAKUNTILPRIORITY - { - current_option_type = CFG_WEAKUNTILPRIORITY; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_WEAKUNTILPRIORITY)) - { - checkIntegerRange - ($5, Configuration::PRIORITY_RANGE, true); - parser_cfg->formula_options.symbol_priority - [::Ltl::LTL_WEAK_UNTIL] = $5; - } + | CFG_UNTILPRIORITY equals_value + { + parser_cfg->readInteger + (parser_cfg->formula_options.symbol_priority + [::Ltl::LTL_UNTIL], + $2, + Configuration::OPERATOR_PRIORITY_RANGE); } - | CFG_XORPRIORITY - { - current_option_type = CFG_XORPRIORITY; - expected_token = CFG_EQUALS; - } - CFG_EQUALS - { - expected_token = CFG_INTEGER; - } - CFG_INTEGER - { - if (!isLocked(CFG_XORPRIORITY)) - { - checkIntegerRange - ($5, Configuration::PRIORITY_RANGE, true); - parser_cfg->formula_options.symbol_priority - [::Ltl::LTL_XOR] = $5; - } - } - ; - -formula_size: CFG_INTEGER - { - if (!isLocked(CFG_SIZE)) - { - checkIntegerRange - ($1, Configuration::FORMULA_SIZE_RANGE, - true); - parser_cfg->formula_options.formula_generator. - size = $1; - parser_cfg->formula_options.formula_generator. - max_size = $1; - } + | CFG_WEAKUNTILPRIORITY equals_value + { + parser_cfg->readInteger + (parser_cfg->formula_options.symbol_priority + [::Ltl::LTL_WEAK_UNTIL], + $2, + Configuration::OPERATOR_PRIORITY_RANGE); } - | CFG_INTEGER_INTERVAL - { - if (!isLocked(CFG_SIZE)) - { - checkIntegerRange - ($1.min, Configuration::FORMULA_SIZE_RANGE, - true); - - Configuration::IntegerRange max_len_range - (Configuration::FORMULA_MAX_SIZE_RANGE); - - max_len_range.min = $1.min; - - checkIntegerRange($1.max, max_len_range, - true); - - parser_cfg->formula_options.formula_generator. - size = $1.min; - parser_cfg->formula_options.formula_generator. - max_size = $1.max; - } - } - ; - -statespace_size: CFG_INTEGER - { - if (!isLocked(CFG_SIZE)) - { - checkIntegerRange - ($1, Configuration::STATESPACE_SIZE_RANGE, - true); - parser_cfg->statespace_generator.min_size - = $1; - parser_cfg->statespace_generator.max_size - = $1; - } + | CFG_XORPRIORITY equals_value + { + parser_cfg->readInteger + (parser_cfg->formula_options.symbol_priority + [::Ltl::LTL_XOR], + $2, + Configuration::OPERATOR_PRIORITY_RANGE); } - - | CFG_INTEGER_INTERVAL - { - if (!isLocked(CFG_SIZE)) - { - checkIntegerRange - ($1.min, - Configuration::STATESPACE_SIZE_RANGE, - true); - - Configuration::IntegerRange max_size_range - (Configuration::STATESPACE_MAX_SIZE_RANGE); - - checkIntegerRange($1.max, max_size_range, - true); - - parser_cfg->statespace_generator.min_size - = $1.min; - parser_cfg->statespace_generator.max_size - = $1.max; - } - } ; %% @@ -1370,7 +652,7 @@ int parseConfiguration(FILE* stream, Configuration& configuration) * configuration -- A reference to a Configuration object in * which the configuration should be stored. * - * Returns: + * Returns: The result of yyparse() on the file. * * ------------------------------------------------------------------------- */ { diff --git a/lbtt/src/Configuration.cc b/lbtt/src/Configuration.cc index ff4b8fb67..9061e3a1d 100644 --- a/lbtt/src/Configuration.cc +++ b/lbtt/src/Configuration.cc @@ -18,10 +18,11 @@ */ #include -#include +#include #include #include #include "Configuration.h" +#include "IntervalList.h" #ifdef HAVE_GETOPT_LONG #include #define OPTIONSTRUCT struct option @@ -34,67 +35,30 @@ #define getopt_long gnu_getopt_long #endif /* HAVE_GETOPT_LONG */ -/****************************************************************************** - * - * Declarations for functions and variables provided by the parser. - * - *****************************************************************************/ - -#include "Config-parse.h" /* Include declarations for - * the tokens that may be - * present in the - * configuration file. - */ - -extern int parseConfiguration /* Parser interface. */ - (FILE*, Configuration&); - -extern void checkIntegerRange /* Range checking */ - (long int, /* functions. */ - const struct Configuration::IntegerRange&, - bool); - -extern void checkProbability(double, bool); - - - /****************************************************************************** * * Definitions for ranges of certain integer-valued configuration options. * *****************************************************************************/ + +const struct Configuration::IntegerRange Configuration::DEFAULT_RANGE + = {0, ULONG_MAX, "value out of range"}; const struct Configuration::IntegerRange Configuration::VERBOSITY_RANGE = {0, 5, "verbosity must be between 0 and 5 (inclusive)"}; - + const struct Configuration::IntegerRange Configuration::ROUND_COUNT_RANGE - = {1, LONG_MAX, "number of rounds must be positive"}; + = {1, ULONG_MAX, "number of rounds must be positive"}; -const struct Configuration::IntegerRange Configuration::GENERATION_RANGE - = {0, LONG_MAX, "length of interval must be nonnegative"}; - -const struct Configuration::IntegerRange Configuration::PRIORITY_RANGE - = {0, INT_MAX / 14, "priority value out of range"}; - -const struct Configuration::IntegerRange - Configuration::PROPOSITION_COUNT_RANGE - = {0, LONG_MAX, "number of propositions must be nonnegative"}; - -const struct Configuration::IntegerRange Configuration::FORMULA_SIZE_RANGE - = {1, LONG_MAX, "formula size must be always positive"}; - -const struct Configuration::IntegerRange - Configuration::FORMULA_MAX_SIZE_RANGE - = {1, LONG_MAX, "minimum formula size exceeds the maximum formula size"}; - -const struct Configuration::IntegerRange Configuration::STATESPACE_SIZE_RANGE - = {1, LONG_MAX, "state space size must be always positive"}; - -const struct Configuration::IntegerRange - Configuration::STATESPACE_MAX_SIZE_RANGE - = {1, LONG_MAX, "minimum state space size exceeds the maximum state space " - "size"}; +const struct Configuration::IntegerRange Configuration::RANDOM_SEED_RANGE + = {0, UINT_MAX, "random seed out of range"}; + +const struct Configuration::IntegerRange Configuration::ATOMIC_PRIORITY_RANGE + = {0, INT_MAX / 3, "priority out of range"}; +const struct Configuration::IntegerRange Configuration::OPERATOR_PRIORITY_RANGE + = {0, INT_MAX / 14, "priority out of range"}; + /****************************************************************************** @@ -136,9 +100,9 @@ Configuration::~Configuration() ::const_iterator it = algorithms.begin(); it != algorithms.end(); ++it) { - delete it->name; - delete it->path_to_program; - delete it->extra_parameters; + for (vector::size_type p = 0; p <= it->num_parameters; ++p) + delete[] it->parameters[p]; + delete[] it->parameters; } } @@ -160,17 +124,15 @@ void Configuration::read(int argc, char* argv[]) reset(); - /* - * Command line option declarations. - */ + /* Command line option declarations. */ static OPTIONSTRUCT command_line_options[] = { - {"comparisontest", no_argument, 0, OPT_COMPARISONTEST}, - {"comparisoncheck", no_argument, 0, OPT_COMPARISONTEST}, + {"comparisontest", optional_argument, 0, OPT_COMPARISONTEST}, + {"comparisoncheck", optional_argument, 0, OPT_COMPARISONTEST}, {"configfile", required_argument, 0, OPT_CONFIGFILE}, - {"consistencytest", no_argument, 0, OPT_CONSISTENCYTEST}, - {"consistencycheck", no_argument, 0, OPT_CONSISTENCYTEST}, + {"consistencytest", optional_argument, 0, OPT_CONSISTENCYTEST}, + {"consistencycheck", optional_argument, 0, OPT_CONSISTENCYTEST}, {"disable", required_argument, 0, OPT_DISABLE}, {"enable", required_argument, 0, OPT_ENABLE}, {"formulachangeinterval", required_argument, 0, OPT_FORMULACHANGEINTERVAL}, @@ -178,21 +140,19 @@ void Configuration::read(int argc, char* argv[]) {"formularandomseed", required_argument, 0, OPT_FORMULARANDOMSEED}, {"globalmodelcheck", no_argument, 0, OPT_GLOBALPRODUCT}, {"help", no_argument, 0, OPT_HELP}, - {"interactive", required_argument, 0, OPT_INTERACTIVE}, - {"intersectiontest", no_argument, 0, OPT_INTERSECTIONTEST}, - {"intersectioncheck", no_argument, 0, OPT_INTERSECTIONTEST}, + {"interactive", optional_argument, 0, OPT_INTERACTIVE}, + {"intersectiontest", optional_argument, 0, OPT_INTERSECTIONTEST}, + {"intersectioncheck", optional_argument, 0, OPT_INTERSECTIONTEST}, {"localmodelcheck", no_argument, 0, OPT_LOCALPRODUCT}, {"logfile", required_argument, 0, OPT_LOGFILE}, {"modelcheck", required_argument, 0, OPT_MODELCHECK}, - {"nocomparisontest", no_argument, 0, OPT_NOCOMPARISONTEST}, - {"nocomparisoncheck", no_argument, 0, OPT_NOCOMPARISONTEST}, - {"noconsistencytest", no_argument, 0, OPT_NOCONSISTENCYTEST}, - {"noconsistencycheck", no_argument, 0, OPT_NOCONSISTENCYTEST}, - {"nointersectiontest", no_argument, 0, OPT_NOINTERSECTIONTEST}, - {"nointersectioncheck", no_argument, 0, OPT_NOINTERSECTIONTEST}, - {"nopause", no_argument, 0, OPT_NOPAUSE}, - {"pause", no_argument, 0, OPT_PAUSE}, - {"pauseonerror", no_argument, 0, OPT_PAUSEONERROR}, + {"nocomparisontest", no_argument, 0, OPT_COMPARISONTEST}, + {"nocomparisoncheck", no_argument, 0, OPT_COMPARISONTEST}, + {"noconsistencytest", no_argument, 0, OPT_CONSISTENCYTEST}, + {"noconsistencycheck", no_argument, 0, OPT_CONSISTENCYTEST}, + {"nointersectiontest", no_argument, 0, OPT_INTERSECTIONTEST}, + {"nointersectioncheck", no_argument, 0, OPT_INTERSECTIONTEST}, + {"pause", optional_argument, 0, OPT_INTERACTIVE}, {"profile", no_argument, 0, OPT_PROFILE}, {"quiet", no_argument, 0, OPT_QUIET}, {"rounds", required_argument, 0, OPT_ROUNDS}, @@ -203,10 +163,11 @@ void Configuration::read(int argc, char* argv[]) {"statespacechangeinterval", required_argument, 0, OPT_STATESPACECHANGEINTERVAL}, {"statespacerandomseed", required_argument, 0, OPT_STATESPACERANDOMSEED}, + {"translatortimeout", required_argument, 0 ,OPT_TRANSLATORTIMEOUT}, {"verbosity", required_argument, 0, OPT_VERBOSITY}, {"version", no_argument, 0, OPT_VERSION}, - {"abbreviatedoperators", no_argument, 0, OPT_ABBREVIATEDOPERATORS}, + {"abbreviatedoperators", optional_argument, 0, OPT_ABBREVIATEDOPERATORS}, {"andpriority", required_argument, 0, OPT_ANDPRIORITY}, {"beforepriority", required_argument, 0, OPT_BEFOREPRIORITY}, {"defaultoperatorpriority", required_argument, 0, @@ -222,7 +183,7 @@ void Configuration::read(int argc, char* argv[]) {"globallypriority", required_argument, 0, OPT_GLOBALLYPRIORITY}, {"implicationpriority", required_argument, 0, OPT_IMPLICATIONPRIORITY}, {"nextpriority", required_argument, 0, OPT_NEXTPRIORITY}, - {"noabbreviatedoperators", no_argument, 0, OPT_NOABBREVIATEDOPERATORS}, + {"noabbreviatedoperators", no_argument, 0, OPT_ABBREVIATEDOPERATORS}, {"nogeneratennf", no_argument, 0, OPT_NOGENERATENNF}, {"nooutputnnf", no_argument, 0, OPT_NOOUTPUTNNF}, {"notpriority", required_argument, 0, OPT_NOTPRIORITY}, @@ -251,185 +212,52 @@ void Configuration::read(int argc, char* argv[]) {0, 0, 0, 0} }; - opterr = 1; + opterr = 1; /* enable error messages from getopt_long */ + + const char* false_value = "false", *true_value = "true", + *always_value = "always"; int opttype; int option_index; - bool error = false, print_config = false, - print_operator_distribution = false; + bool print_config = false, print_operator_distribution = false; + config_file_line_number = -1; - string enabled_or_disabled_algorithms[2]; - - locked_options.clear(); + typedef pair Parameter; + vector parameters; /* - * Read the command line parameters. + * Preprocess the command line parameters. At this point only those special + * options that do not override settings in the configuration file are + * processed completely; all other options are stored in the vector + * `parameters' to be handled only after reading the configuration file. + * The arguments of all parameters taking optional parameters are + * adjusted here. */ do { option_index = 0; - opttype = getopt_long(argc, argv, "h", command_line_options, + opttype = getopt_long(argc, argv, "hV", command_line_options, &option_index); switch (opttype) { - case OPT_COMPARISONTEST : - case OPT_NOCOMPARISONTEST : - global_options.do_comp_test = (opttype == OPT_COMPARISONTEST); - locked_options.insert(make_pair(CFG_GLOBALOPTIONS, - CFG_COMPARISONTEST)); - break; - case OPT_CONFIGFILE : global_options.cfg_filename = optarg; break; - case OPT_CONSISTENCYTEST : - case OPT_NOCONSISTENCYTEST : - global_options.do_cons_test = (opttype == OPT_CONSISTENCYTEST); - locked_options.insert(make_pair(CFG_GLOBALOPTIONS, - CFG_CONSISTENCYTEST)); - break; - - case OPT_DISABLE : - case OPT_ENABLE : - { - int i = (opttype == OPT_DISABLE ? 1 : 0); - - if (!enabled_or_disabled_algorithms[i].empty()) - enabled_or_disabled_algorithms[i] += ','; - enabled_or_disabled_algorithms[i] += optarg; - } - break; - - case OPT_FORMULACHANGEINTERVAL : - case OPT_STATESPACECHANGEINTERVAL : - { - long int interval_length - = parseCommandLineInteger - (command_line_options[option_index].name, optarg); - checkIntegerRange(interval_length, GENERATION_RANGE, false); - - if (opttype == OPT_FORMULACHANGEINTERVAL) - { - global_options.formula_change_interval = interval_length; - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, - CFG_CHANGEINTERVAL)); - } - else - { - global_options.statespace_change_interval = interval_length; - locked_options.insert(make_pair(CFG_STATESPACEOPTIONS, - CFG_CHANGEINTERVAL)); - } - } - - break; - case OPT_FORMULAFILE : global_options.formula_input_filename = optarg; break; - case OPT_FORMULARANDOMSEED : - global_options.formula_random_seed - = parseCommandLineInteger - (command_line_options[option_index].name, optarg); - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, CFG_RANDOMSEED)); - break; - - case OPT_GLOBALPRODUCT : - global_options.product_mode = GLOBAL; - locked_options.insert(make_pair(CFG_GLOBALOPTIONS, CFG_MODELCHECK)); - break; - case OPT_HELP : showCommandLineHelp(argv[0]); exit(0); - case OPT_INTERACTIVE : - if (strcmp(optarg, "always") == 0) - global_options.interactive = ALWAYS; - else if (strcmp(optarg, "onerror") == 0) - global_options.interactive = ONERROR; - else if (strcmp(optarg, "never") == 0) - global_options.interactive = NEVER; - else - error = true; - - locked_options.insert(make_pair(CFG_GLOBALOPTIONS, CFG_INTERACTIVE)); - break; - - case OPT_INTERSECTIONTEST : - case OPT_NOINTERSECTIONTEST : - global_options.do_intr_test = (opttype == OPT_INTERSECTIONTEST); - locked_options.insert(make_pair(CFG_GLOBALOPTIONS, - CFG_INTERSECTIONTEST)); - break; - - case OPT_LOCALPRODUCT : - global_options.product_mode = LOCAL; - locked_options.insert(make_pair(CFG_GLOBALOPTIONS, CFG_MODELCHECK)); - break; - case OPT_LOGFILE : global_options.transcript_filename = optarg; break; - case OPT_MODELCHECK : - if (strcmp(optarg, "global") == 0) - global_options.product_mode = GLOBAL; - else if (strcmp(optarg, "local") == 0) - global_options.product_mode = LOCAL; - else - error = true; - - locked_options.insert(make_pair(CFG_GLOBALOPTIONS, CFG_MODELCHECK)); - break; - - case OPT_PAUSE : - case OPT_NOPAUSE : - global_options.interactive = (opttype == OPT_PAUSE ? ALWAYS : NEVER); - locked_options.insert(make_pair(CFG_GLOBALOPTIONS, CFG_INTERACTIVE)); - break; - - case OPT_PAUSEONERROR : - global_options.interactive = ONERROR; - locked_options.insert(make_pair(CFG_GLOBALOPTIONS, CFG_INTERACTIVE)); - break; - - case OPT_PROFILE : - global_options.do_comp_test - = global_options.do_cons_test - = global_options.do_intr_test - = false; - locked_options.insert(make_pair(CFG_GLOBALOPTIONS, - CFG_COMPARISONTEST)); - locked_options.insert(make_pair(CFG_GLOBALOPTIONS, - CFG_CONSISTENCYTEST)); - locked_options.insert(make_pair(CFG_GLOBALOPTIONS, - CFG_INTERSECTIONTEST)); - break; - - case OPT_QUIET : - global_options.verbosity = 0; - locked_options.insert(make_pair(CFG_GLOBALOPTIONS, CFG_VERBOSITY)); - global_options.interactive = NEVER; - locked_options.insert(make_pair(CFG_GLOBALOPTIONS, CFG_INTERACTIVE)); - break; - - case OPT_ROUNDS : - { - long int number_of_rounds - = parseCommandLineInteger - (command_line_options[option_index].name, optarg); - checkIntegerRange(number_of_rounds, ROUND_COUNT_RANGE, false); - global_options.number_of_rounds = number_of_rounds; - locked_options.insert(make_pair(CFG_GLOBALOPTIONS, CFG_ROUNDS)); - } - - break; - case OPT_SHOWCONFIG : print_config = true; break; @@ -439,421 +267,69 @@ void Configuration::read(int argc, char* argv[]) break; case OPT_SKIP : - { - long int rounds_to_skip - = parseCommandLineInteger - (command_line_options[option_index].name, optarg); - checkIntegerRange(rounds_to_skip, ROUND_COUNT_RANGE, false); - global_options.init_skip = rounds_to_skip; - } - - break; - - case OPT_STATESPACERANDOMSEED : - global_options.statespace_random_seed - = parseCommandLineInteger - (command_line_options[option_index].name, optarg); - locked_options.insert(make_pair(CFG_STATESPACEOPTIONS, - CFG_RANDOMSEED)); - break; - - case OPT_VERBOSITY : - { - long int verbosity - = parseCommandLineInteger - (command_line_options[option_index].name, optarg); - checkIntegerRange(verbosity, VERBOSITY_RANGE, false); - global_options.verbosity = verbosity; - locked_options.insert(make_pair(CFG_GLOBALOPTIONS, CFG_VERBOSITY)); - } - + readInteger(global_options.init_skip, optarg); break; case OPT_VERSION : cout << "lbtt " PACKAGE_VERSION "\n" "lbtt is free software; you may change and " - "redistribute it under the terms of\n" + "redistribute it under the terms of\n" "the GNU General Public License. lbtt comes with " "NO WARRANTY. See the file\n" "COPYING for details.\n"; exit(0); break; - case OPT_ABBREVIATEDOPERATORS : - case OPT_NOABBREVIATEDOPERATORS : - formula_options.allow_abbreviated_operators - = (opttype == OPT_ABBREVIATEDOPERATORS); - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, - CFG_ABBREVIATEDOPERATORS)); - break; - - case OPT_ANDPRIORITY : - case OPT_BEFOREPRIORITY : - case OPT_DEFAULTOPERATORPRIORITY : - case OPT_EQUIVALENCEPRIORITY : - case OPT_FALSEPRIORITY : - case OPT_FINALLYPRIORITY : - case OPT_GLOBALLYPRIORITY : - case OPT_IMPLICATIONPRIORITY : - case OPT_NEXTPRIORITY : - case OPT_NOTPRIORITY : - case OPT_ORPRIORITY : - case OPT_PROPOSITIONPRIORITY : - case OPT_RELEASEPRIORITY : - case OPT_STRONGRELEASEPRIORITY : - case OPT_TRUEPRIORITY : - case OPT_UNTILPRIORITY : - case OPT_WEAKUNTILPRIORITY : - case OPT_XORPRIORITY : - { - long int priority - = parseCommandLineInteger - (command_line_options[option_index].name, optarg); - - checkIntegerRange(priority, PRIORITY_RANGE, false); - - int symbol = -1; - - switch (opttype) - { - case OPT_ANDPRIORITY : - symbol = ::Ltl::LTL_CONJUNCTION; - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, - CFG_ANDPRIORITY)); - break; - - case OPT_BEFOREPRIORITY : - symbol = ::Ltl::LTL_BEFORE; - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, - CFG_BEFOREPRIORITY)); - break; - - case OPT_DEFAULTOPERATORPRIORITY : - formula_options.default_operator_priority = priority; - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, - CFG_DEFAULTOPERATORPRIORITY)); - break; - - case OPT_EQUIVALENCEPRIORITY : - symbol = ::Ltl::LTL_EQUIVALENCE; - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, - CFG_EQUIVALENCEPRIORITY)); - break; - - case OPT_FALSEPRIORITY : - symbol = ::Ltl::LTL_FALSE; - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, - CFG_FALSEPRIORITY)); - break; - - case OPT_FINALLYPRIORITY : - symbol = ::Ltl::LTL_FINALLY; - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, - CFG_FINALLYPRIORITY)); - break; - - case OPT_GLOBALLYPRIORITY : - symbol = ::Ltl::LTL_GLOBALLY; - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, - CFG_GLOBALLYPRIORITY)); - break; - - case OPT_IMPLICATIONPRIORITY : - symbol = ::Ltl::LTL_IMPLICATION; - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, - CFG_IMPLICATIONPRIORITY)); - break; - - case OPT_NEXTPRIORITY : - symbol = ::Ltl::LTL_NEXT; - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, - CFG_NEXTPRIORITY)); - break; - - case OPT_NOTPRIORITY : - symbol = ::Ltl::LTL_NEGATION; - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, - CFG_NOTPRIORITY)); - break; - - case OPT_ORPRIORITY : - symbol = ::Ltl::LTL_DISJUNCTION; - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, - CFG_ORPRIORITY)); - break; - - case OPT_PROPOSITIONPRIORITY : - symbol = ::Ltl::LTL_ATOM; - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, - CFG_PROPOSITIONPRIORITY)); - break; - - case OPT_RELEASEPRIORITY : - symbol = ::Ltl::LTL_V; - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, - CFG_RELEASEPRIORITY)); - break; - - case OPT_STRONGRELEASEPRIORITY : - symbol = ::Ltl::LTL_STRONG_RELEASE; - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, - CFG_STRONGRELEASEPRIORITY)); - break; - - case OPT_TRUEPRIORITY : - symbol = ::Ltl::LTL_TRUE; - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, - CFG_TRUEPRIORITY)); - break; - - case OPT_UNTILPRIORITY : - symbol = ::Ltl::LTL_UNTIL; - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, - CFG_UNTILPRIORITY)); - break; - - case OPT_WEAKUNTILPRIORITY : - symbol = ::Ltl::LTL_WEAK_UNTIL; - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, - CFG_WEAKUNTILPRIORITY)); - break; - - case OPT_XORPRIORITY : - symbol = ::Ltl::LTL_XOR; - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, - CFG_XORPRIORITY)); - - default : - break; - } - - if (symbol != -1) - formula_options.symbol_priority[symbol] = priority; - } - - break; - - case OPT_FORMULAGENERATEMODE : - if (strcmp(optarg, "nnf") == 0) - formula_options.generate_mode = NNF; - else if (strcmp(optarg, "normal") == 0) - formula_options.generate_mode = NORMAL; - else - error = true; - - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, CFG_GENERATEMODE)); - break; - - case OPT_FORMULAOUTPUTMODE : - if (strcmp(optarg, "nnf") == 0) - formula_options.output_mode = NNF; - else if (strcmp(optarg, "normal") == 0) - formula_options.output_mode = NORMAL; - else - error = true; - - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, CFG_OUTPUTMODE)); - break; - - case OPT_FORMULAPROPOSITIONS : - case OPT_STATESPACEPROPOSITIONS : - { - long int num_propositions - = parseCommandLineInteger - (command_line_options[option_index].name, optarg); - - checkIntegerRange(num_propositions, PROPOSITION_COUNT_RANGE, false); - - if (opttype == OPT_STATESPACEPROPOSITIONS) - { - locked_options.insert(make_pair(CFG_STATESPACEOPTIONS, - CFG_PROPOSITIONS)); - statespace_generator.atoms_per_state = num_propositions; - } - else - { - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, - CFG_PROPOSITIONS)); - formula_options.formula_generator.number_of_available_variables - = num_propositions; - } - } - - break; - - case OPT_FORMULASIZE : - case OPT_STATESPACESIZE : - { - IntegerRange min_size_range, max_size_range; - - if (opttype == OPT_FORMULASIZE) - { - min_size_range = FORMULA_SIZE_RANGE; - max_size_range = FORMULA_MAX_SIZE_RANGE; - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, CFG_SIZE)); - } - else - { - min_size_range = STATESPACE_SIZE_RANGE; - max_size_range = STATESPACE_MAX_SIZE_RANGE; - locked_options.insert(make_pair(CFG_STATESPACEOPTIONS, CFG_SIZE)); - } - - string value(optarg); - string::size_type index = value.find("..."); - - if (index == string::npos) - { - long int size - = parseCommandLineInteger - (command_line_options[option_index].name, value); - - checkIntegerRange(size, min_size_range, false); - - if (opttype == OPT_FORMULASIZE) - formula_options.formula_generator.size - = formula_options.formula_generator.max_size - = size; - else - statespace_generator.min_size - = statespace_generator.max_size - = size; - } - else - { - string option_name(command_line_options[option_index].name); - - long int min = parseCommandLineInteger(option_name + " (min)", - value.substr(0, index)); - long int max = parseCommandLineInteger(option_name + " (max)", - value.substr(index + 3)); - - checkIntegerRange(min, min_size_range, false); - max_size_range.min = min; - checkIntegerRange(max, max_size_range, false); - - if (opttype == OPT_FORMULASIZE) - { - formula_options.formula_generator.size = min; - formula_options.formula_generator.max_size = max; - } - else - { - statespace_generator.min_size = min; - statespace_generator.max_size = max; - } - } - } - - break; - - case OPT_GENERATENNF : - case OPT_NOGENERATENNF : - formula_options.generate_mode - = (opttype == OPT_GENERATENNF ? NNF : NORMAL); - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, CFG_GENERATEMODE)); - break; - - case OPT_OUTPUTNNF : - case OPT_NOOUTPUTNNF : - formula_options.output_mode - = (opttype == OPT_OUTPUTNNF ? NNF : NORMAL); - locked_options.insert(make_pair(CFG_FORMULAOPTIONS, CFG_OUTPUTMODE)); - break; - - case OPT_EDGEPROBABILITY : - case OPT_TRUTHPROBABILITY : - { - char* endptr; - double probability = strtod(optarg, &endptr); - - if (*endptr != '\0') - probability = -1.0; - - checkProbability(probability, false); - - if (opttype == OPT_EDGEPROBABILITY) - { - statespace_generator.edge_probability = probability; - locked_options.insert(make_pair(CFG_STATESPACEOPTIONS, - CFG_EDGEPROBABILITY)); - } - else - { - statespace_generator.truth_probability = probability; - locked_options.insert(make_pair(CFG_STATESPACEOPTIONS, - CFG_TRUTHPROBABILITY)); - } - } - - break; - - case OPT_ENUMERATEDPATH : - global_options.statespace_generation_mode = ENUMERATEDPATH; - locked_options.insert(make_pair(CFG_STATESPACEOPTIONS, - CFG_GENERATEMODE)); - break; - - case OPT_RANDOMCONNECTEDGRAPH : - global_options.statespace_generation_mode = RANDOMCONNECTEDGRAPH; - locked_options.insert(make_pair(CFG_STATESPACEOPTIONS, - CFG_GENERATEMODE)); - break; - - case OPT_RANDOMGRAPH : - global_options.statespace_generation_mode = RANDOMGRAPH; - locked_options.insert(make_pair(CFG_STATESPACEOPTIONS, - CFG_GENERATEMODE)); - break; - - case OPT_RANDOMPATH : - global_options.statespace_generation_mode = RANDOMPATH; - locked_options.insert(make_pair(CFG_STATESPACEOPTIONS, - CFG_GENERATEMODE)); - break; - - case OPT_STATESPACEGENERATEMODE : - if (strcmp(optarg, "randomconnectedgraph") == 0) - global_options.statespace_generation_mode = RANDOMCONNECTEDGRAPH; - else if (strcmp(optarg, "randomgraph") == 0) - global_options.statespace_generation_mode = RANDOMGRAPH; - else if (strcmp(optarg, "randompath") == 0) - global_options.statespace_generation_mode = RANDOMPATH; - else if (strcmp(optarg, "enumeratedpath") == 0) - global_options.statespace_generation_mode = ENUMERATEDPATH; - else - error = true; - - locked_options.insert(make_pair(CFG_STATESPACEOPTIONS, - CFG_GENERATEMODE)); - break; - case '?' : case ':' : - exit(-1); - } + exit(2); - if (error) - throw ConfigurationException - ("", string("unrecognized argument (`") + optarg - + "') for option `--" - + command_line_options[option_index].name + "'"); + case -1 : + break; + + case OPT_CONSISTENCYTEST : + case OPT_COMPARISONTEST : + case OPT_INTERSECTIONTEST : + case OPT_ABBREVIATEDOPERATORS : + { + const char* val; + if (command_line_options[option_index].name[0] == 'n') + val = false_value; + else if (optarg == 0) + val = true_value; + else + val = optarg; + parameters.push_back(make_pair(&command_line_options[option_index], + val)); + break; + } + + case OPT_INTERACTIVE : + { + const char* val; + if (optarg == 0) + val = always_value; + else + val = optarg; + parameters.push_back(make_pair(&command_line_options[option_index], + val)); + break; + } + + default : + parameters.push_back(make_pair(&command_line_options[option_index], + optarg)); + break; + } } while (opttype != -1); - if (optind != argc) - throw ConfigurationException - ("", string("unrecognized command line option `") - + argv[optind] + "'"); - - /* - * Read the configuration file. - */ + /* Read the configuration file. */ FILE* configuration_file = fopen(global_options.cfg_filename.c_str(), "r"); if (configuration_file == NULL) throw ConfigurationException - ("", "error opening configuration file `" + (-1, "error opening configuration file `" + global_options.cfg_filename + "'"); try @@ -867,53 +343,370 @@ void Configuration::read(int argc, char* argv[]) throw; } + config_file_line_number = -1; /* Suppress configuration file line number in + * any future error messages */ + /* - * Use the information gathered from command line options to enable or - * disable some of the implementations. + * Process the command line parameters that override settings made in the + * configuration file. */ - set, ALLOC(unsigned long int) > - algorithm_id_set; + vector::const_iterator parameter; - for (int i = 0; i < 2; i++) + try { - if (enabled_or_disabled_algorithms[i].empty()) - continue; - - try + for (parameter = parameters.begin(); parameter != parameters.end(); + ++parameter) { - parseInterval(enabled_or_disabled_algorithms[i], algorithm_id_set, 0, - algorithms.size() - 1); + switch (parameter->first->val) + { + /* Remaining special options (excluding "--enable" and "--disable"). */ - if (algorithm_id_set.empty()) - throw Exception(); - } - catch (const Exception&) - { - throw ConfigurationException("", - string("invalid argument (`") - + enabled_or_disabled_algorithms[i] - + "') for option `--" - + (i == 0 ? "en" : "dis") - + "able'"); + case OPT_ENABLE : case OPT_DISABLE : /* These options can be */ + break; /* processed only after + * determining whether the + * internal model checking + * algorithm might be + * included in the tests. + */ + + case OPT_PROFILE : + global_options.do_comp_test + = global_options.do_cons_test + = global_options.do_intr_test + = false; + break; + + case OPT_QUIET : + global_options.verbosity = 0; + global_options.interactive = NEVER; + break; + + /* + * Options corresponding to the GlobalOptions section in the + * configuration file. + */ + + case OPT_COMPARISONTEST : + readTruthValue(global_options.do_comp_test, parameter->second); + break; + + case OPT_CONSISTENCYTEST : + readTruthValue(global_options.do_cons_test, parameter->second); + break; + + case OPT_GLOBALPRODUCT : + readProductType("global"); + break; + + case OPT_INTERACTIVE : + readInteractivity(parameter->second); + break; + + case OPT_INTERSECTIONTEST : + readTruthValue(global_options.do_intr_test, parameter->second); + break; + + case OPT_LOCALPRODUCT : + readProductType("local"); + break; + + case OPT_MODELCHECK : + readProductType(parameter->second); + break; + + case OPT_ROUNDS : + readInteger(global_options.number_of_rounds, parameter->second, + ROUND_COUNT_RANGE); + break; + + case OPT_TRANSLATORTIMEOUT : + readTranslatorTimeout(parameter->second); + break; + + case OPT_VERBOSITY : + readInteger(global_options.verbosity, parameter->second, + VERBOSITY_RANGE); + break; + + /* + * Options corresponding to the StatespaceOptions section in the + * configuration file. + */ + + case OPT_EDGEPROBABILITY : + readProbability(statespace_generator.edge_probability, + parameter->second); + break; + + case OPT_ENUMERATEDPATH : + readStateSpaceMode("enumeratedpath"); + break; + + case OPT_RANDOMCONNECTEDGRAPH : + readStateSpaceMode("randomconnectedgraph"); + break; + + case OPT_RANDOMGRAPH : + readStateSpaceMode("randomgraph"); + break; + + case OPT_RANDOMPATH : + readStateSpaceMode("randompath"); + break; + + case OPT_STATESPACECHANGEINTERVAL : + readInteger(global_options.statespace_change_interval, + parameter->second); + break; + + case OPT_STATESPACEGENERATEMODE : + readStateSpaceMode(parameter->second); + break; + + case OPT_STATESPACEPROPOSITIONS : + readInteger(statespace_generator.atoms_per_state, parameter->second); + break; + + case OPT_STATESPACERANDOMSEED : + readInteger(global_options.statespace_random_seed, + parameter->second, RANDOM_SEED_RANGE); + break; + + case OPT_STATESPACESIZE : + readSize(parameter->first->val, parameter->second); + break; + + case OPT_TRUTHPROBABILITY : + readProbability(statespace_generator.truth_probability, + parameter->second); + break; + + /* + * Options corresponding to the FormulaOptions section in the + * configuration file. + */ + + case OPT_ABBREVIATEDOPERATORS : + readTruthValue(formula_options.allow_abbreviated_operators, + parameter->second); + break; + + case OPT_ANDPRIORITY : + readInteger(formula_options.symbol_priority[::Ltl::LTL_CONJUNCTION], + parameter->second, OPERATOR_PRIORITY_RANGE); + break; + + case OPT_BEFOREPRIORITY : + readInteger(formula_options.symbol_priority[::Ltl::LTL_BEFORE], + parameter->second, OPERATOR_PRIORITY_RANGE); + break; + + case OPT_DEFAULTOPERATORPRIORITY : + readInteger(formula_options.default_operator_priority, + parameter->second, OPERATOR_PRIORITY_RANGE); + break; + + case OPT_EQUIVALENCEPRIORITY : + readInteger(formula_options.symbol_priority[::Ltl::LTL_EQUIVALENCE], + parameter->second, OPERATOR_PRIORITY_RANGE); + break; + + case OPT_FALSEPRIORITY : + readInteger(formula_options.symbol_priority[::Ltl::LTL_FALSE], + parameter->second, ATOMIC_PRIORITY_RANGE); + break; + + case OPT_FINALLYPRIORITY : + readInteger(formula_options.symbol_priority[::Ltl::LTL_FINALLY], + parameter->second, OPERATOR_PRIORITY_RANGE); + break; + + case OPT_FORMULACHANGEINTERVAL : + readInteger(global_options.formula_change_interval, + parameter->second); + break; + + case OPT_FORMULAGENERATEMODE : + readFormulaMode(formula_options.generate_mode, parameter->second); + break; + + case OPT_FORMULAOUTPUTMODE : + readFormulaMode(formula_options.output_mode, parameter->second); + break; + + case OPT_FORMULAPROPOSITIONS : + readInteger(formula_options.formula_generator. + number_of_available_variables, + parameter->second); + break; + + case OPT_FORMULARANDOMSEED : + readInteger(global_options.formula_random_seed, parameter->second, + RANDOM_SEED_RANGE); + break; + + case OPT_FORMULASIZE : + readSize(parameter->first->val, parameter->second); + break; + + case OPT_GENERATENNF : + readFormulaMode(formula_options.generate_mode, "nnf"); + break; + + case OPT_GLOBALLYPRIORITY : + readInteger(formula_options.symbol_priority[::Ltl::LTL_GLOBALLY], + parameter->second, OPERATOR_PRIORITY_RANGE); + break; + + case OPT_IMPLICATIONPRIORITY : + readInteger(formula_options.symbol_priority[::Ltl::LTL_IMPLICATION], + parameter->second, OPERATOR_PRIORITY_RANGE); + break; + + case OPT_NEXTPRIORITY : + readInteger(formula_options.symbol_priority[::Ltl::LTL_NEXT], + parameter->second, OPERATOR_PRIORITY_RANGE); + break; + + case OPT_NOGENERATENNF : + readFormulaMode(formula_options.generate_mode, "normal"); + break; + + case OPT_NOOUTPUTNNF : + readFormulaMode(formula_options.output_mode, "normal"); + break; + + case OPT_NOTPRIORITY : + readInteger(formula_options.symbol_priority[::Ltl::LTL_NEGATION], + parameter->second, OPERATOR_PRIORITY_RANGE); + break; + + case OPT_ORPRIORITY : + readInteger(formula_options.symbol_priority[::Ltl::LTL_DISJUNCTION], + parameter->second, OPERATOR_PRIORITY_RANGE); + break; + + case OPT_OUTPUTNNF : + readFormulaMode(formula_options.output_mode, "nnf"); + break; + + case OPT_PROPOSITIONPRIORITY : + readInteger(formula_options.symbol_priority[::Ltl::LTL_ATOM], + parameter->second, ATOMIC_PRIORITY_RANGE); + break; + + case OPT_RELEASEPRIORITY : + readInteger(formula_options.symbol_priority[::Ltl::LTL_V], + parameter->second, OPERATOR_PRIORITY_RANGE); + break; + + case OPT_STRONGRELEASEPRIORITY : + readInteger(formula_options.symbol_priority + [::Ltl::LTL_STRONG_RELEASE], + parameter->second, OPERATOR_PRIORITY_RANGE); + break; + + case OPT_TRUEPRIORITY : + readInteger(formula_options.symbol_priority[::Ltl::LTL_TRUE], + parameter->second, ATOMIC_PRIORITY_RANGE); + break; + + case OPT_UNTILPRIORITY : + readInteger(formula_options.symbol_priority[::Ltl::LTL_UNTIL], + parameter->second, OPERATOR_PRIORITY_RANGE); + break; + + case OPT_WEAKUNTILPRIORITY : + readInteger(formula_options.symbol_priority[::Ltl::LTL_WEAK_UNTIL], + parameter->second, OPERATOR_PRIORITY_RANGE); + break; + + case OPT_XORPRIORITY : + readInteger(formula_options.symbol_priority[::Ltl::LTL_XOR], + parameter->second, OPERATOR_PRIORITY_RANGE); + break; + } } - for (set, - ALLOC(unsigned long int) >::const_iterator - id = algorithm_id_set.begin(); - id != algorithm_id_set.end(); - ++id) - { - if (*id >= algorithms.size()) - throw ConfigurationException - ("", - string("invalid implementation identifier (") + toString(*id) - + ") in the argument for `--" - + (i == 0 ? "en" : "dis") - + "able'"); + /* + * If using paths as state spaces, include the internal model checking + * algorithm in the set of algorithms. + */ - algorithms[*id].enabled = (i == 0); + if (global_options.statespace_generation_mode & Configuration::PATH) + { + AlgorithmInformation lbtt_info = {"lbtt", new char*[1], 0, true}; + lbtt_info.parameters[0] = new char[1]; + + algorithm_names["lbtt"] = algorithms.size(); + algorithms.push_back(lbtt_info); } + + /* Process "--enable" and "--disable" options. */ + + for (parameter = parameters.begin(); parameter != parameters.end(); + ++parameter) + { + switch (parameter->first->val) + { + case OPT_DISABLE : + case OPT_ENABLE : + try + { + IntervalList algorithm_ids; + vector nonnumeric_algorithm_ids; + string id_string + = substituteInQuotedString(parameter->second, ",", "\n", + INSIDE_QUOTES); + + parseIntervalList(id_string, algorithm_ids, 0, + algorithms.size() - 1, + &nonnumeric_algorithm_ids); + + for (vector::iterator + id = nonnumeric_algorithm_ids.begin(); + id != nonnumeric_algorithm_ids.end(); + ++id) + { + *id = unquoteString(substituteInQuotedString(*id, "\n", ",")); + map, + ALLOC(unsigned long int) >::const_iterator id_finder + = algorithm_names.find(*id); + if (id_finder == algorithm_names.end()) + throw ConfigurationException + (-1, + string("unknown implementation identifier (`") + + *id + + "')"); + algorithm_ids.merge(id_finder->second); + } + + for (IntervalList::const_iterator id = algorithm_ids.begin(); + id != algorithm_ids.end(); + ++id) + algorithms[*id].enabled = (parameter->first->val == OPT_ENABLE); + } + catch (const IntervalRangeException& e) + { + throw ConfigurationException + (-1, + string("invalid implementation identifier (") + + toString(e.getNumber()) + + ")"); + } + + break; + + default : + break; + } + } + } + catch (ConfigurationException& e) + { + e.changeMessage(string("[--") + parameter->first->name + "]: " + e.what()); + throw e; } /* @@ -929,8 +722,7 @@ void Configuration::read(int argc, char* argv[]) if (global_options.number_of_rounds <= global_options.init_skip) throw ConfigurationException - ("", "the argument for `--skip' must be less than the total " - "number of test rounds"); + (-1, "[--skip]: number of rounds is less than skip count"); /* * Check that there is at least one algorithm available for use. @@ -938,7 +730,7 @@ void Configuration::read(int argc, char* argv[]) if (algorithms.empty()) throw ConfigurationException - ("", "no implementations defined in the configuration file"); + (-1, "no implementations defined in the configuration file"); /* * The case where the number of available variables for propositional @@ -957,8 +749,8 @@ void Configuration::read(int argc, char* argv[]) if (formula_options.symbol_priority[::Ltl::LTL_ATOM] == 0 && formula_options.symbol_priority[::Ltl::LTL_TRUE] == 0 && formula_options.symbol_priority[::Ltl::LTL_FALSE] == 0) - throw ConfigurationException("", "at least one atomic symbol must have " - "nonzero priority"); + throw ConfigurationException(-1, "at least one atomic symbol should have " + "a nonzero priority"); /* * If the operators ->, <->, xor, <>, [], W and M are disallowed, set their @@ -1000,7 +792,7 @@ void Configuration::read(int argc, char* argv[]) } if (!unary_operator_allowed) - throw ConfigurationException("", "at least one unary operator must have " + throw ConfigurationException(-1, "at least one unary operator should have " "a nonzero priority"); /* @@ -1175,6 +967,10 @@ void Configuration::print(ostream& stream, int indent) const estream << "Testing will be interrupted in case of an error.\n"; else estream << "Running in batch mode.\n"; + estream << string(indent + 2, ' ') + + "Signalling a break will " + + (global_options.handle_breaks ? "interrupt" : "abort") + + " testing.\n"; estream << string(indent + 2, ' ') + "Using " @@ -1206,6 +1002,43 @@ void Configuration::print(ostream& stream, int indent) const algorithm_number++; } + estream << '\n' + string(indent + 2, ' '); + if (global_options.translator_timeout > 0) + { + estream << "Timeout for translators is set to " + + toString(global_options.translator_timeout) + + " seconds"; + if (global_options.translator_timeout >= 60) + { + bool first_printed = false; + estream << " ("; + if (global_options.translator_timeout >= 3600) + { + first_printed = true; + estream << toString(global_options.translator_timeout / 3600) + " h"; + } + if (global_options.translator_timeout % 3600 > 60) + { + if (first_printed) + estream << ' '; + else + first_printed = true; + estream << toString((global_options.translator_timeout % 3600) / 60) + + " min"; + } + if (global_options.translator_timeout % 60 != 0) + { + if (first_printed) + estream << ' '; + estream << toString(global_options.translator_timeout % 60) + " s"; + } + estream << ')'; + } + estream << ".\n"; + } + else + estream << "Translators are allowed to run until their termination.\n"; + estream << '\n' + string(indent + 2, ' '); if (global_options.do_comp_test || global_options.do_cons_test @@ -1521,10 +1354,8 @@ string Configuration::algorithmString * * ------------------------------------------------------------------------- */ { - using namespace ::StringUtil; - - return toString(algorithm_id) + ": `" + *(algorithms[algorithm_id].name) - + '\''; + using ::StringUtil::toString; + return toString(algorithm_id) + ": `" + algorithms[algorithm_id].name + '\''; } /* ========================================================================= */ @@ -1541,11 +1372,13 @@ void Configuration::showCommandLineHelp(const char* program_name) { cout << string("Usage: ") + program_name + " [OPTION]...\n\nGeneral options:\n" - " --[no]comparisontest Enable or disable the model " + " --comparisontest[=VALUE], --nocomparisontest\n" + " Enable or disable the model " "checking result\n" " cross-comparison test\n" " --configfile=FILE Read configuration from FILE\n" - " --[no]consistencytest Enable or disable the model " + " --consistencytest[=VALUE], --noconsistencytest\n" + " Enable or disable the model " "checking result\n" " consistency test\n" " --disable=IMPLEMENTATION-ID[,IMPLEMENTATION-ID...]\n" @@ -1560,10 +1393,12 @@ void Configuration::showCommandLineHelp(const char* program_name) " (equivalent to " "`--modelcheck=global')\n" " -h, --help Show this help and exit\n" - " --interactive=MODE Set the interactivity mode " + " --interactive[=MODE[,MODE]], --pause[=MODE[,MODE]]\n" + " Set the interactivity mode " "(`always', `onerror', \n" - " `never')\n" - " --[no]intersectiontest Enable or disable the Büchi " + " `never', `onbreak')\n" + " --intersectiontest[=VALUE], --nointersectiontest\n" + " Enable or disable the Büchi " "automata\n" " intersection emptiness test\n" " --localmodelcheck Use local model checking in tests" @@ -1573,21 +1408,10 @@ void Configuration::showCommandLineHelp(const char* program_name) " --logfile=FILE Write error log to FILE\n" " --modelcheck=MODE Set model checking mode " "(`global' or `local')\n" - " --nopause Do not pause between test rounds " - "(equivalent to\n" - " `--interactive=never')\n" - " --pause Pause unconditionally after every " - "test round\n" - " (equivalent to " - "`--interactive=always')\n" - " --pauseonerror Pause between test rounds only in " - "case of an\n" - " error (equivalent to " - "`--interactive=onerror')\n" " --profile Disable all automata correctness " "tests\n" " --quiet, --silent Run all tests silently without " - "interruption\n" + "pausing\n" " --rounds=NUMBER-OF-ROUNDS Set number of test rounds (1-)\n" " --showconfig Display current configuration and " "exit\n" @@ -1597,13 +1421,15 @@ void Configuration::showCommandLineHelp(const char* program_name) " --skip=NUMBER-OF-ROUNDS Set number of test rounds to skip " "before\n" " starting tests\n" + " --translatortimeout=TIME Set timeout for translators\n" " --verbosity=INTEGER Set the verbosity of output (0-5)\n" - " --version Display program version and exit" + " -V,--version Display program version and exit" "\n\n" "LTL formula generation options:\n" - " --[no]abbreviatedoperators Allow or disallow operators ->, " - "<->, xor, <>,\n" - " [], u, w in the generated " + " --abbreviatedoperators[=VALUE], --noabbreviatedoperators\n" + " Allow or disallow operators ->, " + "<->, xor, <>, [],\n" + " W, M, and B in the generated " "formulas\n" " --andpriority=INTEGER Set priority for the /\\ operator\n" " --beforepriority=INTEGER Set priority for the Before " @@ -1735,6 +1561,7 @@ void Configuration::reset() { global_options.verbosity = 3; global_options.interactive = ALWAYS; + global_options.handle_breaks = false; global_options.number_of_rounds = 10; global_options.init_skip = 0; global_options.statespace_change_interval = 1; @@ -1749,6 +1576,7 @@ void Configuration::reset() global_options.do_intr_test = true; global_options.statespace_random_seed = 1; global_options.formula_random_seed = 1; + global_options.translator_timeout = 0; formula_options.default_operator_priority = 0; formula_options.symbol_priority.clear(); @@ -1779,40 +1607,418 @@ void Configuration::reset() formula_options.formula_generator.reset(); statespace_generator.reset(); - - locked_options.clear(); } /* ========================================================================= */ -long int Configuration::parseCommandLineInteger - (const string& option, const string& value) const +void Configuration::registerAlgorithm + (const string& name, const string& path, const string& parameters, + bool enabled, const int block_begin_line) /* ---------------------------------------------------------------------------- * - * Description: Converts an integer (given as a string) into a long int. Used - * when processing command line parameters. + * Description: Adds a new implementation to the configuration. * - * Arguments: option -- A reference to a constant string giving a name of - * a command line option. - * value -- A reference to a string which is supposed to - * contain an integer. + * Arguments: name -- Name of the implementation. If empty, + * the implementation will be given the + * name `Algorithm n', where n is the + * number of previously registered + * algorithms. The name "lbtt" is + * reserved and cannot be used as a name + * for an implementation. In addition, + * `name' should be unique among the set + * of the names of previously registered + * implementations. + * path -- Path to the executable file used for + * invoking the implementation. This + * string should not be empty. + * parameters -- Parameters for the implementation. + * Parameters containing white space + * should be quoted. + * enabled -- Whether the implementation is initially + * enabled. + * block_begin_line -- Number of the first line of the most + * recently encountered Algorithm block in + * the configuration file. * - * Returns: The value of the integer. + * Returns: Nothing. The function throws a ConfigurationException if + * `name' or `path' fails to satisfy one of the above + * requirements. + * + * ------------------------------------------------------------------------- */ +{ + using namespace ::StringUtil; + string error; + + AlgorithmInformation algorithm_information; + + if (!name.empty()) + algorithm_information.name = name; + else + algorithm_information.name = "Algorithm " + toString(algorithms.size()); + + if (algorithm_information.name == "lbtt") + error = "`lbtt' is a reserved name for an implementation"; + else if (algorithm_names.find(algorithm_information.name) + != algorithm_names.end()) + error = "multiple definitions for implementation `" + + algorithm_information.name + "'"; + else if (path.empty()) + error = "missing path to executable for implementation `" + + algorithm_information.name + "'"; + + if (!error.empty()) + throw ConfigurationException + (toString(block_begin_line) + + (config_file_line_number > block_begin_line + ? "-" + toString(config_file_line_number) + : string("")), + error); + + vector params; + sliceString(unquoteString(substituteInQuotedString(parameters, " \t", "\n\n", + OUTSIDE_QUOTES)), + "\n", + params); + + /* + * Initialize the parameter array for the implementation. This array is + * arranged into a standard argv-style array of C-style strings (ready to be + * used as a parameter for one of the exec functions) and has the following + * structure: + * Index Description + * 0 -- Path to the executable for invoking the + * implementation (obtained from `path'). + * 1...params.size() -- Optional parameters (obtained from the + * `params' vector). + * params.size() + 1, -- Reserved for storing the input and output + * params.size() + 2 file names given as the last two parameters + * for the implementation. + * params.size() + 3 -- A 0 pointer terminating the parameter list. + */ + + algorithm_information.parameters = new char*[params.size() + 4]; + algorithm_information.num_parameters = params.size(); + + algorithm_information.parameters[0] = new char[path.size() + 1]; + memcpy(static_cast(algorithm_information.parameters[0]), + static_cast(path.c_str()), path.size() + 1); + + for (vector::size_type p = 0; + p < algorithm_information.num_parameters; + ++p) + { + algorithm_information.parameters[p + 1] = new char[params[p].size() + 1]; + memcpy(static_cast(algorithm_information.parameters[p + 1]), + static_cast(params[p].c_str()), params[p].size() + 1); + } + + algorithm_information.parameters + [algorithm_information.num_parameters + 3] = 0; + + algorithm_information.enabled = enabled; + + algorithm_names[algorithm_information.name] = algorithms.size(); + algorithms.push_back(algorithm_information); +} + +/* ========================================================================= */ +void Configuration::readProbability(double& target, const string& value) +/* ---------------------------------------------------------------------------- + * + * Description: Reads a probability and stores it into `target'. + * + * Arguments: target -- A reference to a double for storing the result. + * value -- The probability as a string. + * + * Returns: Nothing; the result is stored into `target'. The function + * throws a ConfigurationException if `value' is not a valid + * probability (a number between 0.0 and 1.0). * * ------------------------------------------------------------------------- */ { char* endptr; - long int val = strtol(value.c_str(), &endptr, 10); + string error; - if (*endptr != '\0' || value.empty()) + target = strtod(value.c_str(), &endptr); + if (*endptr != '\0') + error = "`" + value + "' is not a valid real number"; + else if (target < 0.0 || target > 1.0) + error = "probability must be between 0.0 and 1.0 (inclusive)"; + + if (!error.empty()) + throw ConfigurationException(config_file_line_number, error); +} + +/* ========================================================================= */ +void Configuration::readSize(int valtype, const string& value) +/* ---------------------------------------------------------------------------- + * + * Description: Initializes formula or state space size ranges from `value'. + * + * Arguments: valtype -- If == OPT_STATESPACESIZE, store the result in + * `this->statespace_generator.min_size' and + * `this->statespace_generator.max_size'; otherwise + * store the result in + * `this->formula_options.formula_generator.size' + * and + * `this->formula_options.formula_generator. + * max_size'. + * value -- Size range as a string (a single integer or a + * closed integer interval). + * + * Returns: Nothing; the result is stored into the Configuration object. + * The function throws a ConfigurationException if `value' is + * not a valid positive integer or a closed nonempty integer + * interval. + * + * ------------------------------------------------------------------------- */ +{ + string error; + unsigned long int min, max; + + try + { + int interval_type = ::StringUtil::parseInterval(value, min, max); + if (!(interval_type & ::StringUtil::LEFT_BOUNDED) || + !(interval_type & ::StringUtil::RIGHT_BOUNDED)) + throw Exception(); + + if (min < 1) + { + if (valtype == OPT_STATESPACESIZE) + error = "state space size must be positive"; + else + error = "formula size must be positive"; + } + else if (min > max) + { + if (valtype == OPT_STATESPACESIZE) + error = "minimum state space size exceeds maximum state space size"; + else + error = "minimum formula size exceeds maximum formula size"; + } + } + catch (const Exception&) + { + error = "`" + value + "' is neither a valid positive integer nor a closed " + "integer interval"; + } + + if (!error.empty()) + throw ConfigurationException(config_file_line_number, error); + + if (valtype == OPT_STATESPACESIZE) + { + statespace_generator.min_size = min; + statespace_generator.max_size = max; + } + else + { + formula_options.formula_generator.size = min; + formula_options.formula_generator.max_size = max; + } +} + +/* ========================================================================= */ +void Configuration::readTruthValue(bool& target, const string& value) +/* ---------------------------------------------------------------------------- + * + * Description: Interprets a symbolic truth value and stores it into + * `target'. + * + * Arguments: target -- A reference to a Boolean variable whose value + * should be set according to the given value. + * value -- The symbolic truth value. + * + * Returns: Nothing; the interpreted value is stored in `target'. If + * `value' is not a valid truth value, the function throws a + * ConfigurationException. + * + * ------------------------------------------------------------------------- */ +{ + const string value_in_lower_case = ::StringUtil::toLowerCase(value); + + if (value_in_lower_case == "yes" || value_in_lower_case == "true") + target = true; + else if (value_in_lower_case == "no" || value_in_lower_case == "false") + target = false; + else throw ConfigurationException - ("", "the argument for `--" + option + "' must be a nonnegative " - "integer"); + (config_file_line_number, + "`" + value + "' is not a valid truth value"); +} - if (val == LONG_MIN || val == LONG_MAX) +/* ========================================================================= */ +void Configuration::readInteractivity(const string& value) +/* ---------------------------------------------------------------------------- + * + * Description: Interprets a symbolic list of interactivity modes and updates + * `this->global_options.interactive' and + * `this->global_options.handle_breaks' accordingly. + * + * Argument: value -- The symbolic mode (a comma-separated list of + * "always", "onerror", "never" or "onbreak"; the + * case is not relevant). + * + * Returns: Nothing; the result is stored in + * `this->global_options.interactive' and/or + * `this->global_options.handle_breaks'. The function throws a + * ConfigurationException is `value' is not a valid + * interactivity mode. + * + * ------------------------------------------------------------------------- */ +{ + /* + * Reset the interactivity mode to NEVER and disable break handling to allow + * the interactivity specification to be interpreted correctly. + */ + + global_options.interactive = NEVER; + global_options.handle_breaks = false; + + vector modes; + ::StringUtil::sliceString(value, ",", modes); + for (vector::const_iterator mode = modes.begin(); + mode != modes.end(); + ++mode) + { + string mode_in_lower_case = ::StringUtil::toLowerCase(*mode); + + if (mode_in_lower_case == "always") + global_options.interactive = ALWAYS; + else if (mode_in_lower_case == "onerror") + global_options.interactive = ONERROR; + else if (mode_in_lower_case == "never") + global_options.interactive = NEVER; + else if (mode_in_lower_case == "onbreak") + global_options.handle_breaks = true; + else + throw ConfigurationException + (config_file_line_number, + "`" + *mode + "' is not a valid interactivity mode"); + } +} + +/* ========================================================================= */ +void Configuration::readProductType(const string& value) +/* ---------------------------------------------------------------------------- + * + * Description: Interprets a symbolic model checking mode and updates + * `this->global_options.product_mode' accordingly. + * + * Argument: value -- The symbolic mode (one of "local" or "global"; the + * case of characters is not relevant). + * + * Returns: Nothing; the result is stored in + * `this->global_options.product_mode'. The function throws a + * ConfigurationException is `value' is not a valid model + * checking mode. + * + * ------------------------------------------------------------------------- */ +{ + const string value_in_lower_case = ::StringUtil::toLowerCase(value); + + if (value_in_lower_case == "global") + global_options.product_mode = GLOBAL; + else if (value_in_lower_case == "local") + global_options.product_mode = LOCAL; + else throw ConfigurationException - ("", "the argument for `--" + option + "' is out of range"); + (config_file_line_number, + "`" + value + "' is not a valid model checking mode"); +} - return val; +/* ========================================================================= */ +void Configuration::readFormulaMode(FormulaMode& target, const string& mode) +/* ---------------------------------------------------------------------------- + * + * Description: Interprets a symbolic formula mode and updates `target' + * accordingly. + * + * Argument: mode -- Symbolic formula mode (one of "normal" or "nnf"; + * the case of characters is not relevant). + * + * Returns: Nothing; the result is stored into `target'. The function + * throws a ConfigurationException if `mode' is not a valid mode + * string. + * + * ------------------------------------------------------------------------- */ +{ + const string mode_in_lower_case = ::StringUtil::toLowerCase(mode); + + if (mode_in_lower_case == "nnf") + target = NNF; + else if (mode_in_lower_case == "normal") + target = NORMAL; + else + throw ConfigurationException + (config_file_line_number, + "`" + mode + "' is not a valid formula mode"); +} + +/* ========================================================================= */ +void Configuration::readStateSpaceMode(const string& mode) +/* ---------------------------------------------------------------------------- + * + * Description: Interprets a symbolic state space generation mode and updates + * `global_options.statespace_generation_mode' accordingly. + * + * Argument: mode -- Symbolic state space generation mode (one of + * "randomconnectedgraph", "randomgraph", "randompath" + * and "enumeratedpath"; the case of characters is + * not relevant). + * + * Returns: Nothing; the result is stored into + * `global_options.statespace_generation_mode'. The function + * throws a ConfigurationException if `mode' is not one of the + * above keywords. + * + * ------------------------------------------------------------------------- */ +{ + const string mode_in_lower_case = ::StringUtil::toLowerCase(mode); + + if (mode_in_lower_case == "randomconnectedgraph") + global_options.statespace_generation_mode = RANDOMCONNECTEDGRAPH; + else if (mode_in_lower_case == "randomgraph") + global_options.statespace_generation_mode = RANDOMGRAPH; + else if (mode_in_lower_case == "randompath") + global_options.statespace_generation_mode = RANDOMPATH; + else if (mode_in_lower_case == "enumeratedpath") + global_options.statespace_generation_mode = ENUMERATEDPATH; + else + throw ConfigurationException + (config_file_line_number, + "`" + mode + "' is not a valid state space generation mode"); +} + +/* ========================================================================= */ +void Configuration::readTranslatorTimeout(const string& value) +/* ---------------------------------------------------------------------------- + * + * Description: Reads a time specification from a string into + * `this->global_options.translator_timeout'. + * + * Argument: value -- A time specification in the format expected by + * ::StringUtil::parseTime. + * + * Returns: Nothing; the result is stored into + * `this->global_options.translator_timeout'. The function + * throws a ConfigurationException if `value' is not a valid + * time specification. + * + * ------------------------------------------------------------------------- */ +{ + unsigned long int hours, minutes, seconds; + try + { + ::StringUtil::parseTime(value, hours, minutes, seconds); + } + catch (const Exception&) + { + throw ConfigurationException + (config_file_line_number, + "`" + value + "' is not a valid time specification"); + } + global_options.translator_timeout = (hours * 60 + minutes) * 60 + seconds; } /* ========================================================================= */ diff --git a/lbtt/src/Configuration.h b/lbtt/src/Configuration.h index bdfeda739..8d491eb8f 100644 --- a/lbtt/src/Configuration.h +++ b/lbtt/src/Configuration.h @@ -21,6 +21,7 @@ #define CONFIGURATION_H #include +#include #include #include #include @@ -35,8 +36,6 @@ using namespace std; - - /****************************************************************************** * * A class for storing program configuration information. @@ -69,15 +68,23 @@ public: * string. */ + bool isInternalAlgorithm(unsigned long int id) /* Tests whether a given */ + const; /* algorithm identifier + * refers to lbtt's + * internal model + * checking algorithm. + */ + static void showCommandLineHelp /* Prints the list of */ (const char* program_name); /* command line options. */ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - enum InteractionMode {NEVER, ALWAYS, ONERROR}; /* Enumeration constants - * affecting the behaviour - * of the program as - * regards user control. + enum InteractionMode {NEVER, ALWAYS, ONERROR, /* Enumeration constants */ + ONBREAK}; /* affecting the + * behavior of the + * program as regards + * user control. */ enum FormulaMode {NORMAL, NNF}; /* Enumeration constants @@ -109,19 +116,23 @@ public: * parameters). */ { - string* name; /* Name of the algorithm. + string name; /* Name of the algorithm. */ - string* path_to_program; /* Path to the executable + char** parameters; /* Command-line parameters * required for running - * the algorithm. - */ - - string* extra_parameters; /* Additional command-line - * parameters required for - * running the executable. + * the executable. See + * the documentation for + * the registerAlgorithm + * function (in + * Configuration.cc) for + * more information. */ + vector::size_type num_parameters; /* Number of command-line + * parameters. + */ + bool enabled; /* Determines whether the * algorithm is enabled * (whether it will be used @@ -169,6 +180,12 @@ public: * commands. */ + bool handle_breaks; /* If true, pause testing + * also on interrupt + * signals instead of + * simply aborting. + */ + unsigned long int number_of_rounds; /* Number of test rounds. */ @@ -278,6 +295,10 @@ public: * formula generation * algorithms. */ + + unsigned int translator_timeout; /* Timeout (in seconds) for + * translators. + */ }; struct FormulaConfiguration /* A structure for storing @@ -360,6 +381,12 @@ public: * the tests. */ + map, /* Mapping between */ + ALLOC(unsigned long int) > /* algorithm names and */ + algorithm_names; /* their numeric + * identifiers. + */ + GlobalConfiguration global_options; /* General configuration * information. */ @@ -376,15 +403,6 @@ public: * algorithms. */ - typedef pair IntPair; - - set, ALLOC(IntPair) > /* Configuration options */ - locked_options; /* the values of which - * should not be - * initialized from the - * configuration file. - */ - /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ class ConfigurationException : public Exception /* A class for reporting @@ -419,6 +437,9 @@ public: /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +private: + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + struct IntegerRange /* Data structure for * representing integer- * valued ranges of certain @@ -426,11 +447,11 @@ public: * options. */ { - long int min; /* Lower bound. */ + unsigned long int min; /* Lower bound. */ - long int max; /* Upper bound. */ + unsigned long int max; /* Upper bound. */ - char* error_message; /* Error message to be + const char* error_message; /* Error message to be * displayed if the value * is not within the * specified range. @@ -439,31 +460,30 @@ public: /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - /* - * Ranges for certain integer-valued configuration options. - */ + /* Ranges for certain integer-valued configuration options. */ static const struct IntegerRange - VERBOSITY_RANGE, ROUND_COUNT_RANGE, GENERATION_RANGE, PRIORITY_RANGE, - PROPOSITION_COUNT_RANGE, FORMULA_SIZE_RANGE, FORMULA_MAX_SIZE_RANGE, - STATESPACE_SIZE_RANGE, STATESPACE_MAX_SIZE_RANGE; + DEFAULT_RANGE, VERBOSITY_RANGE, + ROUND_COUNT_RANGE, RANDOM_SEED_RANGE, + ATOMIC_PRIORITY_RANGE, OPERATOR_PRIORITY_RANGE; -private: - enum CommandLineOptionType /* Command line options. */ - {OPT_COMPARISONTEST = 10000, OPT_CONFIGFILE, + /* Command line options. */ + + enum CommandLineOptionType + {OPT_HELP = 'h', OPT_VERSION = 'V', + + OPT_COMPARISONTEST = 10000, OPT_CONFIGFILE, OPT_CONSISTENCYTEST, OPT_DISABLE, OPT_ENABLE, - OPT_FORMULACHANGEINTERVAL, OPT_FORMULAFILE, - OPT_FORMULARANDOMSEED, OPT_HELP = 'h', - OPT_GLOBALPRODUCT = 20000, OPT_INTERACTIVE, + OPT_FORMULACHANGEINTERVAL, + OPT_FORMULAFILE, OPT_FORMULARANDOMSEED, + OPT_GLOBALPRODUCT, OPT_INTERACTIVE, OPT_INTERSECTIONTEST, OPT_LOGFILE, - OPT_MODELCHECK, OPT_NOCOMPARISONTEST, - OPT_NOCONSISTENCYTEST, OPT_NOINTERSECTIONTEST, - OPT_NOPAUSE, OPT_PAUSE, OPT_PAUSEONERROR, - OPT_PROFILE, OPT_QUIET, OPT_ROUNDS, - OPT_SHOWCONFIG, OPT_SHOWOPERATORDISTRIBUTION, - OPT_SKIP, OPT_STATESPACECHANGEINTERVAL, - OPT_STATESPACERANDOMSEED, OPT_VERBOSITY, - OPT_VERSION, + OPT_MODELCHECK, OPT_PROFILE, OPT_QUIET, + OPT_ROUNDS, OPT_SHOWCONFIG, + OPT_SHOWOPERATORDISTRIBUTION, OPT_SKIP, + OPT_STATESPACECHANGEINTERVAL, + OPT_STATESPACERANDOMSEED, + OPT_TRANSLATORTIMEOUT, OPT_VERBOSITY, OPT_LOCALPRODUCT, @@ -476,7 +496,6 @@ private: OPT_FORMULAPROPOSITIONS, OPT_FORMULASIZE, OPT_GENERATENNF, OPT_GLOBALLYPRIORITY, OPT_IMPLICATIONPRIORITY, OPT_NEXTPRIORITY, - OPT_NOABBREVIATEDOPERATORS, OPT_NOGENERATENNF, OPT_NOOUTPUTNNF, OPT_NOTPRIORITY, OPT_ORPRIORITY, OPT_OUTPUTNNF, OPT_PROPOSITIONPRIORITY, @@ -503,6 +522,8 @@ private: /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + friend int yyparse(); + Configuration(const Configuration& cfg); /* Prevent copying and */ Configuration& operator= /* assignment of */ (const Configuration& cfg); /* Configuration @@ -514,10 +535,59 @@ private: * to default values. */ - long int parseCommandLineInteger /* Converts an integer */ - (const string& option, const string& value) /* to a string with */ - const; /* some additional - * validity checks. + void registerAlgorithm /* Adds a new algorithm */ + (const string& name, const string& path, /* to the configuration. */ + const string& parameters, bool enabled, + const int block_begin_line); + + template /* Reads an integer, */ + void readInteger /* checks that it is */ + (T& target, const string& value, /* within given bounds */ + const IntegerRange& range = DEFAULT_RANGE); /* and stores it into + * an unsigned integer + * type variable. + */ + + void readProbability /* Reads a probability */ + (double& target, const string& value); /* and stores it into + * a given variable of + * type double. + */ + + void readSize(int valtype, const string& value); /* Initializes formula or + * state space size + * ranges from a given + * string. + */ + + void readTruthValue /* Interprets a symbolic */ + (bool& target, const string& value); /* truth value. */ + + void readInteractivity(const string& value); /* Interprets a symbolic + * interactivity mode. + */ + + void readProductType(const string& value); /* Interprets a symbolic + * model checking mode. + */ + + void readFormulaMode /* Interprets a symbolic */ + (FormulaMode& target, const string& mode); /* formula mode. + */ + + void readStateSpaceMode(const string& mode); /* Initializes + * `global_options. + * statespace_generation + * _mode' from a + * symbolic mode + * identifier. + */ + + void readTranslatorTimeout(const string& value); /* Initializes + * `global_options. + * translator_timeout' + * from a symbolic time + * specification. */ double operatorProbability /* Computes the */ @@ -532,6 +602,97 @@ private: +/****************************************************************************** + * + * Declarations for functions and variables provided by the parser. + * + *****************************************************************************/ + +extern int config_file_line_number; /* Number of the current + * line in the + * configuration file. + */ + +extern int parseConfiguration /* Parser interface. */ + (FILE*, Configuration&); + + + +/****************************************************************************** + * + * Inline function definitions for class Configuration. + * + *****************************************************************************/ + +/* ========================================================================= */ +inline bool Configuration::isInternalAlgorithm(unsigned long int id) const +/* ---------------------------------------------------------------------------- + * + * Description: Tests whether a given algorithm identifier refers to lbtt's + * internal model checking algorithm. + * + * Argument: id -- Identifier to test. + * + * Returns: True if `id' is the identifier of lbtt's internal model + * checking algorithm. + * + * ------------------------------------------------------------------------- */ +{ + return ((global_options.statespace_generation_mode & Configuration::PATH) + && id == algorithms.size() - 1); +} + + + +/****************************************************************************** + * + * Template function definitions for class Configuration. + * + *****************************************************************************/ + +/* ========================================================================= */ +template +void Configuration::readInteger + (T& target, const string& value, const IntegerRange& range) +/* ---------------------------------------------------------------------------- + * + * Description: Reads an integer and stores it into `target'. + * + * Arguments: target -- A reference to an unsigned integer type variable + * for storing the result. + * value -- The integer as a string. + * range -- A reference to a constant IntegerRange object + * giving the bounds for the value. + * + * Returns: Nothing; the result is stored into `target'. The function + * throws a ConfigurationException if `value' is not a valid + * integer within the bounds specified by `range'. + * + * ------------------------------------------------------------------------- */ +{ + string error; + unsigned long int val; + + try + { + val = ::StringUtil::parseNumber(value); + } + catch (const ::StringUtil::NotANumberException&) + { + error = "`" + value + "' is not a valid nonnegative integer"; + } + + if (error.empty() && (val < range.min || val > range.max)) + error = range.error_message; + + if (!error.empty()) + throw ConfigurationException(config_file_line_number, error); + + target = val; +} + + + /****************************************************************************** * * Inline function definitions for class Configuration::ConfigurationException. diff --git a/lbtt/src/DispUtil.h b/lbtt/src/DispUtil.h index ce3e08141..a7a8bc2f5 100644 --- a/lbtt/src/DispUtil.h +++ b/lbtt/src/DispUtil.h @@ -58,6 +58,16 @@ void printTextBlock /* Writes an indented */ * a stream. */ + bool printText /* "Verbosity-aware" */ + (const char* text, /* functions for writing */ + const int verbosity_threshold, /* text to standard */ + const int indent); /* output. */ + + bool printText + (const string& text, + const int verbosity_threshold, + const int indent); + /****************************************************************************** @@ -78,16 +88,6 @@ struct StreamFormatting * e.g. the justification * of output. */ - - bool printText /* "Verbosity-aware" */ - (const char* text, /* functions for writing */ - const int verbosity_threshold, /* text to standard */ - const int indent); /* output. */ - - bool printText - (const string& text, - const int verbosity_threshold, - const int indent); }; diff --git a/lbtt/src/Exception.h b/lbtt/src/Exception.h index a429704b7..e6a88d98b 100644 --- a/lbtt/src/Exception.h +++ b/lbtt/src/Exception.h @@ -23,6 +23,7 @@ #include #include #include +#include #include using namespace std; @@ -299,6 +300,16 @@ public: template /* Operator for reading */ Exceptional_istream &operator>>(T &t); /* from the stream. */ + Exceptional_istream& get(istream::char_type& C); /* Reads a character from + * the stream. + */ + + Exceptional_istream& read /* Reads a given number */ + (istream::char_type* buffer, streamsize count); /* of characters from + * the stream into a + * buffer. + */ + operator istream&(); /* Casts the exception- * aware input stream into * a regular input stream. @@ -992,6 +1003,47 @@ inline Exceptional_istream::~Exceptional_istream() { } +/* ========================================================================= */ +inline Exceptional_istream& Exceptional_istream::get(istream::char_type& c) +/* ---------------------------------------------------------------------------- + * + * Description: Reads a character from the input stream. + * + * Argument: c -- A reference to a character to extract. + * + * Returns: A reference to the stream. + * + * ------------------------------------------------------------------------- */ +{ + stream->get(c); + if (stream->rdstate() & exception_mask) + throw IOException("error reading from stream"); + + return *this; +} + +/* ========================================================================= */ +inline Exceptional_istream& Exceptional_istream::read + (istream::char_type* buffer, streamsize count) +/* ---------------------------------------------------------------------------- + * + * Description: Reads a given number of characters from the stream into a + * buffer. + * + * Arguments: buffer -- A pointer to the buffer. + * count -- Number of characters to read. + * + * Returns: A reference to the stream. + * + * ------------------------------------------------------------------------- */ +{ + stream->read(buffer, count); + if (stream->rdstate() & exception_mask) + throw IOException("error reading from stream"); + + return *this; +} + /* ========================================================================= */ inline Exceptional_istream::operator istream&() /* ---------------------------------------------------------------------------- diff --git a/lbtt/src/ExternalTranslator.cc b/lbtt/src/ExternalTranslator.cc index adda19ea1..5f8da60ad 100644 --- a/lbtt/src/ExternalTranslator.cc +++ b/lbtt/src/ExternalTranslator.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #ifdef HAVE_FCNTL_H @@ -62,25 +63,30 @@ ExternalTranslator::~ExternalTranslator() } /* ========================================================================= */ -ExternalTranslator::TempFileObject& -ExternalTranslator::registerTempFileObject - (const string& filename, TempFileObject::Type type) +const char* ExternalTranslator::registerTempFileObject + (const string& filename, const TempFsysName::NameType type, + const bool literal) /* ---------------------------------------------------------------------------- * * Description: Registers a temporary file or directory such that it will be * automatically deleted when the ExternalTranslator object is * destroyed. * - * Arguments: filename -- Name of the temporary file or directory. + * Arguments: filename -- Name of the temporary file or directory. If + * empty, a new name will be created. * type -- Type of the object (TempFileObject::FILE or * TempFileObject::DIRECTORY). + * literal -- Tells whether the file name should be + * interpreted literally. * - * Returns: A reference to the file object. + * Returns: Nothing. * * ------------------------------------------------------------------------- */ { - temporary_file_objects.push(new TempFileObject(filename, type)); - return *temporary_file_objects.top(); + TempFsysName* name = new TempFsysName; + name->allocate(filename.c_str(), type, literal); + temporary_file_objects.push(name); + return name->get(); } /* ========================================================================= */ @@ -99,19 +105,19 @@ void ExternalTranslator::translate * * ------------------------------------------------------------------------- */ { - TempFileObject& external_program_input_file = registerTempFileObject(); + const char* external_program_input_file + = registerTempFileObject("lbtt-translate"); - TempFileObject& external_program_output_file = registerTempFileObject(); + const char* external_program_output_file + = registerTempFileObject("lbtt-translate"); string translated_formula; translateFormula(formula, translated_formula); ofstream input_file; - input_file.open(external_program_input_file.getName().c_str(), - ios::out | ios::trunc); + input_file.open(external_program_input_file, ios::out | ios::trunc); if (!input_file.good()) - throw FileCreationException(string("`") - + external_program_input_file.getName() + throw FileCreationException(string("`") + external_program_input_file + "'"); Exceptional_ostream einput_file(&input_file, ios::failbit | ios::badbit); @@ -121,96 +127,11 @@ void ExternalTranslator::translate input_file.close(); string command_line = string(command_line_arguments[2]) - + commandLine(external_program_input_file.getName(), - external_program_output_file.getName()); + + commandLine(external_program_input_file, + external_program_output_file); if (!execSuccess(system(command_line.c_str()))) throw ExecFailedException(command_line_arguments[2]); - parseAutomaton(external_program_output_file.getName(), filename); -} - - - -/****************************************************************************** - * - * Function definitions for class ExternalTranslator::TempFileObject. - * - *****************************************************************************/ - -/* ========================================================================= */ -ExternalTranslator::TempFileObject::TempFileObject - (const string& filename, Type t) -/* ---------------------------------------------------------------------------- - * - * Description: Constructor for class TempFileObject. Creates a temporary - * file or a directory and tests whether it can really be - * written to (if not, a FileCreationException is thrown). - * - * Arguments: filename -- Name of the temporary file or directory. - * If the filename is an empty string, the - * filename is obtained by a call to tmpnam(3). - * t -- Type of the object (TempFileObject::FILE or - * TempFileObject::DIRECTORY). - * - * Returns: Nothing. - * - * ------------------------------------------------------------------------- */ -{ - if (filename.empty()) - { - char tempname[L_tmpnam + 1]; - - if (tmpnam(tempname) == 0) - throw FileCreationException("a temporary file"); - - name = tempname; - } - else - name = filename; - - if (t == FILE) - { - ofstream tempfile; - tempfile.open(name.c_str(), ios::out | ios::trunc); - if (!tempfile.good()) - throw FileCreationException("a temporary file"); - tempfile.close(); - } - else - { - if (mkdir(name.c_str(), 0700) != 0) - throw FileCreationException("a temporary directory"); - } - - type = t; -} - -/* ========================================================================= */ -ExternalTranslator::TempFileObject::~TempFileObject() -/* ---------------------------------------------------------------------------- - * - * Description: Destructor for class TempFileObject. Deletes the file or - * the directory associated with the object (displays a warning - * if this fails). - * - * Arguments: None. - * - * Returns: Nothing. - * - * ------------------------------------------------------------------------- */ -{ - if (remove(name.c_str()) != 0) - { - string msg("error removing temporary "); - - if (type == TempFileObject::FILE) - msg += "file"; - else - msg += "directory"; - - msg += " `" + name + "'"; - - printWarning(msg); - } + parseAutomaton(external_program_output_file, filename); } diff --git a/lbtt/src/ExternalTranslator.h b/lbtt/src/ExternalTranslator.h index 609617591..12171674b 100644 --- a/lbtt/src/ExternalTranslator.h +++ b/lbtt/src/ExternalTranslator.h @@ -33,6 +33,7 @@ #include "Exception.h" #include "LtlFormula.h" #include "translate.h" +#include "TempFsysName.h" #include "TranslatorInterface.h" /****************************************************************************** @@ -98,13 +99,21 @@ * from the names of the input/output files. Each of these files should be * "registered" before calling the external program with the function * - * void registerTempFileObject - * (const string& filename, TempFileObject::Type t) + * const char* registerTempFileObject + * (const string& filename, const TempFsysName::NameType t, + * const bool literal) * - * where `filename' is the full name of the temporary file and `t' is a type - * of the object (TempFileObject::FILE or TempFileObject::DIRECTORY). + * where `filename' is the prefix of a temporary file name, `t' is a type + * of the object (TempFsysName::FILE or TempFsysName::DIRECTORY), and + * `literal' specifies whether `filename' should be interpreted literally or + * not (if not, `filename' will be treated as a suggestion for the name + * of the temporary file). If the name is to be interpreted literally, + * `filename' should contain the full path name of the temporary file to be + * created. In all cases, the function returns the full path name of the + * temporary file or directory, or it throws an IOException (defined in + * Exception.h) if the creation fails. * - * All files or directories registered using this function will then be + * All files or directories registered using this function will be * automatically deleted after the translation is finished or aborted. * The files or directories will be deleted in the reverse order of * registration, i.e., the most recently registered file/directory will be @@ -115,55 +124,15 @@ class ExternalTranslator : public TranslatorInterface { public: - /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - - class TempFileObject /* A class for storing */ - { /* information about */ - public: /* temporary files and - * directories. - */ - - enum Type {FILE, DIRECTORY}; /* Types for a temporary - * file object. - */ - - TempFileObject /* Constructor. */ - (const string& filename = "", Type t = FILE); - - ~TempFileObject(); /* Destructor. */ - - const string& getName() const; /* Returns the filename - * associated with the - * object. - */ - - Type getType() const; /* Returns the type of - * the object. - */ - - private: - string name; /* Name of the file object. - */ - - Type type; /* Type of the file object. - */ - - TempFileObject(const TempFileObject&); /* Prevent copying and */ - TempFileObject& operator= /* assignment of */ - (const TempFileObject&); /* TempFileObjects. */ - }; - - /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - ExternalTranslator(); /* Constructor. */ ~ExternalTranslator(); /* Destructor. */ - TempFileObject& registerTempFileObject /* Registers a temporary */ + const char* registerTempFileObject /* Registers a temporary */ (const string& filename = "", /* file or directory */ - TempFileObject::Type /* such that it will be */ - t = TempFileObject::FILE); /* automatically deleted - * when the translation + const TempFsysName::NameType /* such that it will be */ + t = TempFsysName::FILE, /* automatically deleted */ + const bool literal = false); /* when the translation * is complete. */ @@ -205,17 +174,12 @@ private: * objects. */ - stack > /* information. */ + stack > /* information. */ temporary_file_objects; - friend class KecWrapper; /* Friend declarations. */ - friend class Ltl2AutWrapper; - friend class Ltl2BaWrapper; - friend class ProdWrapper; - friend class SpinWrapper; - friend class WringWrapper; + friend class SpinWrapper; /* Friend declarations. */ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ @@ -316,46 +280,6 @@ inline bool ExternalTranslator::execSuccess(int exitcode) -/****************************************************************************** - * - * Inline function definitions for class ExternalTranslator::TempFileObject. - * - *****************************************************************************/ - -/* ========================================================================= */ -inline const string& ExternalTranslator::TempFileObject::getName() const -/* ---------------------------------------------------------------------------- - * - * Description: Returns the name associated with the - * ExternalTranslator::TempFileObject. - * - * Arguments: None. - * - * Returns: The name associated with the object. - * - * ------------------------------------------------------------------------- */ -{ - return name; -} - -/* ========================================================================= */ -inline ExternalTranslator::TempFileObject::Type -ExternalTranslator::TempFileObject::getType() const -/* ---------------------------------------------------------------------------- - * - * Description: Returns the type of the ExternalTranslator::TempFileObject. - * - * Arguments: None. - * - * Returns: The type associated with the object. - * - * ------------------------------------------------------------------------- */ -{ - return type; -} - - - /****************************************************************************** * * Inline function definitions for class diff --git a/lbtt/src/FormulaRandomizer.h b/lbtt/src/FormulaRandomizer.h index a0391d499..cf40472db 100644 --- a/lbtt/src/FormulaRandomizer.h +++ b/lbtt/src/FormulaRandomizer.h @@ -21,6 +21,7 @@ #define FORMULARANDOMIZER_H #include +#include #include #include #include "LbttAlloc.h" diff --git a/lbtt/src/FormulaWriter.h b/lbtt/src/FormulaWriter.h index 9eba9f868..34b3c2c8b 100644 --- a/lbtt/src/FormulaWriter.h +++ b/lbtt/src/FormulaWriter.h @@ -21,13 +21,11 @@ #define FORMULAWRITER_H #include "Exception.h" +#include "LtlFormula.h" namespace Ltl { -class LtlFormula; -class Atom; - /****************************************************************************** * * A function template class for writing the formula to a stream. diff --git a/lbtt/src/Graph.h.in b/lbtt/src/Graph.h.in index 29c8e52e6..9b7c4a6b7 100644 --- a/lbtt/src/Graph.h.in +++ b/lbtt/src/Graph.h.in @@ -186,6 +186,10 @@ public: * graph nodes. */ + class PathElement; /* A class for representing + * (node, edge) pairs + */ + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ protected: @@ -205,6 +209,14 @@ public: * the graph nodes. */ + typedef EdgeContainer EdgeContainerType; /* Type definition for + * containers of graph + * edges. + */ + + typedef deque /* Type definition for */ + Path; /* paths in a graph. */ + typedef pair StateIdPair; /* Type definition for a * pair of state * identifiers in a graph. @@ -323,8 +335,8 @@ public: /* default assignment operator */ - Graph::size_type targetNode() /* Returns the index of */ - const; /* the target node of + size_type targetNode() const; /* Returns the index of */ + /* the target node of * the directed edge. */ @@ -353,7 +365,7 @@ protected: */ private: - Graph::size_type target_node; /* Identifier of the edge's + size_type target_node; /* Identifier of the edge's * target node. */ }; @@ -404,6 +416,62 @@ protected: +/****************************************************************************** + * + * A template class for representing (node identifier, edge) pairs in a graph. + * + *****************************************************************************/ + +template +class Graph::PathElement +{ +public: + explicit PathElement /* Constructors. */ + (const typename Graph::size_type + n, + const typename Graph::Edge* + e = 0); + + PathElement + (const typename Graph::size_type + n, + const typename Graph::Edge& e); + + /* default copy constructor */ + + ~PathElement(); /* Destructor. */ + + /* default assignment operator */ + + size_type node() const; /* Returns the identifier + * of the node associated + * with the path element. + */ + + bool hasEdge() const; /* Tells whether there is + * an edge associated with + * the path element. + */ + + const Edge& edge() const; /* Returns the edge + * associated with the + * path element. + */ + +private: + typename Graph::size_type node_id; /* Identifier of the node + * associated with the path + * element. + */ + + const typename Graph::Edge* /* Pointer to the edge */ + edge_pointer; /* associated with the + * path element. + */ +}; + + + /****************************************************************************** * * An exception class for reporting errors when indexing graph nodes. @@ -924,7 +992,7 @@ Graph::stats() const * * ------------------------------------------------------------------------- */ { - pair::size_type, unsigned long int> result; + pair result; result.first = nodes.size(); result.second = 0; @@ -1468,6 +1536,128 @@ void Graph::Node::print +/****************************************************************************** + * + * Inline function definitions for template class + * Graph::PathElement. + * + *****************************************************************************/ + +/* ========================================================================= */ +template +inline Graph::PathElement::PathElement + (const typename Graph::size_type n, + const typename Graph::Edge* e) : + node_id(n), edge_pointer(e) +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class Graph::PathElement. + * Creates a (node identifier, edge) pair from a node identifier + * and a pointer to an edge. + * + * Arguments: n -- Numeric identifier of a graph node. + * e -- A constant pointer to a graph edge. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline Graph::PathElement::PathElement + (const typename Graph::size_type n, + const typename Graph::Edge& e) : + node_id(n), edge_pointer(&e) +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class Graph::PathElement. + * Creates a (node identifier, edge) pair from a node identifier + * and an edge. + * + * Arguments: n -- Numeric identifier of a graph node. + * e -- A constant reference to a graph edge. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline Graph::PathElement::~PathElement() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class Graph::PathElement. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline typename Graph::size_type +Graph::PathElement::node() const +/* ---------------------------------------------------------------------------- + * + * Description: Returns the identifier of the node associated with a + * Graph::PathElement object. + * + * Arguments: None. + * + * Returns: Identifier of the node associated with the object. + * + * ------------------------------------------------------------------------- */ +{ + return node_id; +} + +/* ========================================================================= */ +template +inline bool Graph::PathElement::hasEdge() const +/* ---------------------------------------------------------------------------- + * + * Description: Tells whether there is an edge associated with a + * Graph::PathElement object. + * + * Arguments: None. + * + * Returns: true iff there is an edge associated with the object. + * + * ------------------------------------------------------------------------- */ +{ + return (edge != 0); +} + +/* ========================================================================= */ +template +inline const typename Graph::Edge& +Graph::PathElement::edge() const +/* ---------------------------------------------------------------------------- + * + * Description: Returns the edge associated with a + * Graph::PathElement object. The function + * assumes that there is such an edge; it is an error to call + * this function for a PathElement object `element' for which + * `element.hasEdge() == false'. + * + * Arguments: None. + * + * Returns: The edge associated with the object. + * + * ------------------------------------------------------------------------- */ +{ + return *edge_pointer; +} + + + /****************************************************************************** * * Inline function definitions for class NodeIndexException. diff --git a/lbtt/src/IntervalList.cc b/lbtt/src/IntervalList.cc new file mode 100644 index 000000000..52a5ebb35 --- /dev/null +++ b/lbtt/src/IntervalList.cc @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2004 + * Heikki Tauriainen + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "IntervalList.h" +#include "StringUtil.h" + +/****************************************************************************** + * + * Function definitions for class IntervalList. + * + *****************************************************************************/ + +/* ========================================================================= */ +void IntervalList::merge(unsigned long int min, unsigned long int max) +/* ---------------------------------------------------------------------------- + * + * Description: Merges a new interval with a list of intervals. + * + * Arguments: min, max -- Upper and lower bound of the new interval. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + if (min > max) + return; + + list::iterator interval; + for (interval = intervals.begin(); + interval != intervals.end() && interval->second + 1 < min; + ++interval) + ; + + if (interval == intervals.end()) + { + intervals.insert(interval, make_pair(min, max)); + return; + } + + if (interval->first <= min && max <= interval->second) + return; + + if (max + 1 < interval->first) + { + intervals.insert(interval, make_pair(min, max)); + return; + } + + if (min < interval->first) + interval->first = min; + + if (interval->second < max) + { + interval->second = max; + list::iterator interval2 = interval; + ++interval2; + while (interval2 != intervals.end() + && interval2->first <= interval->second + 1) + { + if (interval->second < interval2->second) + interval->second = interval2->second; + list::iterator interval_to_erase = interval2; + ++interval2; + intervals.erase(interval_to_erase); + } + } +} + +/* ========================================================================= */ +void IntervalList::remove(unsigned long int min, unsigned long int max) +/* ---------------------------------------------------------------------------- + * + * Description: Removes a closed interval from an interval list. + * + * Arguments: min, max -- Bounds for the interval to remove. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + if (min > max) + return; + + list::iterator interval; + for (interval = intervals.begin(); + interval != intervals.end() && interval->second < min; + ++interval) + ; + + while (interval != intervals.end()) + { + if (max < interval->first) /* min <= max < imin <= imax */ + return; + + if (interval->first < min) + { + if (max < interval->second) /* imin < min <= max < imax */ + { + intervals.insert(interval, make_pair(interval->first, min - 1)); + interval->first = max + 1; + return; + } + interval->second = min - 1; /* imin < min <= imax <= max */ + ++interval; + } + else if (max < interval->second) /* min <= imin <= max < imax */ + { + interval->first = max + 1; + return; + } + else /* min <= imin <= imax <= max */ + { + list::iterator interval_to_erase = interval; + ++interval; + intervals.erase(interval_to_erase); + } + } +} + +/* ========================================================================= */ +bool IntervalList::covers(unsigned long int min, unsigned long int max) const +/* ---------------------------------------------------------------------------- + * + * Description: Test whether an interval list covers a given interval. + * + * Arguments: min, max -- Upper and lower bound for the interval to test. + * + * Returns: True if the IntervalList covers the given interval. + * + * ------------------------------------------------------------------------- */ +{ + if (min > max) + return true; /* empty interval is always covered */ + + list::const_iterator interval; + for (interval = intervals.begin(); + interval != intervals.end() && min > interval->second; + ++interval) + ; + + if (interval == intervals.end()) + return false; + + return (min >= interval->first && max <= interval->second); +} + +/* ========================================================================= */ +string IntervalList::toString() const +/* ---------------------------------------------------------------------------- + * + * Description: Converts the interval list to a string. + * + * Arguments: None. + * + * Returns: A string listing the intervals in the interval list. + * + * ------------------------------------------------------------------------- */ +{ + string s; + for (list::const_iterator + interval = intervals.begin(); + interval != intervals.end(); + ++interval) + { + if (interval != intervals.begin()) + s += ','; + s += StringUtil::toString(interval->first) + "..." + + StringUtil::toString(interval->second); + } + return s; +} diff --git a/lbtt/src/IntervalList.h b/lbtt/src/IntervalList.h new file mode 100644 index 000000000..2a55b9641 --- /dev/null +++ b/lbtt/src/IntervalList.h @@ -0,0 +1,499 @@ +/* + * Copyright (C) 2004 + * Heikki Tauriainen + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef INTERVALLIST_H +#define INTERVALLIST_H + +#include +#include +#include +#include +#include "LbttAlloc.h" + +using namespace std; + +/****************************************************************************** + * + * The IntervalList class represents a list of disjoint closed intervals + * formed from pairs of unsigned long integers. The class supports merging + * a new interval with a list of intervals, removing an interval from a list + * of intervals and checking whether the interval list covers a given element + * (or a given interval). The elements of the intervals can also be accessed + * in increasing order via IntervalList::const_iterator. + * + *****************************************************************************/ + +class IntervalList +{ +private: + typedef pair + Interval; + +public: + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + class const_iterator /* A class for iterating */ + { /* over the elements of */ + /* an IntervalList. */ + public: + const_iterator(); /* Constructor. */ + + /* default copy constructor */ + + ~const_iterator(); /* Destructor. */ + + /* default assignment operator */ + + bool operator==(const const_iterator& it) /* Comparison operators. */ + const; + + bool operator!=(const const_iterator& it) + const; + + unsigned long int operator*() const; /* Dereference operator. */ + + unsigned long int operator++(); /* Prefix increment. */ + + unsigned long int operator++(int); /* Postfix increment. */ + + private: + const list* /* The interval list */ + interval_list; /* associated with the */ + /* iterator. */ + + list /* An iterator pointing */ + ::const_iterator interval; /* at the current */ + /* interval list. */ + + unsigned long int element; /* Element currently + * pointed to by the + * iterator. + */ + + friend class IntervalList; + }; + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + IntervalList(); /* Constructor. */ + + ~IntervalList(); /* Destructor. */ + + /* default copy constructor */ + + /* default assignment operator */ + + void merge(unsigned long int element); /* Merges a point interval + * with the list of + * intervals. + */ + + void merge /* Merges a new interval */ + (unsigned long int min, unsigned long int max); /* with the list of + * intervals. + */ + + void remove(unsigned long int element); /* Removes an element from + * the list of intervals. + */ + + void remove /* Removes an interval */ + (unsigned long int min, unsigned long int max); /* from the list of + * intervals. + */ + + bool covers(unsigned long int element) const; /* Tests whether the + * interval list covers an + * element. + */ + + bool covers /* Tests whether the */ + (unsigned long int min, unsigned long int max) /* interval list covers */ + const; /* an interval. */ + + const_iterator begin() const; /* Returns an iterator to + * the beginning of the + * interval list. + */ + + const_iterator end() const; /* Returns an iterator to + * the end of the interval + * list. + */ + + typedef const_iterator iterator; /* The interval list + * cannot be modified with + * iterators. + */ + + typedef list /* Size type. */ + ::size_type size_type; + + size_type size() const; /* Tell the number of + * disjoint intervals in + * the interval list. + */ + + size_type max_size() const; /* Tell the maximum + * allowable number of + * disjoint intervals in + * the interval list. + */ + + bool empty() const; /* Tell whether the + * interval list is empty. + */ + + void clear(); /* Makes the interval list + * empty. + */ + + string toString() const; /* Converts the interval + * list to a string. + */ + +private: + list intervals; /* List of intervals. */ + + friend class const_iterator; +}; + + + +/****************************************************************************** + * + * Inline function definitions for class IntervalList. + * + *****************************************************************************/ + +/* ========================================================================= */ +inline IntervalList::IntervalList() +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class IntervalList. Creates an empty list of + * intervals. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +inline IntervalList::~IntervalList() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class IntervalList. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +inline void IntervalList::merge(unsigned long int element) +/* ---------------------------------------------------------------------------- + * + * Description: Merges an element (a point interval) with an IntervalList. + * + * Arguments: element -- Element to merge. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + merge(element, element); +} + +/* ========================================================================= */ +inline bool IntervalList::covers(unsigned long int element) const +/* ---------------------------------------------------------------------------- + * + * Description: Tests whether an interval list covers an element. + * + * Arguments: element -- Element to test. + * + * Returns: True if the IntervalList covers the element. + * + * ------------------------------------------------------------------------- */ +{ + return covers(element, element); +} + +/* ========================================================================= */ +inline IntervalList::size_type IntervalList::size() const +/* ---------------------------------------------------------------------------- + * + * Description: Tells the number of disjoint intervals in an IntervalList. + * + * Arguments: None. + * + * Returns: Number of disjoint intervals in the IntervalList. + * + * ------------------------------------------------------------------------- */ +{ + return intervals.size(); +} + +/* ========================================================================= */ +inline IntervalList::size_type IntervalList::max_size() const +/* ---------------------------------------------------------------------------- + * + * Description: Tells the maximum allowable number of disjoint intervals in + * an IntervalList. + * + * Arguments: None. + * + * Returns: Maximum allowable number of disjoint intervals in the + * IntervalList. + * + * ------------------------------------------------------------------------- */ +{ + return intervals.max_size(); +} + +/* ========================================================================= */ +inline bool IntervalList::empty() const +/* ---------------------------------------------------------------------------- + * + * Description: Tells whether an IntervalList is empty. + * + * Arguments: None. + * + * Returns: True if the IntervalList is empty. + * + * ------------------------------------------------------------------------- */ +{ + return intervals.empty(); +} + +/* ========================================================================= */ +inline void IntervalList::clear() +/* ---------------------------------------------------------------------------- + * + * Description: Makes an IntervalList empty. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + intervals.clear(); +} + +/* ========================================================================= */ +inline IntervalList::const_iterator IntervalList::begin() const +/* ---------------------------------------------------------------------------- + * + * Description: Returns an IntervalList::const_iterator pointing to the + * beginning of an IntervalList. + * + * Arguments: None. + * + * Returns: An IntervalList::const_iterator pointing to the beginning of + * the IntervalList. + * + * ------------------------------------------------------------------------- */ +{ + const_iterator it; + it.interval_list = &this->intervals; + it.interval = intervals.begin(); + if (it.interval != intervals.end()) + it.element = it.interval->first; + else + it.element = 0; + return it; +} + +/* ========================================================================= */ +inline IntervalList::const_iterator IntervalList::end() const +/* ---------------------------------------------------------------------------- + * + * Description: Returns an IntervalList::const_iterator pointing to the end + * of an IntervalList. + * + * Arguments: None. + * + * Returns: An IntervalList::const_iterator pointing to the end of the + * IntervalList. + * + * ------------------------------------------------------------------------- */ +{ + const_iterator it; + it.interval_list = &this->intervals; + it.interval = intervals.end(); + it.element = 0; + return it; +} + + + +/****************************************************************************** + * + * Inline function definitions for class IntervalList::const_iterator. + * + *****************************************************************************/ + +/* ========================================================================= */ +inline IntervalList::const_iterator::const_iterator() +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class IntervalList::const_iterator. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +inline IntervalList::const_iterator::~const_iterator() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class IntervalList::const_iterator. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +inline bool IntervalList::const_iterator::operator== + (const const_iterator& it) const +/* ---------------------------------------------------------------------------- + * + * Description: Equality test for two IntervalList::const_iterators. Two + * IntervalList::const_iterators are equal if and only if they + * point to the same interval of an interval list and the same + * element in the interval. + * + * Argument: it -- A constant reference to another + * IntervalList::const_iterator. + * + * Returns: Result of the equality test (a truth value). + * + * ------------------------------------------------------------------------- */ +{ + return (interval_list == it.interval_list && interval == it.interval + && element == it.element); +} + +/* ========================================================================= */ +inline bool IntervalList::const_iterator::operator!= + (const const_iterator& it) const +/* ---------------------------------------------------------------------------- + * + * Description: Inequality test for two IntervalList::const_iterators. Two + * IntervalList::const_iterators are not equal if and only if + * they point to different intervals of an interval list or to + * different elements of the same interval in the list. + * + * Argument: it -- A constant reference to another + * IntervalList::const_iterator. + * + * Returns: Result of the inequality test (a truth value). + * + * ------------------------------------------------------------------------- */ +{ + return (interval_list != it.interval_list || interval != it.interval + || element != it.element); +} + +/* ========================================================================= */ +inline unsigned long int IntervalList::const_iterator::operator*() const +/* ---------------------------------------------------------------------------- + * + * Description: Dereferencing operator for IntervalList::const_iterator. + * + * Arguments: None. + * + * Returns: The element currently pointed to by the iterator. + * + * ------------------------------------------------------------------------- */ +{ + return element; +} + +/* ========================================================================= */ +inline unsigned long int IntervalList::const_iterator::operator++() +/* ---------------------------------------------------------------------------- + * + * Description: Prefix increment operator for IntervalList::const_iterator. + * + * Arguments: None. + * + * Returns: The element following the "current" element in the interval + * list. + * + * ------------------------------------------------------------------------- */ +{ + if (element == interval->second) + { + ++interval; + if (interval != interval_list->end()) + element = interval->first; + else + element = 0; + } + else + ++element; + + return element; +} + +/* ========================================================================= */ +inline unsigned long int IntervalList::const_iterator::operator++(int) +/* ---------------------------------------------------------------------------- + * + * Description: Postfix increment operator for IntervalList::const_iterator. + * + * Arguments: None. + * + * Returns: The "current" element in the interval list. + * + * ------------------------------------------------------------------------- */ +{ + unsigned long int current_element = element; + if (element == interval->second) + { + ++interval; + if (interval != interval_list->end()) + element = interval->first; + else + element = 0; + } + else + ++element; + + return current_element; +} + +#endif /* INTERVALLIST_H */ diff --git a/lbtt/src/LbttAlloc.h b/lbtt/src/LbttAlloc.h index b4019d922..2f8ef8cc3 100644 --- a/lbtt/src/LbttAlloc.h +++ b/lbtt/src/LbttAlloc.h @@ -22,11 +22,7 @@ #include -#ifdef HAVE_SINGLE_CLIENT_ALLOC -#define ALLOC(typename) single_client_alloc -#else #define ALLOC(typename) allocator -#endif /* HAVE_SINGLE_CLIENT_ALLOC */ #ifdef HAVE_OBSTACK_H diff --git a/lbtt/src/Ltl-parse.yy b/lbtt/src/Ltl-parse.yy new file mode 100644 index 000000000..d558db0b7 --- /dev/null +++ b/lbtt/src/Ltl-parse.yy @@ -0,0 +1,610 @@ +/* + * Copyright (C) 2004 + * Heikki Tauriainen + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +%{ +#include +#include +#include +#include +#include +#include +#include "Exception.h" +#include "LbttAlloc.h" +#include "LtlFormula.h" + +using namespace Ltl; + +/****************************************************************************** + * + * Variables and functions used for parsing an LTL formula. + * + *****************************************************************************/ + +static Exceptional_istream* estream; /* Pointer to input stream. + */ + +static LtlFormula* result; /* This variable stores the + * result after a call to + * ltl_parse. + */ + +static std::set, /* Intermediate results. */ + ALLOC(LtlFormula*) > /* (This set is used */ + intermediate_results; /* for keeping track of + * the subformulas of a + * partially constructed + * formula in case the + * memory allocated for + * the subformulas needs + * to be freed because + * of a parse error.) + */ + +static int ltl_lex(); /* The lexical scanner. */ + + + +/****************************************************************************** + * + * Function for reporting parse errors. + * + *****************************************************************************/ + +static void ltl_error(const char*) +{ + throw LtlFormula::ParseErrorException("error parsing LTL formula"); +} + + + +/****************************************************************************** + * + * Function for updating the set of intermediate results. + * + *****************************************************************************/ + +inline LtlFormula* newFormula(LtlFormula& f) +{ + intermediate_results.insert(&f); + return &f; +} + +%} + + + +%name-prefix="ltl_" + + + +/****************************************************************************** + * + * Declarations for terminal and nonterminal symbols used in the grammar rules + * below. + * + *****************************************************************************/ + +%union { + class LtlFormula* formula; +} + +/* Uninterpreted symbols. */ + +%token LTLPARSE_LPAR LTLPARSE_RPAR LTLPARSE_FALSE LTLPARSE_TRUE + LTLPARSE_UNKNOWN + +/* Atomic propositions. */ + +%token LTLPARSE_ATOM + +/* Operators. */ + +%nonassoc LTLPARSE_UNTIL LTLPARSE_RELEASE LTLPARSE_WEAK_UNTIL + LTLPARSE_STRONG_RELEASE LTLPARSE_BEFORE +%left LTLPARSE_IMPLY LTLPARSE_EQUIV LTLPARSE_XOR +%left LTLPARSE_OR +%left LTLPARSE_AND +%nonassoc LTLPARSE_NOT LTLPARSE_NEXT LTLPARSE_FINALLY LTLPARSE_GLOBALLY +%nonassoc LTLPARSE_EQUALS + +/* Compound formulas. */ + +%type formula atomic_formula unary_formula prefix_op_formula + binary_formula prefix_b_formula infix_b_formula + + + +/****************************************************************************** + * + * Grammar rule definitions. + * + *****************************************************************************/ + +%% + +ltl_formula: formula + { result = $1; } + ; + +formula: atomic_formula + { $$ = $1; } + + | unary_formula + { $$ = $1; } + + | binary_formula + { $$ = $1; } + + | LTLPARSE_LPAR formula LTLPARSE_RPAR + { $$ = $2; } + ; + +atomic_formula: LTLPARSE_ATOM + { $$ = $1; } + + | LTLPARSE_ATOM LTLPARSE_EQUALS LTLPARSE_FALSE + { + intermediate_results.erase($1); + $$ = newFormula(Not::construct($1)); + } + + | LTLPARSE_ATOM LTLPARSE_EQUALS LTLPARSE_TRUE + { $$ = $1; } + + | LTLPARSE_FALSE + { $$ = newFormula(False::construct()); } + + | LTLPARSE_TRUE + { $$ = newFormula(True::construct()); } + ; + +unary_formula: LTLPARSE_NOT formula + { + intermediate_results.erase($2); + $$ = newFormula(Not::construct($2)); + } + + | LTLPARSE_NEXT formula + { + intermediate_results.erase($2); + $$ = newFormula(Next::construct($2)); + } + + | LTLPARSE_FINALLY formula + { + intermediate_results.erase($2); + $$ = newFormula(Finally::construct($2)); + } + + | LTLPARSE_GLOBALLY formula + { + intermediate_results.erase($2); + $$ = newFormula(Globally::construct($2)); + } + ; + +prefix_op_formula: atomic_formula + { $$ = $1; } + + | unary_formula + { $$ = $1; } + + | prefix_b_formula + { $$ = $1; } + + | LTLPARSE_LPAR formula LTLPARSE_RPAR + { $$ = $2; } + ; + +binary_formula: prefix_b_formula + { $$ = $1; } + | infix_b_formula + { $$ = $1; } + ; + +prefix_b_formula: LTLPARSE_AND prefix_op_formula prefix_op_formula + { + intermediate_results.erase($2); + intermediate_results.erase($3); + $$ = newFormula(And::construct($2, $3)); + } + + | LTLPARSE_OR prefix_op_formula prefix_op_formula + { + intermediate_results.erase($2); + intermediate_results.erase($3); + $$ = newFormula(Or::construct($2, $3)); + } + + | LTLPARSE_IMPLY prefix_op_formula prefix_op_formula + { + intermediate_results.erase($2); + intermediate_results.erase($3); + $$ = newFormula(Imply::construct($2, $3)); + } + + | LTLPARSE_EQUIV prefix_op_formula prefix_op_formula + { + intermediate_results.erase($2); + intermediate_results.erase($3); + $$ = newFormula(Equiv::construct($2, $3)); + } + + | LTLPARSE_XOR prefix_op_formula prefix_op_formula + { + intermediate_results.erase($2); + intermediate_results.erase($3); + $$ = newFormula(Xor::construct($2, $3)); + } + + | LTLPARSE_UNTIL prefix_op_formula prefix_op_formula + { + intermediate_results.erase($2); + intermediate_results.erase($3); + $$ = newFormula(Until::construct($2, $3)); + } + + | LTLPARSE_RELEASE prefix_op_formula prefix_op_formula + { + intermediate_results.erase($2); + intermediate_results.erase($3); + $$ = newFormula(V::construct($2, $3)); + } + + | LTLPARSE_WEAK_UNTIL prefix_op_formula prefix_op_formula + { + intermediate_results.erase($2); + intermediate_results.erase($3); + $$ = newFormula(WeakUntil::construct($2, $3)); + } + + | LTLPARSE_STRONG_RELEASE prefix_op_formula + prefix_op_formula + { + intermediate_results.erase($2); + intermediate_results.erase($3); + $$ = newFormula(StrongRelease::construct($2, $3)); + } + + | LTLPARSE_BEFORE prefix_op_formula prefix_op_formula + { + intermediate_results.erase($2); + intermediate_results.erase($3); + $$ = newFormula(Before::construct($2, $3)); + } + ; + +infix_b_formula: formula LTLPARSE_AND formula + { + intermediate_results.erase($1); + intermediate_results.erase($3); + $$ = newFormula(And::construct($1, $3)); + } + + | formula LTLPARSE_OR formula + { + intermediate_results.erase($1); + intermediate_results.erase($3); + $$ = newFormula(Or::construct($1, $3)); + } + + | formula LTLPARSE_IMPLY formula + { + intermediate_results.erase($1); + intermediate_results.erase($3); + $$ = newFormula(Imply::construct($1, $3)); + } + + | formula LTLPARSE_EQUIV formula + { + intermediate_results.erase($1); + intermediate_results.erase($3); + $$ = newFormula(Equiv::construct($1, $3)); + } + + | formula LTLPARSE_XOR formula + { + intermediate_results.erase($1); + intermediate_results.erase($3); + $$ = newFormula(Xor::construct($1, $3)); + } + + | formula LTLPARSE_UNTIL formula + { + intermediate_results.erase($1); + intermediate_results.erase($3); + $$ = newFormula(Until::construct($1, $3)); + } + + | formula LTLPARSE_RELEASE formula + { + intermediate_results.erase($1); + intermediate_results.erase($3); + $$ = newFormula(V::construct($1, $3)); + } + + | formula LTLPARSE_WEAK_UNTIL formula + { + intermediate_results.erase($1); + intermediate_results.erase($3); + $$ = newFormula(WeakUntil::construct($1, $3)); + } + + | formula LTLPARSE_STRONG_RELEASE formula + { + intermediate_results.erase($1); + intermediate_results.erase($3); + $$ = newFormula(StrongRelease::construct($1, $3)); + } + + | formula LTLPARSE_BEFORE formula + { + intermediate_results.erase($1); + intermediate_results.erase($3); + $$ = newFormula(Before::construct($1, $3)); + } + ; + +%% + + + +/****************************************************************************** + * + * Helper function for reading lexical tokens from a stream. + * + *****************************************************************************/ + +static inline size_t matchCharactersFromStream + (istream& stream, char* chars) +{ + size_t num_matched; + for (num_matched = 0; *chars != '\0' && stream.peek() == *chars; ++chars) + { + stream.ignore(1); + ++num_matched; + } + return num_matched; +} + + +/****************************************************************************** + * + * Main interface to the parser. + * + *****************************************************************************/ + +namespace Ltl +{ + +/* ========================================================================= */ +LtlFormula* parseFormula(istream& stream) +/* ---------------------------------------------------------------------------- + * + * Description: Parses an LTL formula from a stream. The formula should be + * in one of the formats used by the tools lbtt 1.0.x (both + * prefix and infix form), Spin/Temporal Massage Parlor/LTL2BA, + * LTL2AUT or Wring 1.1.0 (actually, the grammar is basically + * a combination of the grammars of the above tools with the + * exception that propositions should always be written in the + * form `pN' for some integer N; in principle, it is possible to + * use a mixed syntax for the formula). The input should be + * terminated with a newline. + * + * Argument: stream -- A reference to the input stream. + * + * Returns: A pointer to the formula. The function throws an + * LtlFormula::ParseErrorException if the syntax is incorrect, + * or an IOException in case of an end-of-file or another I/O + * error. + * + * ------------------------------------------------------------------------- */ +{ + Exceptional_istream es(&stream, ios::badbit | ios::failbit | ios::eofbit); + estream = &es; + intermediate_results.clear(); + + try + { + ltl_parse(); + } + catch (...) + { + for (std::set, ALLOC(LtlFormula*) > + ::const_iterator f = intermediate_results.begin(); + f != intermediate_results.end(); + ++f) + LtlFormula::destruct(*f); + throw; + } + return result; +} + +} + + + +/****************************************************************************** + * + * The lexical scanner. + * + *****************************************************************************/ + +static int ltl_lex() +{ + char c; + std::istream& stream = static_cast(*estream); + + do + { + estream->get(c); + } + while (isspace(c) && c != '\n'); + + switch (c) + { + case '\n' : return 0; + + case '(' : + return (matchCharactersFromStream(stream, ")") == 1 + ? LTLPARSE_NEXT + : LTLPARSE_LPAR); + + case ')' : return LTLPARSE_RPAR; + + case 'f' : + switch (matchCharactersFromStream(stream, "alse")) + { + case 0 : case 4 : + return LTLPARSE_FALSE; + default: + break; + } + return LTLPARSE_UNKNOWN; + + case '0' : return LTLPARSE_FALSE; + + case 't' : + switch (matchCharactersFromStream(stream, "rue")) + { + case 0 : case 3 : + return LTLPARSE_TRUE; + default : + return LTLPARSE_UNKNOWN; + } + + case 'T' : + return (matchCharactersFromStream(stream, "RUE") == 3 + ? LTLPARSE_TRUE + : LTLPARSE_UNKNOWN); + + case '1' : return LTLPARSE_TRUE; + + case '!' : case '~' : return LTLPARSE_NOT; + + case '&' : + matchCharactersFromStream(stream, "&"); + return LTLPARSE_AND; + + case '/' : + return (matchCharactersFromStream(stream, "\\") == 1 + ? LTLPARSE_AND + : LTLPARSE_UNKNOWN); + + case '*' : return LTLPARSE_AND; + + case '|' : + matchCharactersFromStream(stream, "|"); + return LTLPARSE_OR; + + case '\\' : + return (matchCharactersFromStream(stream, "/") == 1 + ? LTLPARSE_OR + : LTLPARSE_UNKNOWN); + + case '+' : return LTLPARSE_OR; + + case '=' : + return (matchCharactersFromStream(stream, ">") == 1 + ? LTLPARSE_IMPLY + : LTLPARSE_EQUALS); + + case '-' : + return (matchCharactersFromStream(stream, ">") == 1 + ? LTLPARSE_IMPLY + : LTLPARSE_UNKNOWN); + + case 'i' : return LTLPARSE_IMPLY; + + case '<' : + if (matchCharactersFromStream(stream, ">") == 1) + return LTLPARSE_FINALLY; + return (matchCharactersFromStream(stream, "->") == 2 + || matchCharactersFromStream(stream, "=>") == 2 + ? LTLPARSE_EQUIV + : LTLPARSE_UNKNOWN); + + case 'e' : return LTLPARSE_EQUIV; + + case 'x' : + return (matchCharactersFromStream(stream, "or") == 2 + ? LTLPARSE_XOR + : LTLPARSE_UNKNOWN); + + case '^' : return LTLPARSE_XOR; + + case 'X' : return LTLPARSE_NEXT; + + case 'U' : return LTLPARSE_UNTIL; + + case 'V' : case 'R' : return LTLPARSE_RELEASE; + + case 'W' : return LTLPARSE_WEAK_UNTIL; + + case 'M' : return LTLPARSE_STRONG_RELEASE; + + case 'B' : return LTLPARSE_BEFORE; + + case 'F' : + switch (matchCharactersFromStream(stream, "ALSE")) + { + case 0 : + return LTLPARSE_FINALLY; + case 4 : + return LTLPARSE_FALSE; + default : + return LTLPARSE_UNKNOWN; + } + + case '[' : + return (matchCharactersFromStream(stream, "]") == 1 + ? LTLPARSE_GLOBALLY + : LTLPARSE_UNKNOWN); + + case 'G' : return LTLPARSE_GLOBALLY; + + case 'p' : + { + long int id = 0; + bool id_ok = false; + int ch = stream.peek(); + while (ch >= '0' && ch <= '9') + { + id_ok = true; + estream->get(c); + if (LONG_MAX / 10 < id) + throw LtlFormula::ParseErrorException + ("error parsing LTL formula (proposition identifier out of " + "range)"); + id *= 10; + id += (c - '0'); + ch = stream.peek(); + } + + if (id_ok) + { + ltl_lval.formula = newFormula(Atom::construct(id)); + return LTLPARSE_ATOM; + } + return LTLPARSE_UNKNOWN; + } + + default : return LTLPARSE_UNKNOWN; + } +} diff --git a/lbtt/src/LtlFormula.cc b/lbtt/src/LtlFormula.cc index 7c4034722..2fa3d4723 100644 --- a/lbtt/src/LtlFormula.cc +++ b/lbtt/src/LtlFormula.cc @@ -24,9 +24,8 @@ namespace Ltl { -map +set /* LTL formulae. */ LtlFormula::formula_storage; unsigned long int /* Upper limit for the */ @@ -38,8 +37,6 @@ unsigned long int /* Upper limit for the */ * truth assignment). */ - - /****************************************************************************** * * Function for obtaining the infix symbol associated with a given @@ -875,193 +872,6 @@ Bitset LtlFormula::findPropositionalModel(long int max_atom) const return model; } -/* ========================================================================= */ -LtlFormula* LtlFormula::read(Exceptional_istream& stream) -/* ---------------------------------------------------------------------------- - * - * Description: Recursively constructs an LtlFormula by parsing input from an - * exception-aware input stream. - * - * Argument: stream -- A reference to an exception-aware input stream. - * - * Returns: The constructed LtlFormula. - * - * ------------------------------------------------------------------------- */ -{ - string token; - LtlFormula* formula; - - try - { - stream >> token; - } - catch (const IOException&) - { - if (static_cast(stream).eof()) - throw ParseErrorException("error parsing LTL formula (unexpected end of " - "input)"); - else - throw ParseErrorException("error parsing LTL formula (I/O error)"); - } - - if (token[0] == 'p') - { - if (token.length() == 1) - throw ParseErrorException("error parsing LTL formula (unrecognized " - "token: `" + token + "')"); - - long int id; - char* endptr; - - id = strtol(token.c_str() + 1, &endptr, 10); - - if (*endptr != '\0' || id < 0 || id == LONG_MIN || id == LONG_MAX) - throw ParseErrorException("error parsing LTL formula (unrecognized " - "token: `" + token + "')"); - - formula = &Atom::construct(id); - } - else - { - if (token.length() > 1) - throw ParseErrorException("error parsing LTL formula (unrecognized " - "token: `" + token + "')"); - - switch (token[0]) - { - case LTL_TRUE : - formula = &True::construct(); - break; - - case LTL_FALSE : - formula = &False::construct(); - break; - - case LTL_NEGATION : - case LTL_NEXT : - case LTL_FINALLY : - case LTL_GLOBALLY : - { - LtlFormula* g = read(stream); - - try - { - switch (token[0]) - { - case LTL_NEGATION : - formula = &Not::construct(g); - break; - - case LTL_NEXT : - formula = &Next::construct(g); - break; - - case LTL_FINALLY : - formula = &Finally::construct(g); - break; - - default : /* LTL_GLOBALLY */ - formula = &Globally::construct(g); - break; - } - } - catch (...) - { - LtlFormula::destruct(g); - throw; - } - - break; - } - - case LTL_CONJUNCTION : - case LTL_DISJUNCTION : - case LTL_IMPLICATION : - case LTL_EQUIVALENCE : - case LTL_XOR : - case LTL_UNTIL : - case LTL_V : - case LTL_WEAK_UNTIL : - case LTL_STRONG_RELEASE : - case LTL_BEFORE : - { - LtlFormula* g = read(stream); - LtlFormula* h; - - try - { - h = read(stream); - } - catch (...) - { - LtlFormula::destruct(g); - throw; - } - - try - { - switch (token[0]) - { - case LTL_CONJUNCTION : - formula = &And::construct(g, h); - break; - - case LTL_DISJUNCTION : - formula = &Or::construct(g, h); - break; - - case LTL_IMPLICATION : - formula = &Imply::construct(g, h); - break; - - case LTL_EQUIVALENCE : - formula = &Equiv::construct(g, h); - break; - - case LTL_XOR : - formula = &Xor::construct(g, h); - break; - - case LTL_UNTIL : - formula = &Until::construct(g, h); - break; - - case LTL_V : - formula = &V::construct(g, h); - break; - - case LTL_WEAK_UNTIL : - formula = &WeakUntil::construct(g, h); - break; - - case LTL_STRONG_RELEASE : - formula = &StrongRelease::construct(g, h); - break; - - default : /* LTL_BEFORE */ - formula = &Before::construct(g, h); - break; - } - } - catch (...) - { - LtlFormula::destruct(g); - LtlFormula::destruct(h); - throw; - } - - break; - } - - default : - throw ParseErrorException("error parsing LTL formula (unrecognized " - "token: `" + token + "')"); - } - } - - return formula; -} - /* ========================================================================= */ void LtlFormula::print(Exceptional_ostream& estream, OutputMode mode) const /* ---------------------------------------------------------------------------- diff --git a/lbtt/src/LtlFormula.h b/lbtt/src/LtlFormula.h index c5b2efea7..a9209be7c 100644 --- a/lbtt/src/LtlFormula.h +++ b/lbtt/src/LtlFormula.h @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include #include "LbttAlloc.h" @@ -188,13 +188,6 @@ public: * input stream. */ - static LtlFormula* read /* Constructs an */ - (Exceptional_istream& stream); /* LtlFormula by parsing - * input from an - * exception-aware input - * stream. - */ - void print /* Writes the formula to */ (ostream& stream = cout, /* an output stream. */ OutputMode mode = LTL_INFIX) const; @@ -250,6 +243,10 @@ protected: unsigned int is_constant : 1; /* operators and atomic */ } info_flags; /* propositions. */ + unsigned long int refcount; /* Number of references to + * `this' LtlFormula. + */ + static LtlFormula& /* Updates the shared */ insertToStorage(LtlFormula* f); /* formula storage with * a new formula. @@ -288,8 +285,8 @@ private: * formula. */ - static map /* LTL formulae. */ + static set /* LTL formulae. */ formula_storage; static unsigned long int /* Upper limit for the */ @@ -330,6 +327,16 @@ private: +/****************************************************************************** + * + * Interface to the formula parser. + * + *****************************************************************************/ + +extern LtlFormula* parseFormula(istream& stream); + + + /****************************************************************************** * * A class for atomic propositions. @@ -474,7 +481,7 @@ private: * to satisfy the * LtlFormula member * function interface. - */ + */ }; @@ -1076,7 +1083,7 @@ typedef BinaryFormula Before; *****************************************************************************/ /* ========================================================================= */ -inline LtlFormula::LtlFormula() +inline LtlFormula::LtlFormula() : refcount(1) /* ---------------------------------------------------------------------------- * * Description: Constructor for class LtlFormula. Initializes the attributes @@ -1088,8 +1095,6 @@ inline LtlFormula::LtlFormula() * * --------------------------------------------------------------------------*/ { - info_flags.is_propositional = 0; - info_flags.is_constant = 0; } /* ========================================================================= */ @@ -1122,14 +1127,9 @@ inline void LtlFormula::destruct(LtlFormula* f) * * ------------------------------------------------------------------------- */ { - map::iterator - deleter; - - deleter = formula_storage.find(f); - if (--deleter->second == 0) + if (--f->refcount == 0) { - formula_storage.erase(deleter); + formula_storage.erase(f); delete f; } } @@ -1147,7 +1147,7 @@ inline LtlFormula* LtlFormula::clone() * * ------------------------------------------------------------------------- */ { - formula_storage.find(this)->second++; + ++refcount; return this; } @@ -1241,8 +1241,7 @@ inline LtlFormula* LtlFormula::read(istream& stream) * * ------------------------------------------------------------------------- */ { - Exceptional_istream estream(&stream, ios::badbit | ios::failbit); - return read(estream); + return parseFormula(stream); } /* ========================================================================= */ @@ -1303,8 +1302,6 @@ inline Exceptional_ostream& operator<< return stream; } - - /* ========================================================================= */ inline LtlFormula& LtlFormula::insertToStorage(LtlFormula* f) /* ---------------------------------------------------------------------------- @@ -1317,19 +1314,16 @@ inline LtlFormula& LtlFormula::insertToStorage(LtlFormula* f) * * ------------------------------------------------------------------------- */ { - map::iterator - inserter; - - inserter = formula_storage.find(f); + set::iterator inserter + = formula_storage.find(f); if (inserter != formula_storage.end()) { delete f; - inserter->second++; - return *(inserter->first); + ++(*inserter)->refcount; + return **inserter; } - formula_storage.insert(make_pair(f, 1)); + formula_storage.insert(f); return *f; } @@ -1571,8 +1565,7 @@ inline Atom& Atom::construct(long int a) } /* ========================================================================= */ -inline Atom::Atom(long int a) : - LtlFormula(), atom(a) +inline Atom::Atom(long int a) : atom(a) /* ---------------------------------------------------------------------------- * * Description: Constructor for class Atom. Creates a new propositional atom. @@ -1886,8 +1879,7 @@ inline UnaryFormula& UnaryFormula::construct(LtlFormula& f) /* ========================================================================= */ template -inline UnaryFormula::UnaryFormula(LtlFormula* f) : - LtlFormula(), subformula(f) +inline UnaryFormula::UnaryFormula(LtlFormula* f) : subformula(f) /* ---------------------------------------------------------------------------- * * Description: Constructs an LTL formula with a unary operator. @@ -2189,7 +2181,7 @@ BinaryFormula::construct(LtlFormula& f1, LtlFormula* f2) /* ========================================================================= */ template inline BinaryFormula::BinaryFormula(LtlFormula* f1, LtlFormula* f2) : - LtlFormula(), subformula1(f1), subformula2(f2) + subformula1(f1), subformula2(f2) /* ---------------------------------------------------------------------------- * * Description: Constructs a binary LTL formula. diff --git a/lbtt/src/Makefile.am b/lbtt/src/Makefile.am index 76f3a8992..6ca8f0e46 100644 --- a/lbtt/src/Makefile.am +++ b/lbtt/src/Makefile.am @@ -1,22 +1,90 @@ +BUILT_SOURCES = Config-parse.h NeverClaim-parse.h +AM_YFLAGS = -d + bin_PROGRAMS = lbtt lbtt-translate -lbtt_SOURCES = BitArray.h Bitset.h BitArray.cc BuchiAutomaton.h \ -BuchiAutomaton.cc Config-parse.yy Config-lex.ll Configuration.h \ -Configuration.cc DispUtil.h DispUtil.cc EdgeContainer.h Exception.h \ -FormulaRandomizer.h FormulaRandomizer.cc FormulaWriter.h LbttAlloc.h \ -LtlFormula.h LtlFormula.cc main.cc PathEvaluator.h PathEvaluator.cc \ -PathIterator.h PathIterator.cc ProductAutomaton.h ProductAutomaton.cc \ -Random.h SccIterator.h SharedTestData.h StatDisplay.h StatDisplay.cc \ -StateSpace.h StateSpace.cc StateSpaceRandomizer.h StateSpaceRandomizer.cc \ -StringUtil.h StringUtil.cc TestOperations.h TestOperations.cc TestRoundInfo.h \ -TestStatistics.h TestStatistics.cc UserCommandReader.h UserCommandReader.cc \ -UserCommands.h UserCommands.cc -EXTRA_lbtt_SOURCES = gnu-getopt.h Config-parse.h +lbtt_SOURCES = \ + BitArray.h \ + Bitset.h \ + BitArray.cc \ + BuchiAutomaton.h \ + BuchiAutomaton.cc \ + BuchiProduct.h \ + BuchiProduct.cc \ + Config-parse.yy \ + Config-lex.ll \ + Configuration.h \ + Configuration.cc \ + DispUtil.h \ + DispUtil.cc \ + EdgeContainer.h \ + Exception.h \ + FormulaRandomizer.h \ + FormulaRandomizer.cc \ + FormulaWriter.h \ + IntervalList.h \ + IntervalList.cc \ + LbttAlloc.h \ + LtlFormula.h \ + LtlFormula.cc \ + Ltl-parse.yy \ + main.cc \ + PathEvaluator.h \ + PathEvaluator.cc \ + PathIterator.h \ + PathIterator.cc \ + Product.h \ + Random.h \ + SccCollection.h \ + SharedTestData.h \ + StatDisplay.h \ + StatDisplay.cc \ + StateSpace.h \ + StateSpace.cc \ + StateSpaceProduct.h \ + StateSpaceRandomizer.h \ + StateSpaceRandomizer.cc \ + StringUtil.h \ + StringUtil.cc \ + TempFsysName.h \ + TempFsysName.cc \ + TestOperations.h \ + TestOperations.cc \ + TestRoundInfo.h \ + TestStatistics.h \ + TestStatistics.cc \ + UserCommandReader.h \ + UserCommandReader.cc \ + UserCommands.h \ + UserCommands.cc +EXTRA_lbtt_SOURCES = gnu-getopt.h lbtt_LDADD = @LIBOBJS@ @READLINELIBS@ -lbtt_translate_SOURCES = BitArray.h BitArray.cc Exception.h \ -ExternalTranslator.h ExternalTranslator.cc FormulaWriter.h LbttAlloc.h \ -LbtWrapper.h LtlFormula.h LtlFormula.cc NeverClaim-parse.yy NeverClaim-lex.ll \ -NeverClaimAutomaton.h NeverClaimAutomaton.cc SpinWrapper.h SpinWrapper.cc \ -StringUtil.h StringUtil.cc translate.h translate.cc TranslatorInterface.h -EXTRA_lbtt_translate_SOURCES = gnu-getopt.h NeverClaim-parse.h +lbtt_translate_SOURCES = \ + BitArray.h \ + BitArray.cc \ + Exception.h \ + ExternalTranslator.h \ + ExternalTranslator.cc \ + FormulaWriter.h \ + IntervalList.h \ + IntervalList.cc \ + LbttAlloc.h \ + LbtWrapper.h \ + LtlFormula.h \ + LtlFormula.cc \ + Ltl-parse.yy \ + NeverClaim-parse.yy \ + NeverClaim-lex.ll \ + NeverClaimAutomaton.h \ + NeverClaimAutomaton.cc \ + SpinWrapper.h \ + SpinWrapper.cc \ + StringUtil.h \ + StringUtil.cc \ + TempFsysName.h \ + TempFsysName.cc \ + translate.h \ + translate.cc \ + TranslatorInterface.h +EXTRA_lbtt_translate_SOURCES = gnu-getopt.h lbtt_translate_LDADD = @LIBOBJS@ diff --git a/lbtt/src/NeverClaim-lex.ll b/lbtt/src/NeverClaim-lex.ll index ba60fc1bf..e59a56ea6 100644 --- a/lbtt/src/NeverClaim-lex.ll +++ b/lbtt/src/NeverClaim-lex.ll @@ -30,6 +30,7 @@ extern int current_neverclaim_line_number; %option never-interactive %option noyywrap +%option nounput %% diff --git a/lbtt/src/PathEvaluator.cc b/lbtt/src/PathEvaluator.cc index 583a2490b..936d1ff98 100644 --- a/lbtt/src/PathEvaluator.cc +++ b/lbtt/src/PathEvaluator.cc @@ -61,26 +61,25 @@ void PathEvaluator::reset() /* ========================================================================= */ bool PathEvaluator::evaluate - (const LtlFormula& formula, const StateSpace& statespace, - const vector& - states_on_path, - StateSpace::size_type loop_state) + (const LtlFormula& formula, const StateSpace::Path& prefix, + const StateSpace::Path& cycle, const StateSpace& statespace) /* ---------------------------------------------------------------------------- * - * Description: Evaluates an LTL formula in a state space in which the states - * are connected into a non-branching sequence that ends in a - * loop. + * Description: Evaluates an LTL formula in a path formed from a prefix and + * an infinitely repeating cycle of states in a state space. * - * Arguments: formula -- Formula to be evaluated. - * statespace -- State space from which the path is - * extracted. - * states_on_path -- Mapping between states in the path and - * the states in `statespace' such that - * `statespace[states_on_path[i]]' - * corresponds to the ith state of the path. - * loop_state -- Number of the state in the path to which - * the ``last'' state of the path is - * connected. + * Arguments: formula -- Formula to be evaluated. + * prefix -- A StateSpace::Path object corresponding to + * the prefix of the path. Only the state + * identifiers in the path elements are used; + * the function will not require `prefix' to + * actually represent a path in `statespace'. + * cycle -- A StateSpace::Path object corresponding to + * the infinitely repeating cycle. Only the + * state identifiers in the path elements are + * relevant. + * statespace -- State space to which the state identifiers in + * `path' and `cycle' refer. * * Returns: `true' if and only if the LTL formula holds in the path. * @@ -88,13 +87,21 @@ bool PathEvaluator::evaluate { reset(); - if (states_on_path.empty() || loop_state >= states_on_path.size()) + if (cycle.empty()) return false; current_formula = &formula; current_path = &statespace; - current_loop_state = loop_state; - path_states = states_on_path; + current_loop_state = prefix.size(); + path_states.reserve(prefix.size() + cycle.size()); + for (StateSpace::Path::const_iterator state = prefix.begin(); + state != prefix.end(); + ++state) + path_states.push_back(state->node()); + for (StateSpace::Path::const_iterator state = cycle.begin(); + state != cycle.end(); + ++state) + path_states.push_back(state->node()); return eval(); } diff --git a/lbtt/src/PathEvaluator.h b/lbtt/src/PathEvaluator.h index 7b7a178ef..794e7ea59 100644 --- a/lbtt/src/PathEvaluator.h +++ b/lbtt/src/PathEvaluator.h @@ -54,11 +54,11 @@ public: bool evaluate /* Tests whether an */ (const LtlFormula& formula, /* LtlFormula holds in a */ - const StateSpace& statespace, /* state space. */ - const vector& - states_on_path, - StateSpace::size_type loop_state); + const StateSpace::Path& prefix, /* path described by a */ + const StateSpace::Path& cycle, /* prefix and a cycle of */ + const StateSpace& statespace); /* states from a state + * space. + */ bool evaluate /* Same as above. */ (const LtlFormula& formula, diff --git a/lbtt/src/Product.h b/lbtt/src/Product.h new file mode 100644 index 000000000..f7b49a677 --- /dev/null +++ b/lbtt/src/Product.h @@ -0,0 +1,2936 @@ +/* + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 + * Heikki Tauriainen + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PRODUCT_H +#define PRODUCT_H + +#include +#include +#include +#include +#include +#include "LbttAlloc.h" +#include "BitArray.h" +#include "EdgeContainer.h" +#include "Exception.h" +#include "Graph.h" +#include "SccCollection.h" + +using namespace std; + +extern bool user_break; + +namespace Graph +{ + +/****************************************************************************** + * + * A template class for representing the product of two + * Graph objects (which, in lbtt, are always either two + * BuchiAutomaton objects, or a BuchiAutomaton and a StateSpace). + * + * The class provides functions for checking the products of these objects for + * emptiness (i.e., for two Büchi automata, whether the intersection of their + * languages is (non)empty; for a Büchi automaton and a state space, whether + * some infinite path in the state space is accepted by the automaton). The + * functions are as follows: + * + * * bool localEmptinessCheck + * (Graph::size_type, + * Graph::size_type) + * Checks whether the subproduct rooted at a product state + * determined by a pair of component state identifiers is not + * empty and returns true if this is the case. + * + * * pair::size_type, unsigned long int> + * globalEmptinessCheck + * (Graph::size_type state, Bitset&, + * unsigned long int emptiness_check_size) + * Checks a set of subproducts for emptiness and stores the + * results in a bit set that should have room for (at least) + * `emptiness_check_size' bits (this number is assumed to be less + * than the number of states in the second component of the + * product). The first parameter `state' identifies a state in + * the first component of the product. After the call, the i'th + * bit (for all 0 <= i < emptiness_check_size) in the bit set will + * then be 1 iff the subproduct rooted at the product state + * determined by the pair of state identifiers (state, i) is + * nonempty. The function returns a pair of numbers corresponding + * to the number of product states and transitions generated + * during the emptiness check. + * + * * void findWitness + * (Graph::size_type, + * Graph::size_type, + * Product::Witness&) + * Checks whether the subproduct rooted at a product state + * determined by a pair of component state identifiers is not + * empty. If this is the case, the function constructs a + * certificate (a "witness") for the nonemptiness. For the + * product of two Büchi automata, the witness is an accepting + * execution from both automata on the same input; for the product + * of a Büchi automaton and a state space, the witness is a path + * in the state space that is accepted by the automaton. + * + * All of these functions construct the product "on the fly" with the help of + * operations provided by the class Operations used for instantiating the + * template. The public interface of this class must support the following + * operations: + * + * * Operations(const Graph&, + * const Graph&) + * Constructor that accepts references to the first and second + * component of the product (in this order) as parameters. + * + * * bool empty() + * A predicate which returns "true" iff either of the product + * components is (trivially) empty, i.e., iff either component has + * no states. + * + * * unsigned long int numberOfAcceptanceSets() + * Returns the number of acceptance sets associated with a state + * or a transition in the product. + * + * * const Graph::Node& firstComponent + * (Graph::size_type), + * const Graph::Node& secondComponent + * (Graph::size_type) + * Functions for accessing the states in the individual product + * components such that firstComponent(i) (secondComponent(i)) + * returns a reference to the i'th state of the first (second) + * component in the product. + * + * * void mergeAcceptanceInformation + * (const Graph::Node&, + * const Graph::Node&, + * BitArray&) + * Updates the acceptance information of a product state + * (determined by a state of the first and the second component, + * respectively) into a BitArray that is guaranteed to have room + * for at least numberOfAcceptanceSets() bits. The function + * should not clear bits in the array. + * + * * void mergeAcceptanceInformation + * (const Graph::Edge&, + * const Graph::Edge&, + * BitArray& + * Updates the acceptance information of a product transition + * (corresponding to a pair of transitions of the first and second + * product component) into a BitArray (guaranteed to have room for + * at least numberOfAcceptanceSets() bits). The function should + * not clear bits in the array. + * + * * void validateEdgeIterators + * (const Graph::Node& node_1, + * const Graph::Node& node_2, + * GraphEdgeContainer::const_iterator iterator_1&, + * GraphEdgeContainer::const_iterator iterator_2&) + * Checks whether a pair of edges determined from a pair of + * iterators corresponds to an edge starting from a given state + * (node_1, node_2) in the product. If yes, the function should + * leave the iterators intact; otherwise the iterators should be + * updated such that they point to a pair of edges corresponding + * to an edge in the product (or to `node_1.edges().end()' and + * `node_2.edges().end()' if this is not possible). + * Calling the function with the iterators initialized to + * `node_1.edges().begin()' and `node_2.edges().begin()' should + * update the iterators such that repeated calls to + * `incrementEdgeIterators' (see below) result in an enumeration + * of all product edges beginning from the product state + * (node_1, node_2). + * + * * void incrementEdgeIterators + * (const Graph::Node& node_1, + * const Graph::Node& node_2, + * GraphEdgeContainer::const_iterator iterator_1&, + * GraphEdgeContainer::const_iterator iterator_2&) + * Updates a pair of edge iterators to point to the "next" edge + * starting from a given state (node_1, node_2) in the product + * (or to (node_1.edges().end(), node_2.edges().end()) if this is + * not possible). + * + * See the files BuchiProduct.h and StateSpaceProduct.h for examples of classes + * used for instantiating the template. + * + * Given a class suitable for instantiating the Product template, a product is + * built with the constructor + * Product::Product + * (const Graph& graph_1, + * const Graph& graph_2). + * The product can be then analyzed by calling one of the emptiness checking + * functions described above. + * + * Note: All emptiness checking functions fail by throwing an exception of type + * Product::SizeException if + * `graph_1.size() * graph_2.size()' exceeds the maximum integer + * representables using Graph::size_type. The + * implementation does not support such products. + * + * Note: Operations in the Product class are not re-entrant. + * + *****************************************************************************/ + +template +class Product +{ +public: + Product /* Constructor. */ + (const Graph& g1, + const Graph& g2); + + ~Product(); /* Destructor. */ + + typedef typename Graph /* Type of product state */ + ::size_type size_type; /* identifiers. */ + + class ProductState; /* A class for accessing + * states in the product. + */ + + const ProductState operator[] /* Indexing operator. */ + (const size_type index) const; + + size_type stateId /* Constructs a product */ + (const size_type state_1, /* state identifier from */ + const size_type state_2) const; /* the identifiers of + * the state components. + */ + + const Graph::Node& /* Functions for */ + firstComponent(const size_type state) const; /* accessing the */ + const Graph::Node& /* components of a */ + secondComponent(const size_type state) const; /* product state. */ + + bool empty() const; /* Tells whether the + * product is (trivially) + * empty. + */ + + struct Witness /* Structure for */ + { /* representing witness */ + pair::Path, /* paths for */ + Graph::Path> /* the nonemptiness of */ + prefix; /* the product. */ + pair::Path, + Graph::Path> + cycle; + }; + + bool localEmptinessCheck /* Checks whether the */ + (const typename Graph /* subproduct rooted at */ + ::size_type s1_id, /* a product state */ + const typename Graph /* determined by a pair */ + ::size_type s2_id); /* of component state + * identifiers is empty. + */ + + const pair /* Checks a set of */ + globalEmptinessCheck /* subproducts for */ + (const typename Graph /* emptiness (see */ + ::size_type state_id, /* above). */ + Bitset& result, + const unsigned long int emptiness_check_size); + + void findWitness + (const size_type s1_id, const size_type s2_id, /* Checks whether the */ + Witness& witness); /* subproduct rooted at + * a product state + * determined by a pair + * of component state + * identifiers is empty. + * If this is the case, + * constructs also a + * witness for the + * nonemptiness. + */ + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + class ProductEdge; /* Classes for */ + class ProductEdgePointer; /* representing + * transitions in the + * product and + * "pointer-like" + * objects to them. + */ + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + class ProductEdgeCollection /* A class that mimics + * a container for + * transitions starting + * from a product state. + * (The container does + * not actually store the + * transitions; instead, + * it provides functions + * for constructing + * iterators that can be + * used to generate the + * transitions.) + */ + { + public: + explicit ProductEdgeCollection /* Constructor. */ + (const size_type state); + + /* default copy constructor */ + + ~ProductEdgeCollection(); /* Destructor. */ + + /* default assignment operator */ + + class const_iterator /* Iterator for generating + * the transitions starting + * from a product state. + */ + { + public: + const_iterator(); /* Default constructor. */ + + const_iterator + (const size_type state, /* Constructor. */ + const GraphEdgeContainer::const_iterator& + e1, + const GraphEdgeContainer::const_iterator& + e2); + + /* default copy constructor */ + + ~const_iterator(); /* Destructor. */ + + /* default assignment operator */ + + bool operator==(const const_iterator& it) /* Equality test between */ + const; /* iterators. */ + + bool operator!=(const const_iterator& it) /* Inequality test */ + const; /* between iterators. */ + + const ProductEdgePointer operator*() const; /* Dereferencing */ + const ProductEdge operator->() const; /* operators. */ + + const ProductEdgePointer operator++(); /* Prefix and postfix */ + const ProductEdgePointer operator++(int); /* increment operators. */ + + private: + size_type product_state; /* Product state */ + /* associated with the + * iterator. + */ + + GraphEdgeContainer::const_iterator edge_1; /* Pair of iterators */ + GraphEdgeContainer::const_iterator edge_2; /* from which product + * edges are determined. + */ + }; + + const const_iterator begin() const; /* Returns an iterator to + * the "beginning" of the + * list of transitions + * starting from the + * product state + * `this->product_state'. + */ + + const const_iterator end() const; /* Returns an iterator to + * the "end" of the list of + * transitions starting + * from the product state + * `this->product_state'. + */ + + private: + size_type product_state; /* Product state associated + * with the transition + * container. + */ + }; + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + class SizeException : public Exception /* An exception class to */ + /* be used in cases */ + /* where `size_type' */ + /* cannot hold values */ + /* large enough to */ + /* accommodate the */ + /* largest identifier */ + /* for a product state. */ + { + public: + SizeException(); /* Constructor. */ + + /* default copy constructor */ + + ~SizeException() throw(); /* Destructor. */ + + SizeException& operator= /* Assignment operator. */ + (const SizeException& e); + + /* `what' inherited from class Exception */ + }; + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + typedef ProductEdge Edge; /* Type definitions */ + typedef ProductEdgeCollection EdgeContainerType; /* required for making */ + /* Product */ + struct PathElement; /* suitable for */ + typedef deque /* instantiating the */ + Path; /* SccCollection + * template (see + * SccCollection.h). + */ + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +private: + Product(const Product&); /* Prevent copying and */ + Product& operator=(const Product&); /* assignment of Product + * objects. + */ + + class AcceptanceTracker; /* Callback operations */ + class SimpleEmptinessChecker; /* used when searching */ + class AcceptanceReachabilityTracker; /* the product for */ + class AcceptingComponentFinder; /* strongly connected + * components. + */ + + void addCycleSegment /* Helper function for */ + (pair::Path, /* constructing a */ + Graph::Path >& cycle, /* segment of the cycle */ + size_type source_state_id, Edge transition, /* in a witness for the */ + const size_type root_id, /* nonemptiness of the */ + const map, + ALLOC(PathElement) >& + predecessor) const; + + Operations operations; /* Operations for + * building the product + * on the fly. + */ + + bool too_large; /* Will be set to true + * if `size_type' cannot + * hold the maximum value + * that may be required for + * product state + * identifiers. Calling + * one of the emptiness + * checking operations on + * such a product results + * in a run-time exception. + */ + + const size_type state_id_multiplier; /* Size of the "second" + * component of the + * product. + */ + + static Product* product; /* Pointer to the "current" + * product (i.e., the + * product for which one + * of the emptiness + * checking operations was + * last called) to allow + * accessing it from + * member classes. + */ +}; + + + +/****************************************************************************** + * + * A template class for providing a Graph<>::Node-like interface to the states + * in a product (needed for accessing the transitions leaving from a state). + * + *****************************************************************************/ + +template +class Product::ProductState +{ +public: + ProductState(const size_type state); /* Constructor. */ + + /* default copy constructor */ + + ~ProductState(); /* Destructor. */ + + /* default assignment operator */ + + const EdgeContainerType& edges() const; /* Returns an object for + * generating the + * transitions starting + * from the state. + */ + +private: + EdgeContainerType outgoing_edges; /* Object for generating + * the transitions starting + * from the state. + */ +}; + + + +/****************************************************************************** + * + * A template class for providing a Graph<>::Edge-like interface to the + * transitions in a product. + * + *****************************************************************************/ + +template +class Product::ProductEdge +{ +public: + ProductEdge /* Constructor. */ + (const GraphEdgeContainer::const_iterator& e1, + const GraphEdgeContainer::const_iterator& e2); + + /* default copy constructor */ + + ~ProductEdge(); /* Destructor. */ + + /* default assignment operator */ + + const Graph::Edge& /* Functions for */ + firstComponent() const; /* accessing the */ + const Graph::Edge& /* components of a */ + secondComponent() const; /* product transition. */ + + size_type targetNode() const; /* Returns the target state + * of the transition. + */ + +private: + const Graph::Edge* edge_1; /* Components of the */ + const Graph::Edge* edge_2; /* transition. */ +}; + + + +/****************************************************************************** + * + * A template class for providing a constant pointer -like interface to + * ProductEdge objects. + * + *****************************************************************************/ + +template +class Product::ProductEdgePointer +{ +public: + ProductEdgePointer /* Constructor. */ + (const GraphEdgeContainer::const_iterator& e1, + const GraphEdgeContainer::const_iterator& e2); + + /* default copy constructor */ + + ~ProductEdgePointer(); /* Destructor. */ + + /* default assignment operator */ + + const ProductEdge& operator*() const; /* Dereferencing */ + const ProductEdge* operator->() const; /* operators. */ + +private: + ProductEdge edge; /* The product transition. + */ +}; + + + +/****************************************************************************** + * + * A template class for representing (product state, product transition) pairs + * in a path in the product. + * + *****************************************************************************/ + +template +struct Product::PathElement +{ + PathElement(const size_type s, const Edge& t); /* Constructor. */ + + /* default copy constructor */ + + ~PathElement(); /* Destructor. */ + + /* default assignment operator */ + + size_type state; /* Product state and */ + Edge transition; /* transition. */ +}; + + + +/****************************************************************************** + * + * A template class for tracking acceptance information in strongly connected + * product components by (essentially) recording the information into roots of + * the components. This is done using the method described by Couvreur in + * [J.-M. Couvreur. On-the-fly verification of linear temporal logic. + * In Proceedings of the FM'99 World Congress on Formal Methods in the + * Development of Computing Systems, Volume I, LNCS 1708, pp. 253--271. + * Springer-Verlag, 1999]. + * + *****************************************************************************/ + +template +class Product::AcceptanceTracker : + public VisitorInterface > +{ +public: + explicit AcceptanceTracker /* Constructor. */ + (const unsigned long int num_accept_sets); + + virtual ~AcceptanceTracker(); /* Destructor. */ + + /* `enter' inherited */ + + /* `backtrack' inherited */ + + /* `touch' inherited */ + + /* `leave' inherited */ + + virtual void addEdgeToComponent /* Adds the acceptance */ + (const Edge& t, const size_type scc_id); /* sets associated with + * a product transition + * to a nontrivial + * strongly connected + * component of the + * product. + */ + + virtual void addNodeToComponent /* Adds the acceptance */ + (const size_type state_id, /* sets associated with */ + const size_type scc_id); /* a product state to a */ + /* nontrivial strongly + * connected component + * of the product. + */ + + /* `beginComponent' inherited */ + + /* `insert' inherited */ + + virtual void endComponent /* Removes the */ + (const size_type scc_id); /* association between a + * nontrivial strongly + * connected product + * component and a set + * of acceptance sets + * when the component is + * not needed any + * longer. + */ + +protected: + typedef pair /* Association between */ + AcceptanceStackElement; /* a strongly connected + * component identifier + * and a collection of + * acceptance sets. + */ + + typedef deque /* the above */ + AcceptanceStack; /* associations. */ + + AcceptanceStack acceptance_stack; /* Stack for storing the + * dfs numbers of roots + * of strongly connected + * components and + * acceptance sets + * associated with them. + */ + + BitArray* acceptance_sets; /* Used for manipulating + * the stack. + */ + + const unsigned long int /* Number of acceptance */ + number_of_acceptance_sets; /* sets in the product. */ + +private: + AcceptanceTracker(const AcceptanceTracker&); /* Prevent copying and */ + AcceptanceTracker& operator= /* assignment of */ + (const AcceptanceTracker&); /* AcceptanceTracker + * objects. + */ +}; + + + +/****************************************************************************** + * + * A template class for checking a product for emptiness. + * + *****************************************************************************/ + +template +class Product::SimpleEmptinessChecker : public AcceptanceTracker +{ +public: + explicit SimpleEmptinessChecker /* Constructor. */ + (const unsigned long int num_accept_sets); + + ~SimpleEmptinessChecker(); /* Destructor. */ + + typedef int SccType; /* Dummy type definition + * required for supporting + * the expected class + * interface. + */ + + const SccType& operator()() const; /* Dummy function required + * for supporting the + * expected class + * interface. + */ + + /* `enter' inherited */ + + /* `backtrack' inherited */ + + /* `touch' inherited */ + + /* `leave' inherited */ + + void addEdgeToComponent /* Adds the acceptance */ + (const Edge& t, const size_type scc_id); /* sets associated with + * a product transition + * to a nontrivial + * strongly connected + * component of the + * product and aborts + * the emptiness check + * if an accepting + * strongly connected + * component is + * detected. + */ + + void addNodeToComponent /* Adds the acceptance */ + (const size_type state, /* sets associated with */ + const size_type scc_id); /* a product state to a + * nontrivial strongly + * connected component + * of the product and + * aborts the emptiness + * check if an accepting + * strongly connected + * component is + * detected. + */ + + /* `beginComponent' inherited */ + + /* `insert' inherited */ + + /* `endComponent' inherited */ + +private: + SimpleEmptinessChecker /* Prevent copying and */ + (const SimpleEmptinessChecker&); /* assignment of */ + SimpleEmptinessChecker& /* SimpleEmptiness- */ + operator=(const SimpleEmptinessChecker&); /* Checker objects. */ + + void abortIfNonempty() const; /* Aborts the search when + * an accepting strongly + * connected component is + * found. + */ + + SccType dummy; /* Dummy variable needed + * for implementing the + * operator() function. + */ +}; + + + +/****************************************************************************** + * + * A template class for tracking the reachability of accepting strongly + * connected components in a product. + * + *****************************************************************************/ + +template +class Product::AcceptanceReachabilityTracker + : public Product::AcceptanceTracker +{ +public: + explicit AcceptanceReachabilityTracker /* Constructor. */ + (const unsigned long int num_accept_sets); + + ~AcceptanceReachabilityTracker(); /* Destructor. */ + + typedef int SccType; /* Dummy type definition + * required for supporting + * the expected class + * interface. + */ + + const SccType& operator()() const; /* Dummy function required + * for supporting the + * expected class + * interface. + */ + + void enter(const size_type); /* Function called when + * entering a new product + * state. + */ + + void backtrack /* Function called when */ + (const size_type source, const Edge&, /* backtracking from a */ + const size_type target); /* product state. */ + + void touch /* Function called when */ + (const size_type source, const Edge& edge, /* processing an edge */ + const size_type target); /* with a target state + * that has already been + * visited during the + * search. + */ + + /* `leave' inherited */ + + /* `addEdgeToComponent' inherited */ + + /* `addNodeToComponent' inherited */ + + void beginComponent /* Tests whether the */ + (const size_type, const size_type state_id); /* strongly connected + * component about to + * be extracted from the + * product is an + * accepting component, + * or if it contains a + * state from which such + * a component is known + * to be reachable. + */ + + void insert(const size_type state); /* Function used for + * updating accepting + * component reachability + * information while + * extracting states from + * a product component. + */ + + /* `endComponent' inherited */ + + bool isMarked(const size_type state) const; /* Tests whether an + * accepting component is + * known to be reachable + * from a product state. + */ + + size_type numberOfStates() const; /* Tells the number of + * product states explored + * during the search. + */ + + unsigned long int numberOfTransitions() const; /* Tells the number of + * product transitions + * explored during the + * search. + */ + +private: + AcceptanceReachabilityTracker /* Prevent copying and */ + (const AcceptanceReachabilityTracker&); /* assignment of */ + AcceptanceReachabilityTracker& /* AcceptanceSet- */ + operator= /* ReachabilityTracker */ + (const AcceptanceReachabilityTracker&); /* objects. */ + + void markState(const size_type state); /* Adds a product state to + * the set of states from + * which an accepting + * component is known to be + * reachable. + */ + + set, /* Set of states from */ + ALLOC(size_type) > /* which an accepting */ + reachability_info; /* component is known to + * be reachable in the + * product. + */ + + size_type number_of_states; /* Number of states + * explored during the + * search. + */ + + unsigned long int number_of_transitions; /* Number of transitions + * explored during the + * search. + */ + + bool mark_scc; /* Used for determining + * whether to insert states + * into `this-> + * reachability_info' while + * extracting a strongly + * connected component from + * the product. + */ + + SccType dummy; /* Dummy variable needed + * for implementing the + * operator() function. + */ +}; + + + +/****************************************************************************** + * + * A template class for finding accepting maximal strongly connected components + * in a product. + * + *****************************************************************************/ + +template +class Product::AcceptingComponentFinder : + public Product::AcceptanceTracker +{ +public: + explicit AcceptingComponentFinder /* Constructor. */ + (const unsigned long int num_accept_sets); + + ~AcceptingComponentFinder(); /* Destructor. */ + + typedef set, /* Type definition for */ + ALLOC(size_type) > /* the set of product */ + SccType; /* state identifiers in + * an accepting + * strongly connected + * component. + */ + + const SccType& operator()() const; /* Returns the last + * accepting maximal + * strongly connected + * component found in the + * product. + */ + + /* `enter' inherited */ + + /* `backtrack' inherited */ + + /* `touch' inherited */ + + /* `leave' inherited */ + + /* `addEdgeToComponent' inherited */ + + /* `addNodeToComponent' inherited */ + + void beginComponent /* Tests whether the */ + (const size_type, const size_type); /* maximal strongly + * connected component + * that is about to be + * extracted from the + * product is an + * accepting component. + */ + + void insert(const size_type state); /* Inserts a state to an + * accepting component. + */ + + /* `endComponent' inherited */ + +private: + AcceptingComponentFinder /* Prevent copying and */ + (const AcceptingComponentFinder&); /* assignment of */ + AcceptingComponentFinder& /* AcceptingComponent- */ + operator=(const AcceptingComponentFinder&); /* Finder objects. */ + + SccType scc; /* Set of product state + * identifiers forming the + * last accepting strongly + * connected component + * found in the product. + */ + + bool construct_component; /* Used for determining + * whether the states + * extracted from a + * strongly connected + * component in the product + * should be inserted into + * `this->scc'. + */ +}; + + + +/****************************************************************************** + * + * Inline function definitions for template class Product. + * + *****************************************************************************/ + +/* ========================================================================= */ +template +inline Product::Product + (const Graph& g1, const Graph& g2) + : operations(g1, g2), state_id_multiplier(g2.size()) +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class Product. + * + * Arguments: g1, g2 -- Constant references to the components of the + * product. + * + * Returns: Nothing. If `Product::size_type' cannot hold + * values large enough to accommodate the largest identifier for + * a product state, `this->too_large' is set to true to cause + * all emptiness checking operations on the product to fail by + * throwing a Product::SizeException. + * + * ------------------------------------------------------------------------- */ +{ + too_large = (!g2.empty() && + g1.size() > (static_cast(-1) / g2.size())); +} + +/* ========================================================================= */ +template +inline Product::~Product() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class Product. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline const typename Product::ProductState +Product::operator[] + (const typename Product::size_type index) const +/* ---------------------------------------------------------------------------- + * + * Description: Indexing operator for class Product. + * + * Argument: index -- Index of a state of the product. + * + * Returns: A ProductState object corresponding to the state with the + * given index. + * + * ------------------------------------------------------------------------- */ +{ + return ProductState(index); +} + +/* ========================================================================= */ +template +inline typename Product::size_type Product + ::stateId + (const size_type state_1, const size_type state_2) const +/* ---------------------------------------------------------------------------- + * + * Description: Returns the product state identifier corresponding to + * identifiers of the state components. + * + * Arguments: state_1, state_2 -- Identifiers for the product state + * components. + * + * Returns: Identifier of the product state corresponding to the + * components. + * + * ------------------------------------------------------------------------- */ +{ + return (state_1 * state_id_multiplier) + state_2; +} + +/* ========================================================================= */ +template +inline const Graph::Node& +Product::firstComponent(const size_type state) const +/* ---------------------------------------------------------------------------- + * + * Description: Function for accessing the "first" component state of a + * product state. + * + * Arguments: state -- Identifier of a product state. + * + * Returns: A constant reference to the state corresponding to the + * "first" component of the product state. + * + * ------------------------------------------------------------------------- */ +{ + return operations.firstComponent(state / state_id_multiplier); +} + +/* ========================================================================= */ +template +inline const Graph::Node& +Product::secondComponent(const size_type state) const +/* ---------------------------------------------------------------------------- + * + * Description: Function for accessing the "second" component state of a + * product state. + * + * Arguments: state -- Identifier of a product state. + * + * Returns: A constant reference to the state corresponding to the + * "second" component of the product state. + * + * ------------------------------------------------------------------------- */ +{ + return operations.secondComponent(state % state_id_multiplier); +} + +/* ========================================================================= */ +template +inline bool Product::empty() const +/* ---------------------------------------------------------------------------- + * + * Description: Tells whether the product is (trivially) empty, i.e., if + * either of its components has no states. + * + * Arguments: None. + * + * Returns: true iff the product is trivially empty. + * + * ------------------------------------------------------------------------- */ +{ + return operations.empty(); +} + + + +/****************************************************************************** + * + * Function definitions for template class Product. + * + *****************************************************************************/ + +/* ========================================================================= */ +template +bool Product::localEmptinessCheck + (const Graph::size_type s1_id, + const Graph::size_type s2_id) +/* ---------------------------------------------------------------------------- + * + * Description: Checks whether the subproduct rooted at a product state + * determined by a pair of component state identifiers is not + * empty. + * + * Arguments: s1_id, s2_id -- Identifiers for the product state + * components. + * + * Returns: true iff the subproduct rooted at the product state + * determined by `s1_id' and `s2_id' is not empty. Throws a + * Product::SizeException if the product is too + * large to handle. + * + * ------------------------------------------------------------------------- */ +{ + if (too_large) + throw SizeException(); + + if (empty()) + return false; + + product = this; + + SimpleEmptinessChecker ec(operations.numberOfAcceptanceSets()); + typedef SccCollection, SimpleEmptinessChecker> + ProductSccCollection; + + ProductSccCollection sccs(*this, ec); + + try + { + for (typename ProductSccCollection::iterator scc + = sccs.begin(stateId(s1_id, s2_id)); + scc != sccs.end(); + ++scc) + { + if (::user_break) + throw UserBreakException(); + } + } + catch (const int) + { + return true; + } + + return false; +} + +/* ========================================================================= */ +template +const pair::size_type, unsigned long int> +Product::globalEmptinessCheck + (const Graph::size_type state_id, + Bitset& result, const unsigned long int emptiness_check_size) +/* ---------------------------------------------------------------------------- + * + * Description: Checks a set of subproducts of the product for emptiness. + * + * Arguments: state_id -- Identifier of a state in the first + * component of the product. + * result -- A reference to a Bitset for storing + * the result of the emptiness check. + * The set should have room for at + * least `emptiness_check_size' bits. + * emptiness_check_size -- Determines the scope of the + * emptiness check (see below). + * + * Returns: A pair giving the numbers of product states and transitions + * generated during the emptiness check. The result of the + * emptiness check itself is stored into `result' such that + * the i'th bit (for all 0 <= i < emptiness_check_size) in the + * bit set will be 1 iff the subproduct rooted at the product + * state determined by the pair of state identifiers + * (state_id, i) is nonempty. + * + * The function throws a Product::SizeException if + * the product may be too large to handle. + * + * ------------------------------------------------------------------------- */ +{ + if (too_large) + throw SizeException(); + + result.clear(); + + if (empty()) + return make_pair(0, 0); + + product = this; + + AcceptanceReachabilityTracker rt(operations.numberOfAcceptanceSets()); + + typedef SccCollection, AcceptanceReachabilityTracker> + ProductSccCollection; + + ProductSccCollection sccs(*this, rt); + + for (Graph::size_type state = 0; + state < emptiness_check_size; + ++state) + { + for (typename ProductSccCollection::iterator scc + = sccs.begin(stateId(state_id, state)); + scc != sccs.end(); + ++scc) + { + if (::user_break) + throw UserBreakException(); + } + } + + for (Graph::size_type state = 0; + state < emptiness_check_size; + ++state) + { + if (rt.isMarked(stateId(state_id, state))) + result.setBit(state); + } + + return make_pair(rt.numberOfStates(), rt.numberOfTransitions()); +} + +/* ========================================================================= */ +template +void Product::findWitness + (const typename Graph::size_type s1_id, + const typename Graph::size_type s2_id, + typename Product::Witness& witness) +/* ---------------------------------------------------------------------------- + * + * Description: Checks whether the subproduct rooted at a product state + * determined by a pair of component state identifiers is not + * empty. If this is the case, constructs a witness for the + * nonemptiness. + * + * Arguments: s1_id, s2_id -- Identifiers for the product state + * components. + * witness -- A reference to an object for storing a + * witness if such a witness exists. + * + * Returns: Nothing. A witness was found iff + * `!witness.cycle.first.empty() + * && !witness.cycle.second.empty()' holds after the call. + * + * The function throws a Product::SizeException if + * the product may be too large to handle. + * + * ------------------------------------------------------------------------- */ +{ + if (too_large) + throw SizeException(); + + witness.prefix.first.clear(); + witness.prefix.second.clear(); + witness.cycle.first.clear(); + witness.cycle.second.clear(); + + if (empty()) + return; + + product = this; + const unsigned long int number_of_acceptance_sets + = operations.numberOfAcceptanceSets(); + const size_type start_state = stateId(s1_id, s2_id); + + AcceptingComponentFinder acf(number_of_acceptance_sets); + typedef SccCollection, AcceptingComponentFinder> + ProductSccCollection; + + ProductSccCollection sccs(*this, acf); + + for (typename ProductSccCollection::iterator scc = sccs.begin(start_state); + scc != sccs.end(); + ++scc) + { + if (::user_break) + throw UserBreakException(); + + if (!scc->empty()) + { + /* + * The prefix of the witness consists of a path from the given product + * state to a state in an accepting strongly connected product + * component. + */ + + Path path; + scc.getPath(path); + + for (typename Path::const_iterator path_element = path.begin(); + path_element != path.end(); + ++path_element) + { + witness.prefix.first.push_back + (Graph::PathElement + (path_element->state / state_id_multiplier, + path_element->transition.firstComponent())); + witness.prefix.second.push_back + (Graph::PathElement + (path_element->state % state_id_multiplier, + path_element->transition.secondComponent())); + } + + /* + * Construct an accepting cycle by performing a breadth-first search + * in the MSCC. + */ + + const size_type search_start_state + = path.empty() ? start_state : path.back().transition.targetNode(); + + BitArray collected_acceptance_sets(number_of_acceptance_sets); + collected_acceptance_sets.clear(number_of_acceptance_sets); + operations.mergeAcceptanceInformation + (firstComponent(search_start_state), + secondComponent(search_start_state), collected_acceptance_sets); + + unsigned long int number_of_collected_acceptance_sets + = collected_acceptance_sets.count(number_of_acceptance_sets); + + deque search_queue; + set, ALLOC(size_type) > visited; + map, ALLOC(PathElement) > + shortest_path_predecessor; + + size_type bfs_root = search_start_state; + +continue_bfs: + search_queue.clear(); + search_queue.push_back(bfs_root); + visited.clear(); + visited.insert(bfs_root); + shortest_path_predecessor.clear(); + + while (!search_queue.empty()) + { + const EdgeContainerType transitions + = ProductState(search_queue.front()).edges(); + + for (typename EdgeContainerType::const_iterator transition + = transitions.begin(); + transition != transitions.end(); + ++transition) + { + const size_type target = (*transition)->targetNode(); + if (scc->find(target) == scc->end()) + continue; + + if (visited.find(target) == visited.end()) + { + visited.insert(target); + shortest_path_predecessor.insert + (make_pair(target, PathElement(search_queue.front(), + **transition))); + search_queue.push_back(target); + + if (number_of_collected_acceptance_sets + < number_of_acceptance_sets) + operations.mergeAcceptanceInformation + (firstComponent(target), secondComponent(target), + collected_acceptance_sets); + } + + if (number_of_collected_acceptance_sets < number_of_acceptance_sets) + { + /* + * Test whether the current product transition or the target + * state of the transition covers new acceptance sets. If + * this is the case, construct the next segment of the cycle + * and begin a new breadth-first search in the target state of + * the transition. + */ + + operations.mergeAcceptanceInformation + ((*transition)->firstComponent(), + (*transition)->secondComponent(), collected_acceptance_sets); + + const unsigned long int num + = collected_acceptance_sets.count(number_of_acceptance_sets); + if (num > number_of_collected_acceptance_sets) + { + number_of_collected_acceptance_sets = num; + + addCycleSegment(witness.cycle, search_queue.front(), + **transition, bfs_root, + shortest_path_predecessor); + + if (number_of_collected_acceptance_sets + == number_of_acceptance_sets + && target == search_start_state) + return; + + bfs_root = target; + goto continue_bfs; + } + } + else if (target == search_start_state) + { + /* + * If all acceptance sets have been collected and the current + * product transition points to the first state of the cycle, + * the cycle is complete. + */ + + addCycleSegment(witness.cycle, search_queue.front(), **transition, + bfs_root, shortest_path_predecessor); + return; + } + } + + search_queue.pop_front(); + } + + throw Exception + ("Product::findWitness(...): internal error [cycle construction " + "failed]"); + } + } +} + +/* ========================================================================= */ +template +void Product::addCycleSegment + (pair::Path, Graph::Path>& + cycle, + size_type source_state_id, Edge transition, const size_type root_id, + const map, ALLOC(PathElement) >& + predecessor) const +/* ---------------------------------------------------------------------------- + * + * Description: Helper function for constructing a segment of an accepting + * cycle in the product. + * + * Arguments: cycle -- A reference to a pair of paths for + * storing the result. + * source_state_id -- Identifier of the last product state in + * the cycle segment. + * transition -- Last product transition in the cycle + * segment. + * root_id -- Identifier of the first product state in + * the cycle segment. + * predecessor -- Mapping between states and their + * predecessors in the cycle segment. + * + * Returns: Nothing. The segment of the cycle is appended to `cycle'. + * + * ------------------------------------------------------------------------- */ +{ + Graph::Path first_segment; + Graph::Path second_segment; + + while (1) + { + first_segment.push_front + (Graph::PathElement + (source_state_id / state_id_multiplier, transition.firstComponent())); + second_segment.push_front + (Graph::PathElement + (source_state_id % state_id_multiplier, + transition.secondComponent())); + + if (source_state_id == root_id) + { + cycle.first.insert(cycle.first.end(), first_segment.begin(), + first_segment.end()); + cycle.second.insert(cycle.second.end(), second_segment.begin(), + second_segment.end()); + return; + } + + const PathElement& p = predecessor.find(source_state_id)->second; + source_state_id = p.state; + transition = p.transition; + } +} + + + +/****************************************************************************** + * + * Inline function definitions for template class + * Product::ProductState. + * + *****************************************************************************/ + +/* ========================================================================= */ +template +inline Product::ProductState::ProductState + (const size_type state) : outgoing_edges(state) +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class Product::ProductState. + * + * Argument: state -- Identifier of a product state. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline Product::ProductState::~ProductState() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class Product::ProductState. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline const typename Product::EdgeContainerType& +Product::ProductState::edges() const +/* ---------------------------------------------------------------------------- + * + * Description: Returns an object for generating the transitions starting + * from a product state. + * + * Arguments: None. + * + * Returns: A constant reference to an object that can be used for + * generating the transitions starting from the state. + * + * ------------------------------------------------------------------------- */ +{ + return outgoing_edges; +} + + + +/****************************************************************************** + * + * Inline function definitions for template class + * Product::ProductEdge. + * + *****************************************************************************/ + +/* ========================================================================= */ +template +inline Product::ProductEdge::ProductEdge + (const GraphEdgeContainer::const_iterator& e1, + const GraphEdgeContainer::const_iterator& e2) + : edge_1(*e1), edge_2(*e2) +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class Product::ProductEdge. + * + * Arguments: e1, e2 -- Iterators pointing to the components of the + * product transition. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline Product::ProductEdge::~ProductEdge() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class Product::ProductEdge. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline const Graph::Edge& +Product::ProductEdge::firstComponent() const +/* ---------------------------------------------------------------------------- + * + * Description: Function for accessing the "first" component of a product + * transition. + * + * Arguments: None. + * + * Returns: A constant reference to the "first" component of the + * transition. + * + * ------------------------------------------------------------------------- */ +{ + return *edge_1; +} + +/* ========================================================================= */ +template +inline const Graph::Edge& +Product::ProductEdge::secondComponent() const +/* ---------------------------------------------------------------------------- + * + * Description: Function for accessing the "second" component of a product + * transition. + * + * Arguments: None. + * + * Returns: A constant reference to the "second" component of the + * transition. + * + * ------------------------------------------------------------------------- */ +{ + return *edge_2; +} + +/* ========================================================================= */ +template +inline typename Product::size_type +Product::ProductEdge::targetNode() const +/* ---------------------------------------------------------------------------- + * + * Description: Returns the target state of a product transition. + * + * Arguments: None. + * + * Returns: Identifier of the target state of the product transition. + * + * ------------------------------------------------------------------------- */ +{ + return product->stateId(edge_1->targetNode(), edge_2->targetNode()); +} + + + +/****************************************************************************** + * + * Inline function definitions for template class + * Product::ProductEdgePointer. + * + *****************************************************************************/ + +/* ========================================================================= */ +template +inline Product::ProductEdgePointer::ProductEdgePointer + (const GraphEdgeContainer::const_iterator& e1, + const GraphEdgeContainer::const_iterator& e2) + : edge(e1, e2) +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class + * Product::ProductEdgePointer. + * + * Arguments: e1, e2 -- Iterators pointing to the components of the + * product transition. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline Product::ProductEdgePointer::~ProductEdgePointer() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class + * Product::ProductEdgePointer. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline const typename Product::ProductEdge& +Product::ProductEdgePointer::operator*() const +/* ---------------------------------------------------------------------------- + * + * Description: Dereferencing operator for class + * Product::ProductEdgePointer. + * + * Arguments: None. + * + * Returns: A reference to the product transition associated with the + * object. + * + * ------------------------------------------------------------------------- */ +{ + return edge; +} + +/* ========================================================================= */ +template +inline const typename Product::ProductEdge* +Product::ProductEdgePointer::operator->() const +/* ---------------------------------------------------------------------------- + * + * Description: Dereferencing operator for class + * Product::ProductEdgePointer. + * + * Arguments: None. + * + * Returns: A pointer to the product transition associated with the + * object. + * + * ------------------------------------------------------------------------- */ +{ + return &edge; +} + + + +/****************************************************************************** + * + * Inline function definitions for template class + * Product::ProductEdgeCollection. + * + *****************************************************************************/ + +/* ========================================================================= */ +template +inline Product::ProductEdgeCollection::ProductEdgeCollection + (const size_type state) : product_state(state) +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class + * Product::ProductEdgeCollection. + * + * Argument: state -- Identifier of a product state. The + * ProductEdgeCollection object will mimic a + * container for the product transitions starting + * from this state. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline Product::ProductEdgeCollection::~ProductEdgeCollection() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class + * Product::ProductEdgeCollection. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline const typename Product::ProductEdgeCollection + ::const_iterator +Product::ProductEdgeCollection::begin() const +/* ---------------------------------------------------------------------------- + * + * Description: Returns an iterator for generating the transitions starting + * from the product state identified by `this->product_state'. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + return const_iterator + (product_state, + product->firstComponent(product_state).edges().begin(), + product->secondComponent(product_state).edges().begin()); +} + +/* ========================================================================= */ +template +inline const typename Product::ProductEdgeCollection + ::const_iterator +Product::ProductEdgeCollection::end() const +/* ---------------------------------------------------------------------------- + * + * Description: Returns an iterator pointing to the "end" of the collection + * of transitions starting from the product state identified by + * `this->product_state'. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + return const_iterator + (product_state, + product->firstComponent(product_state).edges().end(), + product->secondComponent(product_state).edges().end()); +} + + + +/****************************************************************************** + * + * Inline function definitions for template class + * Product::ProductEdgeCollection::const_iterator. + * + *****************************************************************************/ + +/* ========================================================================= */ +template +inline Product::ProductEdgeCollection::const_iterator + ::const_iterator() : product_state(0) +/* ---------------------------------------------------------------------------- + * + * Description: Default constructor for class + * Product::ProductEdgeCollection::const_iterator. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline Product::ProductEdgeCollection::const_iterator + ::const_iterator + (const size_type state, + const GraphEdgeContainer::const_iterator& e1, + const GraphEdgeContainer::const_iterator& e2) : + product_state(state), edge_1(e1), edge_2(e2) +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class + * Product::ProductEdgeCollection::const_iterator. + * + * Arguments: state -- Identifier of a product state to associate with + * the iterator. + * e1, e1 -- Constant references to a pair of iterators + * pointing to a pair of transitions starting from + * the component states of the product state. These + * iterators are used to determine where to start + * iterating over the product transitions. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + product->operations.validateEdgeIterators + (product->firstComponent(product_state), + product->secondComponent(product_state), + edge_1, edge_2); +} + +/* ========================================================================= */ +template +inline Product::ProductEdgeCollection::const_iterator + ::~const_iterator() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class + * Product::ProductEdgeCollection::const_iterator. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline bool Product::ProductEdgeCollection::const_iterator + ::operator== + (const const_iterator& it) const +/* ---------------------------------------------------------------------------- + * + * Description: Equality test between two + * Product::ProductEdgeCollection::const_iterators. + * + * Argument: it -- A constant reference to an iterator to compare for + * equality. It is assumed that the iterators are + * associated with the same product state; the result of + * a comparison between iterators associated with + * different product states is undefined. + * + * Returns: true iff `it' and `*this' point to the same transition in the + * product. + * + * ------------------------------------------------------------------------- */ +{ + return (it.edge_1 == edge_1 && it.edge_2 == edge_2); +} + +/* ========================================================================= */ +template +inline bool Product::ProductEdgeCollection::const_iterator + ::operator!= + (const const_iterator& it) const +/* ---------------------------------------------------------------------------- + * + * Description: Inequality test between two + * Product::ProductEdgeCollection::const_iterators. + * + * Argument: it -- A constant reference to an iterator to compare for + * equality. It is assumed that the iterators are + * associated with the same product state; the result of + * a comparison between iterators associated with + * different product states is undefined. + * + * Returns: true iff `it' and `*this' point to different transitions in + * the product. + * + * ------------------------------------------------------------------------- */ +{ + return (it.edge_1 != edge_1 || it.edge_2 != edge_2); +} + +/* ========================================================================= */ +template +inline const typename Product::ProductEdgePointer +Product::ProductEdgeCollection::const_iterator::operator*() const +/* ---------------------------------------------------------------------------- + * + * Description: Dereferencing operator for class + * Product::ProductEdgeCollection::const_iterator. + * + * Arguments: None. + * + * Returns: An object of type Product::ProductEdgePointer + * that allows access to the product transition pointed to by + * the iterator. + * + * ------------------------------------------------------------------------- */ +{ + return ProductEdgePointer(edge_1, edge_2); +} + +/* ========================================================================= */ +template +inline const typename Product::ProductEdge +Product::ProductEdgeCollection::const_iterator::operator->() const +/* ---------------------------------------------------------------------------- + * + * Description: Dereferencing operator for class + * Product::ProductEdgeCollection::const_iterator. + * + * Arguments: None. + * + * Returns: A Product::ProductEdge corresponding to the + * product transition pointed to by the iterator. + * + * ------------------------------------------------------------------------- */ +{ + return ProductEdge(edge_1, edge_2); +} + +/* ========================================================================= */ +template +inline const typename Product::ProductEdgePointer +Product::ProductEdgeCollection::const_iterator::operator++() +/* ---------------------------------------------------------------------------- + * + * Description: Prefix increment operator for class + * Product::ProductEdgeCollection::const_iterator. + * + * Arguments: None. + * + * Returns: An object of type Product::ProductEdgePointer + * that behaves like a pointer to the product transition + * obtained by advancing the iterator in the sequence of product + * transitions. + * + * ------------------------------------------------------------------------- */ +{ + product->operations.incrementEdgeIterators + (product->firstComponent(product_state), + product->secondComponent(product_state), + edge_1, edge_2); + return ProductEdgePointer(edge_1, edge_2); +} + +/* ========================================================================= */ +template +inline const typename Product::ProductEdgePointer +Product::ProductEdgeCollection::const_iterator::operator++(int) +/* ---------------------------------------------------------------------------- + * + * Description: Postfix increment operator for class + * Product::ProductEdgeCollection::const_iterator. + * + * Arguments: None. + * + * Returns: An object of type Product::ProductEdgePointer + * that behaves like a pointer to the product transition pointed + * to by the iterator before advancing it in the sequence of + * product transitions. + * + * ------------------------------------------------------------------------- */ +{ + const typename Product::ProductEdgePointer edge(edge_1, edge_2); + product->operations.incrementEdgeIterators + (product->firstComponent(product_state), + product->secondComponent(product_state), + edge_1, edge_2); + return edge; +} + + + +/****************************************************************************** + * + * Inline function definitions for template class + * Product::PathElement. + * + *****************************************************************************/ + +/* ========================================================================= */ +template +inline Product::PathElement::PathElement + (const size_type s, const Edge& t) : state(s), transition(t) +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class Product::PathElement. + * + * Arguments: s, t -- Product state and transition associated with the + * element. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline Product::PathElement::~PathElement() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class Product::PathElement. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + + + +/****************************************************************************** + * + * Inline function definitions for template class + * Product::SizeException. + * + *****************************************************************************/ + +/* ========================================================================= */ +template +inline Product::SizeException::SizeException() : + Exception("product may be too large") +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class Product::SizeException. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline Product::SizeException::~SizeException() throw() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class Product::SizeException. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline typename Product::SizeException& +Product::SizeException::operator= + (const typename Product::SizeException& e) +/* ---------------------------------------------------------------------------- + * + * Description: Assignment operator for class + * Product::SizeException. Assigns the value of + * another Product::SizeException to `this' one. + * + * Arguments: e -- A reference to a constant object of type + * ProductSizeException. + * + * Returns: A reference to the object whose value was changed. + * + * ------------------------------------------------------------------------- */ +{ + Exception::operator=(e); + return *this; +} + + + +/****************************************************************************** + * + * Inline function definitions for template class + * Product::AcceptanceTracker. + * + *****************************************************************************/ + +/* ========================================================================= */ +template +inline Product::AcceptanceTracker::AcceptanceTracker + (const unsigned long int num_accept_sets) + : number_of_acceptance_sets(num_accept_sets) +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class Product::AcceptanceTracker. + * + * Arguments: num_accept_sets -- Number of acceptance sets in the + * product. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + /* Initialize `acceptance_stack' with a sentinel element. */ + acceptance_stack.push_front(make_pair(0, new BitArray(0))); +} + +/* ========================================================================= */ +template +inline Product::AcceptanceTracker::~AcceptanceTracker() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class Product::AcceptanceTracker. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + for (AcceptanceStack::iterator a = acceptance_stack.begin(); + a != acceptance_stack.end(); + ++a) + delete a->second; +} + +/* ========================================================================= */ +template +inline void Product::AcceptanceTracker::addEdgeToComponent + (const typename Product::Edge& t, + const typename Product::size_type scc_id) +/* ---------------------------------------------------------------------------- + * + * Description: Adds the acceptance sets of the product transition `t' + * (inside a strongly connected product component) to the + * collection of acceptance sets associated with the strongly + * connected component identifier (a dfs number of a product + * state in Tarjan's algorithm). (The component is therefore + * nontrivial, because it contains a transition.) This is done + * as described by Couvreur + * [J.-M. Couvreur. On-the-fly verification of linear + * temporal logic. In Proceedings of the FM'99 World + * Congress on Formal Methods in the Development of + * Computing Systems, Volume I, LNCS 1708, pp. 253--271. + * Springer-Verlag, 1999] + * by first collapsing all elements in the top part of + * `this->acceptance_stack' (*) with strongly connected + * component identifiers greater than or equal to `scc_id' into + * a single element by taking the union of their acceptance sets + * and then merging the acceptance sets of the transition `t' + * with this element. + * After the call, the top element of `this->acceptance_stack' + * will have the scc id `scc_id', and `this->acceptance_sets' + * points to the acceptance sets associated with this stack + * element. + * + * (*) It is assumed that the contents of + * `this->acceptance_stack' form (from top to bottom) a sequence + * of elements (id1,a1), (id2,a2), (id3,a3), ..., where + * id1 > id2 > id3 > ... . The function maintains this + * invariant. + * + * Arguments: t -- A constant reference to the product transition. + * scc_id -- Identifier of the strongly connected component + * (assumed to be >= 1). + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + acceptance_sets = 0; + while (acceptance_stack.front().first >= scc_id) + { + if (acceptance_sets != 0) + { + acceptance_stack.front().second->bitwiseOr + (*acceptance_sets, number_of_acceptance_sets); + delete acceptance_sets; + } + acceptance_sets = acceptance_stack.front().second; + if (acceptance_stack.front().first == scc_id) + goto merge_sets; + + acceptance_stack.pop_front(); + } + + if (acceptance_sets == 0) + { + acceptance_sets = new BitArray(number_of_acceptance_sets); + acceptance_sets->clear(number_of_acceptance_sets); + } + acceptance_stack.push_front(make_pair(scc_id, acceptance_sets)); +merge_sets: + product->operations.mergeAcceptanceInformation + (t.firstComponent(), t.secondComponent(), *acceptance_sets); +} + +/* ========================================================================= */ +template +inline void Product::AcceptanceTracker::addNodeToComponent + (const typename Product::size_type state_id, + const typename Product::size_type scc_id) +/* ---------------------------------------------------------------------------- + * + * Description: Adds the acceptance sets of the product state `state_id' to + * the collection of acceptance sets associated with a + * nontrivial strongly connected component identifier (a dfs + * number of a product state in Tarjan's algorithm). + * + * Arguments: state_id -- Identifier of the product state. + * scc_id -- Identifier of the strongly connected component + * (assumed to be >= 1). + * + * Returns: Nothing. Upon return, `this->acceptance_sets' either points + * to the acceptance sets associated with the topmost element of + * `this->acceptance_sets', or, if the component is a + * trivial strongly connected component, + * `this->acceptance_sets == 0'. + * + * ------------------------------------------------------------------------- */ +{ + /* + * When this function gets called, then depth-first search guarantees that + * the strongly connected component identifier of the topmost element (if + * such an element exists) of `this->acceptance_stack' is <= 'scc_id'. + * Furthermore, the strongly connected is nontrivial only if equality holds + * in the above test. + */ + + if (acceptance_stack.front().first < scc_id) + { + acceptance_sets = 0; + return; + } + + acceptance_sets = acceptance_stack.front().second; + product->operations.mergeAcceptanceInformation + (product->firstComponent(state_id), product->secondComponent(state_id), + *acceptance_sets); +} + +/* ========================================================================= */ +template +inline void Product::AcceptanceTracker::endComponent + (const typename Product::size_type scc_id) +/* ---------------------------------------------------------------------------- + * + * Description: Removes the association between a nontrivial strongly + * connected component identifier and a collection of acceptance + * sets when the component is not needed any longer. (This + * function gets called after extracting a maximal strongly + * connected component from the product. It is safe to remove + * the association at this point, because the search will not + * enter the component afterwards, nor can any state visited + * in the future belong to the strongly connected component + * identified by `scc_id'.) + * + * Argument: scc_id -- Identifier of the strongly connected component. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + /* + * Because the depth-first search made a call to `addNodeToComponent' before + * extracting the component from the product, the topmost element (if any) + * of `this->acceptance_stack' is guaranteed to have scc id <= `scc_id' at + * this point. + */ + + if (acceptance_stack.front().first == scc_id) + { + delete acceptance_stack.front().second; + acceptance_stack.pop_front(); + } +} + + + +/****************************************************************************** + * + * Inline function definitions for template class + * Product::SimpleEmptinessChecker. + * + *****************************************************************************/ + +/* ========================================================================= */ +template +inline Product::SimpleEmptinessChecker::SimpleEmptinessChecker + (const unsigned long int num_accept_sets) + : AcceptanceTracker(num_accept_sets), dummy(0) +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class + * Product::SimpleEmptinessChecker. + * + * Arguments: num_accept_sets -- Number of acceptance sets in the + * product. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline Product::SimpleEmptinessChecker::~SimpleEmptinessChecker() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class + * Product::SimpleEmptinessChecker. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline const typename Product::SimpleEmptinessChecker::SccType& +Product::SimpleEmptinessChecker::operator()() const +/* ---------------------------------------------------------------------------- + * + * Description: Dummy function required for supporting the expected class + * interface. + * + * Arguments: None. + * + * Returns: A constant reference to `this->dummy'. + * + * ------------------------------------------------------------------------- */ +{ + return dummy; +} + +/* ========================================================================= */ +template +inline void Product::SimpleEmptinessChecker::addEdgeToComponent + (const typename Product::Edge& t, + const typename Product::size_type scc_id) +/* ---------------------------------------------------------------------------- + * + * Description: Adds a transition to a nontrivial strongly connected product + * component and aborts the search if the addition of the + * transition in the component makes the component accepting. + * + * Arguments: t -- A constant reference to the product transition to + * be added to the component. + * scc_id -- Identifier of the strongly connected component. + * + * Returns: Nothing; aborts the search by throwing the constant 0 if the + * addition of the transition in the component makes the + * component accepting. + * + * ------------------------------------------------------------------------- */ +{ + AcceptanceTracker::addEdgeToComponent(t, scc_id); + /* `this->acceptance_sets' points to the acceptance sets associated with + * the nontrivial SCC `scc_id' at this point. */ + abortIfNonempty(); +} + +/* ========================================================================= */ +template +inline void Product::SimpleEmptinessChecker::addNodeToComponent + (const typename Product::size_type state, + const typename Product::size_type scc_id) +/* ---------------------------------------------------------------------------- + * + * Description: Adds a state to a nontrivial strongly connected product + * component and aborts the search if the addition of the state + * in the component makes the component accepting. + * + * Arguments: state -- Identifier of a product state to be added to the + * component. + * scc_id -- Identifier of the strongly connected component. + * + * Returns: Nothing; aborts the search by throwing the constant 0 if the + * addition of the state in the component makes the component + * accepting. + * + * ------------------------------------------------------------------------- */ +{ + AcceptanceTracker::addNodeToComponent(state, scc_id); + /* If `this->acceptance_sets != 0', then `this->acceptance_sets' points to + * the acceptance sets associated with the nontrivial SCC `scc_id'; + * otherwise the component `scc_id' is a trivial SCC. */ + if (this->acceptance_sets != 0) + abortIfNonempty(); +} + +/* ========================================================================= */ +template +inline void Product::SimpleEmptinessChecker::abortIfNonempty() + const +/* ---------------------------------------------------------------------------- + * + * Description: Tests whether the strongly connected component with + * acceptance sets pointed to by `this->acceptance_sets' is an + * accepting component. This holds if all bits in the + * acceptance set bit vector pointed to by + * `this->acceptance_sets' are set to 1. + * + * Arguments: None. + * + * Returns: Nothing; throws the constant 0 if the component with + * acceptance sets pointed to by `this->acceptance_sets' is an + * accepting strongly connected component. + * + * ------------------------------------------------------------------------- */ +{ + if (this->acceptance_sets->count(this->number_of_acceptance_sets) + == this->number_of_acceptance_sets) + throw 0; +} + + + +/****************************************************************************** + * + * Inline function definitions for template class + * Product::AcceptanceReachabilityTracker. + * + *****************************************************************************/ + +/* ========================================================================= */ +template +inline Product::AcceptanceReachabilityTracker + ::AcceptanceReachabilityTracker + (const unsigned long int num_accept_sets) : + AcceptanceTracker(num_accept_sets), number_of_states(0), + number_of_transitions(0), mark_scc(false), dummy(0) +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class + * Product::AcceptanceReachabilityTracker. + * + * Arguments: num_accept_sets -- Number of acceptance sets in the + * product. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline Product::AcceptanceReachabilityTracker + ::~AcceptanceReachabilityTracker() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class + * Product::AcceptanceReachabilityTracker. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline +const typename Product::AcceptanceReachabilityTracker::SccType& +Product::AcceptanceReachabilityTracker::operator()() const +/* ---------------------------------------------------------------------------- + * + * Description: Dummy function required for supporting the expected class + * interface. + * + * Arguments: None. + * + * Returns: A constant reference to `this->dummy'. + * + * ------------------------------------------------------------------------- */ +{ + return dummy; +} + +/* ========================================================================= */ +template +inline void Product::AcceptanceReachabilityTracker::enter + (const typename Product::size_type) +/* ---------------------------------------------------------------------------- + * + * Description: Function called when entering a new state in the product. + * Increments the number of product states that have been + * explored. + * + * Arguments: The single argument is required to support the expected class + * interface. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + ++number_of_states; +} + +/* ========================================================================= */ +template +inline void Product::AcceptanceReachabilityTracker::backtrack + (const typename Product::size_type source, + const typename Product::Edge&, + const typename Product::size_type target) +/* ---------------------------------------------------------------------------- + * + * Description: Function called when backtracking from a state in the + * product. Increments the number of product edges that have + * been explored. Additionally, if the state from which the + * search backtracks belongs to the set of states from which an + * accepting strongly connected component is known to be + * reachable in the product, adds also the state to which the + * search backtracks into this set of states. + * + * Arguments: (source, target) describe the endpoints of the product + * transition along which the backtrack occurs (i.e., `source' + * is the state _to_ which the search backtracks). The second + * argument is only needed to support the expected function call + * interface. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + ++number_of_transitions; + if (isMarked(target)) + markState(source); +} + +/* ========================================================================= */ +template +inline void Product::AcceptanceReachabilityTracker::touch + (const typename Product::size_type source, + const typename Product::Edge& edge, + const typename Product::size_type target) +/* ---------------------------------------------------------------------------- + * + * Description: Function called when the search encounters an edge with a + * target node that has already been explored. Increments the + * number of explored product transitions and updates accepting + * strongly connected reachability information by calling + * `this->backtrack()'. (This function is needed for supporting + * the expected class interface.) + * + * Arguments: (source, edge, target) describe the product transition that + * "touches" the state `target'. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + backtrack(source, edge, target); +} + +/* ========================================================================= */ +template +inline void Product::AcceptanceReachabilityTracker::beginComponent + (const typename Product::size_type, + const typename Product::size_type state_id) +/* ---------------------------------------------------------------------------- + * + * Description: Tests whether the maximal strongly connected component that + * is about to be extracted from the product is an accepting + * component, or if the component contains a state from which + * such a component is known to be reachable. If either of + * these properties holds, `this->mark_scc' is set to true to + * cause all states referred to in subsequent calls to + * `this->insert' that occur before the next call to + * `this->endComponent' to be added into the set of states from + * which an accepting strongly connected component in the + * product is known to be reachable. + * + * A component is accepting iff `this->acceptance_sets' points + * to a bit vector in which all bits are set to 1. + * + * Arguments: state_id -- Identifier of a product state (in the + * component) that was encountered first during + * the search. + * The first argument is needed to support the expected function + * interface. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + if (isMarked(state_id)) /* If the component itself is not accepting, but */ + mark_scc = true; /* it contains a state from which such a component */ + /* is reachable, then the fact that `state_id' is */ + /* the first state of the component encountered */ + /* during the search and the operation of the */ + /* backtrack and touch functions guarantee that */ + /* `isMarked(state_id) == true' holds at this */ + /* point (the search is about to backtrack from */ + /* this state when this function gets called). */ + else /* test whether the component is an accepting component */ + { + /* + * The dfs search guarantees (by having made a call to + * AcceptanceTracker::addNodeToComponent before calling this function) that + * `this->acceptance_sets' is either equal to 0 (in which case the + * component to be extracted is trivial), or it points to the acceptance + * sets associated with the component to be extracted. + */ + mark_scc = (this->acceptance_sets != 0 + && this->acceptance_sets->count + (this->number_of_acceptance_sets) + == this->number_of_acceptance_sets); + } +} + +/* ========================================================================= */ +template +inline void Product::AcceptanceReachabilityTracker::insert + (const typename Product::size_type state) +/* ---------------------------------------------------------------------------- + * + * Description: If `this->mark_scc == true', inserts a product state + * identifier to the set of states from which an accepting + * strongly connected component is known to be reachable in the + * product. Discards the state otherwise. + * + * Argument: state -- Identifier of a product state. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + if (mark_scc) + markState(state); +} + +/* ========================================================================= */ +template +inline bool Product::AcceptanceReachabilityTracker::isMarked + (const typename Product::size_type state) const +/* ---------------------------------------------------------------------------- + * + * Description: Tells whether an accepting strongly connected component is + * known to be reachable from a state in the product. + * + * Argument: state -- Identifier of the product state to test. + * + * Returns: true iff an accepting strongly connected component is known + * to be reachable from the product state. + * + * ------------------------------------------------------------------------- */ +{ + return (reachability_info.find(state) != reachability_info.end()); +} + +/* ========================================================================= */ +template +inline typename Product::size_type +Product::AcceptanceReachabilityTracker::numberOfStates() const +/* ---------------------------------------------------------------------------- + * + * Description: Tells the number of product states explored during the + * search. + * + * Arguments: None. + * + * Returns: Number of product states explored. + * + * ------------------------------------------------------------------------- */ +{ + return number_of_states; +} + +/* ========================================================================= */ +template +inline unsigned long int +Product::AcceptanceReachabilityTracker::numberOfTransitions() const +/* ---------------------------------------------------------------------------- + * + * Description: Tells the number of product transitions explored during the + * search. + * + * Arguments: None. + * + * Returns: Number of transitions explored. + * + * ------------------------------------------------------------------------- */ +{ + return number_of_transitions; +} + +/* ========================================================================= */ +template +inline void Product::AcceptanceReachabilityTracker::markState + (const typename Product::size_type state) +/* ---------------------------------------------------------------------------- + * + * Description: Adds a product state to the set of states from which an + * accepting strongly connected component is known to be + * reachable in the product. + * + * Argument: state -- Identifier of a product state. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + reachability_info.insert(state); +} + + + +/****************************************************************************** + * + * Inline function definitions for template class + * Product::AcceptingComponentFinder. + * + *****************************************************************************/ + +/* ========================================================================= */ +template +inline Product::AcceptingComponentFinder::AcceptingComponentFinder + (const unsigned long int num_accept_sets) + : AcceptanceTracker(num_accept_sets), construct_component(false) +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class + * Product::AcceptingComponentFinder. + * + * Arguments: num_accept_sets -- Number of acceptance sets in the + * product. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline Product::AcceptingComponentFinder + ::~AcceptingComponentFinder() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class + * Product::AcceptingComponentFinder. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline const typename Product::AcceptingComponentFinder::SccType& +Product::AcceptingComponentFinder::operator()() const +/* ---------------------------------------------------------------------------- + * + * Description: Returns the latest accepting maximal strongly connected + * component found in the product. + * + * Arguments: None. + * + * Returns: A constant reference to a set of states containing the + * identifiers of product states forming an accepting maximal + * strongly connected component in the product. This set is + * empty if no such component has yet been found during the + * emptiness check. + * + * ------------------------------------------------------------------------- */ +{ + return scc; +} + +/* ========================================================================= */ +template +inline void Product::AcceptingComponentFinder::beginComponent + (const typename Product::size_type, + const typename Product::size_type) +/* ---------------------------------------------------------------------------- + * + * Description: Tests whether the maximal strongly connected component that + * is about to be extracted from the product is an accepting + * component. If this is the case, `this->construct_component' + * is set to true to cause all states referred to in subsequent + * calls to `this->insert' that occur before the next call to + * `this->endComponent' to be inserted into `this->scc'. + * + * A component is accepting iff `this->acceptance_sets' points + * to a bit vector in which all bits are set to 1. + * + * Arguments: The arguments are required to support the expected class + * interface. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + scc.clear(); + /* + * The dfs search guarantees (by having made a call to + * AcceptanceTracker::addNodeToComponent before calling this function) that + * `this->acceptance_sets' is either equal to 0 (in which case the component + * to be extracted is trivial), or it points to the acceptance sets + * associated with the component to be extracted. + */ + construct_component = (this->acceptance_sets != 0 + && this->acceptance_sets->count + (this->number_of_acceptance_sets) + == this->number_of_acceptance_sets); +} + +/* ========================================================================= */ +template +inline void Product::AcceptingComponentFinder::insert + (const typename Product::size_type state) +/* ---------------------------------------------------------------------------- + * + * Description: If `this->construct_component == true', inserts a product + * state identifier to the set of identifiers representing an + * accepting maximal strongly connected component. Discards the + * state otherwise. + * + * Argument: state -- Identifier of a product state. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + if (construct_component) + scc.insert(state); +} + +} + +#endif /* !PRODUCT_H */ diff --git a/lbtt/src/SccCollection.h b/lbtt/src/SccCollection.h new file mode 100644 index 000000000..ea31ac97f --- /dev/null +++ b/lbtt/src/SccCollection.h @@ -0,0 +1,1174 @@ +/* + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 + * Heikki Tauriainen + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef SCCCOLLECTION_H +#define SCCCOLLECTION_H + +#include +#include +#include +#include +#include +#include +#include "EdgeContainer.h" +#include "LbttAlloc.h" +#include "Graph.h" + +using namespace std; + +namespace Graph +{ + +/****************************************************************************** + * + * Interface for a "node visitor" object that provides implementations of + * callback functions to be used during a depth-first search for strongly + * connected components. The VisitorInterface class provides empty + * implementations for the operations; visitor objects can be defined by making + * them inherit the VisitorInterface and then overriding some (or all) of the + * interface functions. + * + * A "node visitor" object must always implement the following type definition + * and operation: + * + * SccType + * A type definition for storing data associated with a maximal strongly + * connected component. This data type should support copying and + * assignment; no other assumptions are made about the internals of this + * data type. It is intended that a node visitor object has a member of + * type SccType (which need not be public); this member can then be + * manipulated freely during the search for strongly connected + * components by giving implementations for the other interface + * functions listed below. + * + * const SccType& operator()() + * Function for accessing the data associated with a maximal strongly + * connected component. This function is called when dereferencing a + * strongly connected component iterator (see below). + * + * In addition, a node visitor can overload the default (empty) implementations + * for the following operations: + * + * void enter(typename GraphType::size_type node_id) + * This function is called whenever the search enters a new node of the + * graph, using the identifier of the node as a parameter. + * + * void backtrack + * (typename GraphType::size_type source_id, + * const typename GraphType::Edge& edge, + * typename GraphType::size_type target_id) + * This function is called when the search backtracks from a node with + * the identifier `target_id' to the node `source_id'. `edge' is a + * reference to the edge between graph nodes identified by `source_id' + * and `target_id'. + * + * void touch + * (typename GraphType::size_type source_id, + * const typename GraphType::Edge& edge, + * typename GraphType::size_type target_id) + * This function is called when the search (in node `source_id') + * encounters an edge (`edge') with a target node (with identifier + * `target_id') that has already been visited during the search. + * + * void leave(typename GraphType::size_type node_id) + * This function is called when the search leaves a node with the + * identifier `node_id'. + * + * void addEdgeToComponent + * (const typename GraphType::Edge& edge, + * typename GraphType::size_type component_id) + * This function is called when the search finds an edge belonging to a + * strongly connected component of the graph (i.e., an edge between two + * states in the component). The function is called with a constant + * reference to the edge and a strongly connected component identifier + * as parameters. + * + * void addNodeToComponent + * (typename GraphType::size_type node_id, + * typename GraphType::size_type component_id) + * This function is called when the search finds a state belonging to a + * strongly connected component of the graph. + * + * void beginComponent + * (typename GraphType::size_type component_id, + * typename GraphType::size_type component_root_id) + * This function is called when the SCC algorithm is about to extract a + * new maximal strongly connected component from the graph. Here, + * `component_id' corresponds to the identifier of the component, and + * `component_root_id' is the identifier of the node in the component + * which was first encountered during the search (the depth-first search + * is about to backtrack from this node). + * + * void insert(typename GraphType::size_type node_id) + * After a call to `beginComponent', the SCC search algorithm will call + * `insert' for each identifier of a node in the maximal strongly + * connected component. + * + * void endComponent(typename GraphType::size_type component_id) + * This function is called after the SCC algorithm has finished + * `insert''ing nodes into a maximal strongly connected component. The + * component identifier is given as a parameter. + * + *****************************************************************************/ + +template +class VisitorInterface +{ +public: + VisitorInterface(); /* Constructor. */ + + /* default copy constructor */ + + virtual ~VisitorInterface(); /* Destructor. */ + + /* default assignment operator */ + + virtual void enter /* Interface operations. */ + (const typename GraphType::size_type); + + virtual void backtrack + (const typename GraphType::size_type, + const typename GraphType::Edge&, + const typename GraphType::size_type); + + virtual void touch + (const typename GraphType::size_type, + const typename GraphType::Edge&, + const typename GraphType::size_type); + + virtual void leave + (const typename GraphType::size_type); + + virtual void addEdgeToComponent + (const typename GraphType::Edge&, + const typename GraphType::size_type); + + virtual void addNodeToComponent + (const typename GraphType::size_type, + const typename GraphType::size_type); + + virtual void beginComponent + (const typename GraphType::size_type, + const typename GraphType::size_type); + + virtual void insert + (const typename GraphType::size_type); + + virtual void endComponent + (const typename GraphType::size_type); +}; + + + +/****************************************************************************** + * + * A template class defining a node visitor for collecting the identifiers of + * nodes in a maximal strongly connected component into a set. + * + *****************************************************************************/ + +template +class SccCollector : public VisitorInterface +{ +public: + SccCollector(); /* Constructor. */ + + /* default copy constructor */ + + ~SccCollector(); /* Destructor. */ + + /* default assignment operator */ + + typedef set, /* a set of node id's. */ + ALLOC(typename GraphType::size_type) > + SccType; + + const SccType& operator()() const; /* Returns the set of node + * identifiers in a + * maximal strongly + * connected component. + */ + + /* `enter' inherited */ + + /* `backtrack' inherited */ + + /* `touch' inherited */ + + /* `leave' inherited */ + + /* `addEdgeToComponent' inherited */ + + /* `addNodeToComponent' inherited */ + + void beginComponent /* Function called */ + (const typename GraphType::size_type, /* before inserting */ + const typename GraphType::size_type); /* nodes in a component. */ + + void insert /* Function for */ + (const typename GraphType::size_type node_id); /* inserting nodes into + * a component. + */ + + /* `endComponent' inherited */ + +private: + SccType scc; /* A set of node + * identifiers representing + * a maximal strongly + * connected component. + */ +}; + + + +/****************************************************************************** + * + * A template class for defining a "container" of maximal strongly connected + * components of a graph. The template should be instantiated with two classes + * GraphType and NodeVisitor, where the NodeVisitor type should support the + * interface required of a node visitor (see the above documentation of + * VisitorInterface), and GraphType (which defaults to + * Graph) should support the following type definitions and + * operations: + * + * size_type + * A type that can be used for identifying nodes (uniquely) in the + * graph. This type should have a constructor taking no parameters, + * and it should support copying, assignment, and comparison using the + * `less than' operator. + * + * EdgeContainerType + * A type that represents a container of objects behaving similarly to + * pointers to edges in the graph. This type is expected to have an + * STL-like container interface with `begin()' and `end()' operations + * and a const_iterator with a constructor having no parameters and + * copying and assignment operations. + * + * The objects in the container should behave similarly to pointers + * that can be dereferenced using the * and -> operators to get access + * to objects (the edges in the graph, e.g., + * Graph::Edge) that support the operation + * size_type targetNode() + * that returns the identifier of an edge's target node in the graph. + * + * PathElement + * An object for representing (node_id, edge) pairs of the graph. + * The object should provide a constructor that can be called with two + * parameters: a parameter of type `size_type' and another parameter + * corresponding to the type obtained by dereferencing a pointer-like + * object stored in an object of type EdgeContainerType. + * + * Path + * A type that supports the `clear' operation (with no arguments) and + * the `push_front' operation with an argument of type PathElement. + * + * operator[](size_type node_id) + * This function should return an object that provides access to the + * edges starting from the graph node identified by `node_id' through + * the member function + * const EdgeContainerType& edges(), + * which returns the collection of (pointer-like objects to) edges + * beginning from the graph node with the identifier `node_id'. + * + * bool empty() + * This function should return true iff the graph contains no nodes. + * + * The NodeVisitor type defaults to SccCollector. + * + * The SccCollection class provides the following operations: + * + * SccCollection(const GraphType& g, NodeVisitor& node_visitor) + * Constructor that binds the SccCollection object to the graph `g' + * using the operations defined by `node_visitor' when visiting nodes + * of the graph during the search for strongly connected components. + * + * iterator begin(const typename GraphType::size_type initial_node_id) + * Returns an iterator to the strongly connected components of the + * graph. The function returns an iterator that points to the "first" + * maximal strongly connected component of the graph when starting the + * search for maximal strongly connected components from the node with + * the identifier `initial_node_id'. + * + * Note: If `initial_node_id' belongs to a strongly connected component + * of the graph which has alredy been visited by an iterator obtained + * by a previous call to `begin', the returned iterator is equal to + * `end()'. If this is not desired (i.e., if you wish to repeat the + * search for strongly connected components in a part of the graph, + * initialize a new SccCollection to the graph). + * + * iterator end() + * Returns an iterator pointing "past the end" of the collection of + * strongly connected components. + * + * The class `SccCollection::iterator' provides standard dereferencing and + * prefix and postfix increment operators. It also supports comparison for + * equality and inequality. See below for more detailed information. + * + *****************************************************************************/ + +template , + class NodeVisitor = SccCollector > +class SccCollection +{ +public: + SccCollection /* Constructor. */ + (const GraphType& g, + NodeVisitor& node_visitor); + + ~SccCollection(); /* Destructor. */ + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + typedef map, /* identifiers and the */ + ALLOC(typename GraphType::size_type) >/* order in which they */ + DfsOrdering; /* were encountered in + * the search for + * strongly connected + * components. + */ + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + class iterator /* Iterator for */ + { /* accessing the maximal + * strongly connected + * components. + */ + public: + iterator(); /* Default constructor. */ + + /* default copy constructor */ + + ~iterator(); /* Destructor. */ + + /* default assignment operator */ + + bool operator==(const iterator& it) const; /* Comparison functions. */ + bool operator!=(const iterator& it) const; + + const typename NodeVisitor::SccType& /* Dereferencing */ + operator*() const; /* operators. */ + const typename NodeVisitor::SccType* + operator->() const; + + const typename NodeVisitor::SccType& /* Prefix and postfix */ + operator++(); /* increment operators. */ + const typename NodeVisitor::SccType + operator++(int); + + void getPath(typename GraphType::Path& path); /* Function for accessing + * the path from the + * initial node of the + * search to a node in + * the most recently found + * strongly connected + * component. + */ + + private: + iterator /* Constructor. */ + (const GraphType& g, + NodeVisitor& node_visitor, + DfsOrdering& ordering); + + void initialize /* Instructs the */ + (const typename GraphType::size_type /* iterator to continue */ + node_id); /* the search for + * strongly connected + * components from a + * given node. + */ + + const GraphType& graph; /* Reference to the graph + * with which the iterator + * is associated. + */ + + NodeVisitor& visitor; /* Reference to an object + * that provides + * implementations for the + * functions listed in the + * VisitorInterface class. + */ + + DfsOrdering& dfs_ordering; /* Mapping between node + * identifiers and their + * dfs numbers. + */ + + typename GraphType::size_type initial_node; /* Node from which the + * search was started. + */ + + typename GraphType::size_type dfs_number; /* Number of graph nodes + * processed by the + * iterator. + */ + + struct NodeStackElement /* Structure for */ + { /* storing information */ + typename GraphType::size_type id; /* needed for */ + typename GraphType::EdgeContainerType /* backtracking during */ + ::const_iterator edge; /* the search. */ + typename GraphType::size_type lowlink; + }; + + deque /* backtracking stack. */ + node_stack; + + NodeStackElement* current_node; /* Pointer to the top + * element of the + * backtracking stack. + */ + + deque /* collecting the nodes */ + scc_stack; /* in a strongly + * connected component, + * excluding the root + * nodes of the + * components. + */ + + void computeNextScc(); /* Updates the iterator to + * point to the next + * strongly connected + * component. + */ + + friend class SccCollection; + }; + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + iterator begin /* Returns an iterator */ + (const typename GraphType::size_type /* pointing to the */ + node_id); /* "first" maximal + * strongly connected + * component reachable + * from a given node. + */ + + const iterator& end() const; /* Returns an iterator + * pointing past the "end" + * of the maximal strongly + * connected components. + */ + +private: + SccCollection(const SccCollection&); /* Prevent copying and */ + SccCollection& operator=(const SccCollection&); /* assignment of + * SccCollection + * objects. + */ + + const GraphType& graph; /* Reference to the graph + * associated with the + * container. + */ + + NodeVisitor& visitor; /* Reference to an object + * that provides + * implementations for + * callback operations + * needed during the + * search for strongly + * connected components. + */ + + const iterator end_iterator; /* Iterator pointing past + * the "end" of the + * collection of strongly + * connected components in + * the graph. This + * iterator is special by + * having both + * `initial_node' and + * `dfs_number' set to 0. + */ + + DfsOrdering dfs_ordering; /* Mapping between node + * identifiers and their + * visiting order during + * the search for strongly + * connected components. + */ +}; + + + +/****************************************************************************** + * + * Inline function definitions for template class VisitorInterface. This class + * provides default empty implementations for node visitor operations. + * + *****************************************************************************/ + +template +inline VisitorInterface::VisitorInterface() +{ +} + +template +inline VisitorInterface::~VisitorInterface() +{ +} + +template +inline void VisitorInterface::enter + (const typename GraphType::size_type) +{ +} + +template +inline void VisitorInterface::backtrack + (const typename GraphType::size_type, const typename GraphType::Edge&, + const typename GraphType::size_type) +{ +} + +template +inline void VisitorInterface::touch + (const typename GraphType::size_type, const typename GraphType::Edge&, + const typename GraphType::size_type) +{ +} + +template +inline void VisitorInterface::leave + (const typename GraphType::size_type) +{ +} + +template +inline void VisitorInterface::addEdgeToComponent + (const typename GraphType::Edge&, const typename GraphType::size_type) +{ +} + +template +inline void VisitorInterface::addNodeToComponent + (const typename GraphType::size_type, const typename GraphType::size_type) +{ +} + +template +inline void VisitorInterface::beginComponent + (const typename GraphType::size_type, const typename GraphType::size_type) +{ +} + +template +inline void VisitorInterface::insert + (const typename GraphType::size_type) +{ +} + +template +inline void VisitorInterface::endComponent + (const typename GraphType::size_type) +{ +} + + + +/****************************************************************************** + * + * Inline function definitions for template class SccCollector. + * + *****************************************************************************/ + +/* ========================================================================= */ +template +inline SccCollector::SccCollector() +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class SccCollector. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline SccCollector::~SccCollector() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class SccCollector. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline const typename SccCollector::SccType& +SccCollector::operator()() const +/* ---------------------------------------------------------------------------- + * + * Description: Returns the set of identifiers of nodes in the maximal + * strongly connected component. + * + * Arguments: None. + * + * Returns: A constant reference to the set of identifiers of nodes in + * the component. + * + * ------------------------------------------------------------------------- */ +{ + return scc; +} + +/* ========================================================================= */ +template +inline void SccCollector::beginComponent + (const typename GraphType::size_type, const typename GraphType::size_type) +/* ---------------------------------------------------------------------------- + * + * Description: Clears the set of node identifiers to make it empty before + * filling it with (identifiers of) nodes of a new maximal + * strongly connected component. + * + * Arguments: The arguments are needed to support the expected function + * calling interface. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + scc.clear(); +} + +/* ========================================================================= */ +template +inline void SccCollector::insert + (const typename GraphType::size_type node_id) +/* ---------------------------------------------------------------------------- + * + * Description: Inserts an identifier into the set of node identifiers in a + * maximal strongly connected component. + * + * Arguments: node_id -- Node identifier. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + scc.insert(node_id); +} + + + +/****************************************************************************** + * + * Inline function definitions for template class SccCollection. + * + *****************************************************************************/ + +/* ========================================================================= */ +template +inline SccCollection::SccCollection + (const GraphType& g, NodeVisitor& node_visitor) : + graph(g), visitor(node_visitor), end_iterator(g, node_visitor, dfs_ordering) +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class SccCollection. + * + * Arguments: g -- A constant reference to an object of type + * GraphType. See above for the description + * of the interface that this object should + * support. + * node_visitor -- A reference to an object that provides + * node visiting operations. See the + * documentation of VisitorInterface for more + * information. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline SccCollection::~SccCollection() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class SccCollection. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline typename SccCollection::iterator +SccCollection::begin + (const typename GraphType::size_type node_id) +/* ---------------------------------------------------------------------------- + * + * Description: Returns an iterator pointing to the "first" strongly + * connected component reachable from a given node of + * `this->graph'. + * + * Argument: node_id -- Identifier of the node. + * + * Returns: An iterator pointing to the "first" strongly connected + * component reachable from the node, or an iterator equal to + * `this->end()' if the node with the identifier `node_id' has + * already been included in a strongly connected component + * returned by another iterator to the same collection of + * strongly connected components. + * + * ------------------------------------------------------------------------- */ +{ + iterator it(graph, visitor, dfs_ordering); + it.initialize(node_id); + return it; +} + +/* ========================================================================= */ +template +inline const typename SccCollection::iterator& +SccCollection::end() const +/* ---------------------------------------------------------------------------- + * + * Description: Returns an iterator pointing past the "end" of the collection + * of strongly connected components in `this->graph'. + * + * Arguments: None. + * + * Returns: A constant reference to `this->end_iterator'. + * + * ------------------------------------------------------------------------- */ +{ + return end_iterator; +} + + + +/****************************************************************************** + * + * Inline function definitions for template class SccCollection::iterator. + * + *****************************************************************************/ + +/* ========================================================================= */ +template +inline SccCollection::iterator::iterator + (const GraphType& g, NodeVisitor& node_visitor, + SccCollection::DfsOrdering& ordering) : + graph(g), visitor(node_visitor), dfs_ordering(ordering), initial_node(0), + dfs_number(0) +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class + * SccCollection::iterator. Initializes + * a new iterator for scanning the maximal strongly connected + * components of a graph. + * + * Arguments: g -- The graph with which the iterator is to be + * associated. + * node_visitor -- A reference to a object that implements + * callback functions to be invoked during the + * search for strongly connected components. + * ordering -- A reference to a mapping between node + * identifiers and their dfs numbers. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline SccCollection::iterator::~iterator() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class + * SccCollection::iterator. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +template +inline bool SccCollection::iterator::operator== + (const SccCollection::iterator& it) const +/* ---------------------------------------------------------------------------- + * + * Description: Equality comparison between two SccCollection::iterators. Two + * iterators are equal iff they have the same initial state and + * they have visited the same amount of graph nodes. + * + * Argument: it -- A constant reference to an iterator. + * + * Returns: true iff the iterators are equal. + * + * ------------------------------------------------------------------------- */ +{ + return (it.initial_node == initial_node && it.dfs_number == dfs_number); +} + +/* ========================================================================= */ +template +inline bool SccCollection::iterator::operator!= + (const SccCollection::iterator& it) const +/* ---------------------------------------------------------------------------- + * + * Description: Inequality comparison between two SccCollection::iterators. + * Two iterators are not equal iff they have different initial + * states or if they have visited different numbers of graph + * nodes. + * + * Argument: it -- A constant reference to an iterator. + * + * Returns: true iff the iterators are not equal. + * + * ------------------------------------------------------------------------- */ +{ + return (it.initial_node != initial_node || it.dfs_number != dfs_number); +} + +/* ========================================================================= */ +template +inline const typename NodeVisitor::SccType& +SccCollection::iterator::operator*() const +/* ---------------------------------------------------------------------------- + * + * Description: Dereferencing operator for class + * SccCollection::iterator. Returns the + * data associated with the maximal strongly connected component + * currently pointed to by the iterator. + * + * Arguments: None. + * + * Returns: A constant reference to the data associated with the strongly + * connected component. + * + * ------------------------------------------------------------------------- */ +{ + return visitor(); +} + +/* ========================================================================= */ +template +inline const typename NodeVisitor::SccType* +SccCollection::iterator::operator->() const +/* ---------------------------------------------------------------------------- + * + * Description: Dereferencing operator for class + * SccCollection::iterator. Returns a + * pointer to the data associated with the maximal strongly + * connected component currently pointed to by the iterator. + * + * Arguments: None. + * + * Returns: A constant pointer to the data associated with the strongly + * connected component. + * + * ------------------------------------------------------------------------- */ +{ + return &visitor(); +} + +/* ========================================================================= */ +template +inline const typename NodeVisitor::SccType& +SccCollection::iterator::operator++() +/* ---------------------------------------------------------------------------- + * + * Description: Prefix increment operator for class + * SccCollection::iterator. Computes + * the next maximal strongly connected component of + * `this->graph' and then returns the data associated with it. + * + * Arguments: None. + * + * Returns: A reference to the data associated with the maximal strongly + * connected component found by incrementing the iterator. + * + * ------------------------------------------------------------------------- */ +{ + computeNextScc(); + return visitor(); +} + +/* ========================================================================= */ +template +inline const typename NodeVisitor::SccType +SccCollection::iterator::operator++(int) +/* ---------------------------------------------------------------------------- + * + * Description: Postfix increment operator for class + * SccCollection::iterator. Computes + * the next strongly connected component of + * `this->graph', but returns the data associated with the + * strongly connected component that the iterator pointed to + * _before_ this operation. + * + * Arguments: None (the `int' is only required to distinguish this operator + * from the prefix increment operator). + * + * Returns: The data associated with the maximal strongly connected + * component pointed to by the iterator before incrementing it. + * + * ------------------------------------------------------------------------- */ +{ + const typename NodeVisitor::SccType old_scc = visitor(); + computeNextScc(); + return old_scc; +} + + + +/****************************************************************************** + * + * Function definitions for template class + * SccCollection. + * + *****************************************************************************/ + +/* ========================================================================= */ +template +void SccCollection::iterator::getPath + (typename GraphType::Path& path) +/* ---------------------------------------------------------------------------- + * + * Description: Constructs a path from the initial state of the search for + * the strongly connected components to a node in the most + * recently found maximal strongly connected graph component. + * + * Argument: path -- A reference to a GraphType::Path object for storing + * the (node_id, edge) pairs in the path. + * + * Returns: Nothing. Assuming that `path' is a standard STL container + * type object with the `push_front' operation, `*path.begin()' + * will correspond to the first element on the path after the + * call. + * + * ------------------------------------------------------------------------- */ +{ + path.clear(); + + /* + * When this function is called after extracting a maximal strongly connected + * component from the graph, `node_stack.front()' corresponds to the root + * node of the component. This node will not be included in the path + * (that is, the edge component of `path.back()' will point to this node + * when exiting from this function). + */ + + typename deque::const_iterator + n = node_stack.begin(); + if (n != node_stack.end()) + { + for (++n; n != node_stack.end(); ++n) + { + const typename GraphType::PathElement element(n->id, **n->edge); + path.push_front(element); + } + } +} + +/* ========================================================================= */ +template +void SccCollection::iterator::initialize + (const typename GraphType::size_type node_id) +/* ---------------------------------------------------------------------------- + * + * Description: Initializes a + * SccCollection::iterator for scanning + * the maximal strongly connected components of `this->graph'. + * + * Argument: node_id -- Identifier of a graph node from which to start + * the search for maximal strongly connected + * components. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + node_stack.clear(); + scc_stack.clear(); + + /* + * If `node_id' is an identifier of a node that has not yet been visited, + * make the iterator point to the next strongly connected component of + * `this->graph'. + */ + + if (!graph.empty() && dfs_ordering.find(node_id) == dfs_ordering.end()) + { + initial_node = node_id; + visitor.enter(node_id); + dfs_ordering[node_id] = dfs_number = 1; + const NodeStackElement element + = { node_id, graph[node_id].edges().begin(), 1 }; + node_stack.push_front(element); + current_node = &node_stack.front(); + computeNextScc(); + } +} + +/* ========================================================================= */ +template +void SccCollection::iterator::computeNextScc() +/* ---------------------------------------------------------------------------- + * + * Description: Makes an SccCollection::iterator + * point to the "next" maximal strongly connected component of + * `this->graph', using an algorithm based on the depth-first + * search algorithm of Tarjan + * [R. J. Tarjan. Depth-first search and linear graph + * algorithms. SIAM Journal on Computing 1(2):146--160, + * 1972] + * for computing the maximal strongly connected components of + * the graph. + * + * The implementation includes the optimization in the first + * improved algorithm found in + * [E. Nuutila and E. Soisalon-Soininen. On finding the + * strongly connected components in a directed graph. + * Information Processing Letters 49(1):9--14, 1994]. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + if (current_node->lowlink == 0) + { + /* Backtrack from the root of an SCC that was extracted from the graph */ + const typename GraphType::size_type child_node = current_node->id; + node_stack.pop_front(); + if (node_stack.empty()) + { + initial_node = dfs_number = 0; + return; + } + current_node = &node_stack.front(); + visitor.backtrack(current_node->id, **current_node->edge, child_node); + ++current_node->edge; /* prepare to process the next edge */ + } + +next_edge: + while (current_node->edge != graph[current_node->id].edges().end()) + { + const typename GraphType::size_type child_node + = (*current_node->edge)->targetNode(); + const typename DfsOrdering::const_iterator child_dfs_number_finder + = dfs_ordering.find(child_node); + + if (child_dfs_number_finder == dfs_ordering.end()) /* child not visited */ + { + ++dfs_number; + dfs_ordering[child_node] = dfs_number; + visitor.enter(child_node); + const NodeStackElement element + = { child_node, graph[child_node].edges().begin(), dfs_number }; + node_stack.push_front(element); + current_node = &node_stack.front(); + goto next_edge; + } + + visitor.touch(current_node->id, **current_node->edge, child_node); + if (child_dfs_number_finder->second != 0) /* child in the same SCC */ + { + if (child_dfs_number_finder->second < current_node->lowlink) + current_node->lowlink = child_dfs_number_finder->second; + visitor.addEdgeToComponent(**current_node->edge, current_node->lowlink); + } + + ++current_node->edge; + } + + visitor.addNodeToComponent(current_node->id, current_node->lowlink); + visitor.leave(current_node->id); + + if (dfs_ordering.find(current_node->id)->second != current_node->lowlink) + { + scc_stack.push_front(current_node->id); + + /* Backtrack from a node into a node in the same SCC */ + const typename GraphType::size_type child_node = current_node->id; + const typename GraphType::size_type child_lowlink = current_node->lowlink; + node_stack.pop_front(); + current_node = &node_stack.front(); + if (current_node->lowlink > child_lowlink) + current_node->lowlink = child_lowlink; + visitor.backtrack(current_node->id, **current_node->edge, child_node); + visitor.addEdgeToComponent(**current_node->edge, current_node->lowlink); + ++current_node->edge; /* prepare to process the next edge */ + goto next_edge; + } + + /* + * `current_node' is a root of a maximal strongly connected graph component. + * Extract the component from the graph. + */ + + visitor.beginComponent(current_node->lowlink, current_node->id); + visitor.insert(current_node->id); + dfs_ordering.find(current_node->id)->second = 0; + while (!scc_stack.empty()) + { + const typename GraphType::size_type node = scc_stack.front(); + typename GraphType::size_type& node_dfs_number + = dfs_ordering.find(node)->second; + if (node_dfs_number > current_node->lowlink) + { + scc_stack.pop_front(); + visitor.insert(node); + node_dfs_number = 0; + } + else + break; + } + visitor.endComponent(current_node->lowlink); + current_node->lowlink = 0; +} + +} + +#endif /* !SCCCOLLECTION_H */ diff --git a/lbtt/src/StatDisplay.cc b/lbtt/src/StatDisplay.cc index 0d1074d49..8d6250626 100644 --- a/lbtt/src/StatDisplay.cc +++ b/lbtt/src/StatDisplay.cc @@ -21,6 +21,7 @@ #include #include "DispUtil.h" #include "Exception.h" +#include "IntervalList.h" #include "SharedTestData.h" #include "StatDisplay.h" #include "StringUtil.h" @@ -33,6 +34,47 @@ using namespace ::DispUtil; using namespace ::SharedTestData; using namespace ::StringUtil; +/* ========================================================================= */ +void printStatTableHeader(ostream& stream, int indent) +/* ---------------------------------------------------------------------------- + * + * Description: Displays a table header for test statistics (used in + * verbosity mode 2). + * + * Arguments: stream -- A reference to the output stream to which the + * header should be written. + * indent -- Number of spaces to leave on the left of the + * output. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + Exceptional_ostream estream(&stream, ios::failbit | ios::badbit); + + int num_dashes = 39; + estream << string(indent, ' ') + " # F Elapsed Büchi Büchi Acc."; + if (configuration.global_options.do_cons_test + || configuration.global_options.do_comp_test) + { + num_dashes += 31; + estream << " Product Product Acc."; + if (configuration.global_options.do_cons_test) + { + num_dashes += 3; + estream << " CC"; + } + } + estream << '\n' + string(indent + 10, ' ') + + "time states trans. sets"; + if (configuration.global_options.do_cons_test + || configuration.global_options.do_comp_test) + estream << " states trans. cycles"; + estream << "\n" + string(indent, ' ') + string(num_dashes, '-') + '\n'; + estream.flush(); +} + + /* ========================================================================= */ void printBuchiAutomatonStats (ostream& stream, int indent, @@ -48,7 +90,7 @@ void printBuchiAutomatonStats * Arguments: stream -- A reference to the output stream to which the * information should be written. * indent -- Number of spaces to leave on the left of the - * output. + * output in verbosity modes >= 3. * algorithm -- Identifier of the algorithm used for * generating the automaton. * result_id -- Selects between the automata constructed from @@ -63,35 +105,68 @@ void printBuchiAutomatonStats const AutomatonStats& automaton_stats = test_results[algorithm].automaton_stats[result_id]; - estream << string(indent, ' '); - - if (!automaton_stats.buchiAutomatonComputed()) - estream << "not computed"; + if (configuration.global_options.verbosity <= 2) + { + if (!automaton_stats.buchiAutomatonComputed()) + estream << " N/A N/A N/A N/A"; + else + { + if (automaton_stats.buchi_generation_time >= 0.0) + { + changeStreamFormatting(stream, 9, 2, ios::fixed | ios::right); + estream << automaton_stats.buchi_generation_time; + restoreStreamFormatting(stream); + } + else + estream << " N/A"; + estream << ' '; + changeStreamFormatting(stream, 9, 0, ios::right); + estream << automaton_stats.number_of_buchi_states; + restoreStreamFormatting(stream); + estream << ' '; + changeStreamFormatting(stream, 9, 0, ios::right); + estream << automaton_stats.number_of_buchi_transitions; + restoreStreamFormatting(stream); + estream << ' '; + changeStreamFormatting(stream, 4, 0, ios::right); + estream << automaton_stats.number_of_acceptance_sets; + restoreStreamFormatting(stream); + } + estream << ' '; + } else { - estream << "number of states:" + string(6, ' ') - + toString(automaton_stats.number_of_buchi_states) - + '\n' + string(indent, ' ') + "number of transitions: " - + toString(automaton_stats.number_of_buchi_transitions) - + '\n' + string(indent, ' ') + "acceptance sets:" - + string(7, ' ') - + toString(automaton_stats.number_of_acceptance_sets) - + '\n' + string(indent, ' ') + "computation time:" - + string(6, ' '); - - if (automaton_stats.buchi_generation_time != -1.0) - { - changeStreamFormatting(stream, 9, 2, ios::fixed | ios::left); - estream << automaton_stats.buchi_generation_time; - restoreStreamFormatting(stream); + estream << string(indent, ' '); - estream << " seconds (user time)"; - } + if (!automaton_stats.buchiAutomatonComputed()) + estream << "not computed"; else - estream << "N/A"; + { + estream << "number of states:" + string(6, ' ') + + toString(automaton_stats.number_of_buchi_states) + + '\n' + string(indent, ' ') + "number of transitions: " + + toString(automaton_stats.number_of_buchi_transitions) + + '\n' + string(indent, ' ') + "acceptance sets:" + + string(7, ' ') + + toString(automaton_stats.number_of_acceptance_sets) + + '\n' + string(indent, ' ') + "computation time:" + + string(6, ' '); + + if (automaton_stats.buchi_generation_time != -1.0) + { + changeStreamFormatting(stream, 9, 2, ios::fixed | ios::left); + estream << automaton_stats.buchi_generation_time; + restoreStreamFormatting(stream); + + estream << " seconds (user time)"; + } + else + estream << "N/A"; + } + + estream << '\n'; } - estream << '\n'; estream.flush(); } @@ -110,7 +185,7 @@ void printProductAutomatonStats * Arguments: stream -- A reference to the output stream to which the * information should be written. * indent -- Number of spaces to leave on the left of the - * output. + * output in verbosity modes >= 3. * algorithm -- Identifier of the algorithm used for * generating the product automaton. * result_id -- Selects between the automata constructed from @@ -125,43 +200,64 @@ void printProductAutomatonStats const AutomatonStats& automaton_stats = test_results[algorithm].automaton_stats[result_id]; - estream << string(indent, ' '); - - if (!automaton_stats.productAutomatonComputed()) - estream << "not computed"; + if (configuration.global_options.verbosity <= 2) + { + if (!automaton_stats.productAutomatonComputed()) + estream << " N/A N/A"; + else + { + changeStreamFormatting(stream, 11, 0, ios::right); + estream << automaton_stats.number_of_product_states; + restoreStreamFormatting(stream); + estream << ' '; + changeStreamFormatting(stream, 11, 0, ios::right); + estream << automaton_stats.number_of_product_transitions; + restoreStreamFormatting(stream); + } + estream << ' '; + } else { - estream << "number of states:" + string(6, ' '); + estream << string(indent, ' '); - changeStreamFormatting(stream, 9, 0, ios::left); - estream << automaton_stats.number_of_product_states; - restoreStreamFormatting(stream); - - estream << " ["; - - if (automaton_stats.number_of_product_states != 0) + if (!automaton_stats.productAutomatonComputed()) + estream << "not computed"; + else { - changeStreamFormatting(stream, 0, 2, ios::fixed); - estream << static_cast - (automaton_stats.number_of_product_states) - / static_cast(automaton_stats.number_of_buchi_states) - / static_cast(round_info.statespace->size()) - * 100.0; + estream << "number of states:" + string(6, ' '); + + changeStreamFormatting(stream, 9, 0, ios::left); + estream << automaton_stats.number_of_product_states; restoreStreamFormatting(stream); - estream << "% of worst case (" - << automaton_stats.number_of_buchi_states - * round_info.statespace->size() - << ')'; - } - else - estream << "empty automaton"; + estream << " ["; - estream << "]\n" + string(indent, ' ') + "number of transitions: " - + toString(automaton_stats.number_of_product_transitions); + if (automaton_stats.number_of_product_states != 0) + { + changeStreamFormatting(stream, 0, 2, ios::fixed); + estream << static_cast + (automaton_stats.number_of_product_states) + / static_cast + (automaton_stats.number_of_buchi_states) + / static_cast(round_info.statespace->size()) + * 100.0; + restoreStreamFormatting(stream); + + estream << "% of worst case (" + << automaton_stats.number_of_buchi_states + * round_info.statespace->size() + << ')'; + } + else + estream << "empty automaton"; + + estream << "]\n" + string(indent, ' ') + "number of transitions: " + + toString(automaton_stats.number_of_product_transitions); + } + + estream << '\n'; } - estream << '\n'; estream.flush(); } @@ -181,7 +277,7 @@ void printAcceptanceCycleStats * Arguments: stream -- A reference to the output stream to which the * information should be written. * indent -- Number of spaces to leave on the left of the - * output. + * output in verbosity mode >= 3. * algorithm -- Identifier of the algorithm used for * computing the Büchi automaton whose accepting * cycles are to be considered. @@ -197,41 +293,57 @@ void printAcceptanceCycleStats const AutomatonStats& automaton_stats = test_results[algorithm].automaton_stats[result_id]; - estream << string(indent, ' '); - - if (!automaton_stats.emptiness_check_performed) - estream << "not computed"; - else if (configuration.global_options.product_mode == Configuration::LOCAL) + if (configuration.global_options.verbosity <= 2) { - estream << string("cycle "); - - if (automaton_stats.emptiness_check_result[0]) - estream << "reachable "; + if (!automaton_stats.emptiness_check_performed) + estream << " N/A"; else - estream << "not reachable"; - - estream << " (from the initial state)"; + { + changeStreamFormatting(stream, 6, 0, ios::right); + estream << automaton_stats.emptiness_check_result.count(); + restoreStreamFormatting(stream); + } + estream << ' '; } else { - estream << "cycle reachable from "; + estream << string(indent, ' '); - changeStreamFormatting(stream, 9, 0, ios::left); - estream << automaton_stats.emptiness_check_result.count(); - restoreStreamFormatting(stream); + if (!automaton_stats.emptiness_check_performed) + estream << "not computed"; + else if (configuration.global_options.product_mode == Configuration::LOCAL) + { + estream << string("cycle "); - estream << " states\n" + string(indent, ' ') - + "not reachable from "; + if (automaton_stats.emptiness_check_result[0]) + estream << "reachable "; + else + estream << "not reachable"; - changeStreamFormatting(stream, 9, 0, ios::left); - estream << (round_info.real_emptiness_check_size - - automaton_stats.emptiness_check_result.count()); - restoreStreamFormatting(stream); + estream << " (from the initial state)"; + } + else + { + estream << "cycle reachable from "; - estream << " states"; + changeStreamFormatting(stream, 9, 0, ios::left); + estream << automaton_stats.emptiness_check_result.count(); + restoreStreamFormatting(stream); + + estream << " states\n" + string(indent, ' ') + + "not reachable from "; + + changeStreamFormatting(stream, 9, 0, ios::left); + estream << (round_info.real_emptiness_check_size + - automaton_stats.emptiness_check_result.count()); + restoreStreamFormatting(stream); + + estream << " states"; + } + + estream << '\n'; } - estream << '\n'; estream.flush(); } @@ -249,7 +361,7 @@ void printConsistencyCheckStats * Arguments: stream -- A reference to an output stream to which the * information should be written. * indent -- Number of spaces to leave on the left of the - * output. + * output in verbosity mode >= 3. * algorithm -- Identifier of the algorithm whose consistency * check result should be displayed. * @@ -260,100 +372,114 @@ void printConsistencyCheckStats Exceptional_ostream estream(&stream, ios::failbit | ios::badbit); const AlgorithmTestResults& test_result = test_results[algorithm]; - estream << string(indent, ' '); + if (configuration.global_options.verbosity <= 2) + { + switch (test_result.consistency_check_result) + { + case -1 : + estream << "NA"; + break; - if (test_result.consistency_check_result == -1) - estream << "not performed"; + case 0 : + estream << " F"; + break; + + default: + estream << " P"; + break; + } + } else { - estream << "result:" + string(18, ' '); + estream << string(indent, ' '); - if (test_result.consistency_check_result == 0) - { - estream << "failed [" - + toString(test_result.failed_consistency_check_comparisons) - + " ("; - - changeStreamFormatting(stream, 0, 2, ios::fixed); - estream << ((test_result.consistency_check_comparisons == 0) - ? 0.0 - : static_cast - (test_result.failed_consistency_check_comparisons) - / test_result.consistency_check_comparisons * 100.0); - restoreStreamFormatting(stream); - - estream << "%) of " - + toString(test_result.consistency_check_comparisons) - + " test cases]"; - } + if (test_result.consistency_check_result == -1) + estream << "not performed"; else - estream << "passed"; + { + estream << "result:" + string(18, ' '); + + if (test_result.consistency_check_result == 0) + { + estream << "failed [" + + toString(test_result.failed_consistency_check_comparisons) + + " ("; + + changeStreamFormatting(stream, 0, 2, ios::fixed); + estream << ((test_result.consistency_check_comparisons == 0) + ? 0.0 + : static_cast + (test_result.failed_consistency_check_comparisons) + / test_result.consistency_check_comparisons * 100.0); + restoreStreamFormatting(stream); + + estream << "%) of " + + toString(test_result.consistency_check_comparisons) + + " test cases]"; + } + else + estream << "passed"; + } + + estream << '\n'; } - estream << '\n'; estream.flush(); } /* ========================================================================= */ void printCrossComparisonStats - (ostream& stream, int indent, - vector::size_type - algorithm) + (ostream& stream, int indent, const IntervalList& algorithms) /* ---------------------------------------------------------------------------- * * Description: Displays information about the model checking result cross- * comparison check, extracting the information from a vector of * TestStatistics structures stored in the UserInterface object. * - * Arguments: stream -- A reference to an output stream to which the - * information should be written. - * indent -- Number of spaces to leave on the left of the - * output. - * algorithm -- If less than the number of algorithms, show - * only the results for the algorithm with this - * identifier. + * Arguments: stream -- A reference to an output stream to which the + * information should be written. + * indent -- Number of spaces to leave on the left of the + * output. + * algorithms -- A reference to a constant IntervalList + * storing the numeric identifiers of the + * algorithms for which the statistics should + * be shown. * * Returns: Nothing. * * ------------------------------------------------------------------------- */ { Exceptional_ostream estream(&stream, ios::failbit | ios::badbit); - bool no_errors_to_report = true; - - if (algorithm < configuration.algorithms.size() - && !configuration.algorithms[algorithm].enabled) - { - estream << string(indent, ' ') + "not performed\n\n"; - estream.flush(); - return; - } - - estream << string(indent, ' ') + "result:"; + bool no_errors_to_report = true, nothing_to_report = true; const AutomatonStats* alg_1_pos_results; const AutomatonStats* alg_1_neg_results; - for (vector::size_type - alg_1 = 0; - alg_1 < test_results.size(); - alg_1++) + for (IntervalList::const_iterator alg_1 = algorithms.begin(); + alg_1 != algorithms.end(); + ++alg_1) { - alg_1_pos_results = &test_results[alg_1].automaton_stats[0]; - alg_1_neg_results = &test_results[alg_1].automaton_stats[1]; + alg_1_pos_results = &test_results[*alg_1].automaton_stats[0]; + alg_1_neg_results = &test_results[*alg_1].automaton_stats[1]; for (vector::size_type - alg_2 = alg_1 + 1; - alg_2 < test_results.size(); + alg_2 = 0; + alg_2 < round_info.number_of_translators; alg_2++) { - if ((algorithm >= configuration.algorithms.size() - || alg_1 == algorithm - || alg_2 == algorithm) - && configuration.algorithms[alg_1].enabled + if (*alg_1 != alg_2 + && (alg_2 > *alg_1 || !algorithms.covers(alg_2)) + && configuration.algorithms[*alg_1].enabled && configuration.algorithms[alg_2].enabled) { bool pos_test, neg_test; + if (nothing_to_report) + { + nothing_to_report = false; + estream << string(indent, ' ') + "result:"; + } + for (int counter = 0; counter < 2; counter++) { if (counter == 0) @@ -381,89 +507,76 @@ void printCrossComparisonStats else estream << "-) "; - estream << ' '; - if (alg_1 == round_info.number_of_translators) - estream << "lbtt"; - else - estream << configuration.algorithmString(alg_1); - estream << ", "; - if (alg_2 == round_info.number_of_translators) - estream << "lbtt"; - else - estream << configuration.algorithmString(alg_2); + estream << " " + configuration.algorithmString(*alg_1) + ", " + + configuration.algorithmString(alg_2); } } } } } - if (no_errors_to_report) + if (nothing_to_report) + estream << string(indent, ' ') + "not performed"; + else if (no_errors_to_report) estream << string(20, ' ') + "no failures detected"; - estream << "\n\n"; estream.flush(); } /* ========================================================================= */ void printBuchiIntersectionCheckStats - (ostream& stream, int indent, - vector::size_type - algorithm) + (ostream& stream, int indent, const IntervalList& algorithms) /* ---------------------------------------------------------------------------- * * Description: Displays information about the Büchi automaton intersection * emptiness check results, extracting the information from a * TestStatistics structure stored in the UserInterface object. * - * Arguments: stream -- A reference to an output stream to which the - * information should be written. - * indent -- Number of spaces to leave on the left of the - * output. - * algorithm -- If less than the number of algorithms, show - * only the results for the algorithm with this - * identifier. + * Arguments: stream -- A reference to an output stream to which the + * information should be written. + * indent -- Number of spaces to leave on the left of the + * output. + * algorithms -- A reference to a constant IntervalList + * storing the numeric identifiers of the + * algorithms for which the statistics should + * be shown. * * Returns: Nothing. * * ------------------------------------------------------------------------- */ { Exceptional_ostream estream(&stream, ios::failbit | ios::badbit); - bool no_errors_to_report = true; - - if (algorithm < round_info.number_of_translators - && !configuration.algorithms[algorithm].enabled) - { - estream << string(indent, ' ') + "not performed\n\n"; - estream.flush(); - return; - } - - estream << string(indent, ' ') + "result:"; + bool no_errors_to_report = true, nothing_to_report = true; const AutomatonStats* alg_1_pos_results; const AutomatonStats* alg_1_neg_results; - for (vector::size_type - alg_1 = 0; - alg_1 < round_info.number_of_translators; - alg_1++) + for (IntervalList::const_iterator alg_1 = algorithms.begin(); + alg_1 != algorithms.end(); + ++alg_1) { - alg_1_pos_results = &test_results[alg_1].automaton_stats[0]; - alg_1_neg_results = &test_results[alg_1].automaton_stats[1]; + alg_1_pos_results = &test_results[*alg_1].automaton_stats[0]; + alg_1_neg_results = &test_results[*alg_1].automaton_stats[1]; for (vector::size_type - alg_2 = alg_1; + alg_2 = 0; alg_2 < round_info.number_of_translators; alg_2++) { - if ((algorithm >= round_info.number_of_translators - || alg_1 == algorithm - || alg_2 == algorithm) - && configuration.algorithms[alg_1].enabled - && configuration.algorithms[alg_2].enabled) + if (configuration.algorithms[*alg_1].enabled + && configuration.algorithms[alg_2].enabled + && (alg_2 >= *alg_1 || !algorithms.covers(alg_2)) + && !configuration.isInternalAlgorithm(*alg_1) + && !configuration.isInternalAlgorithm(alg_2)) { bool pos_test, neg_test; + if (nothing_to_report) + { + nothing_to_report = false; + estream << string(indent, ' ') + "result:"; + } + for (int counter = -1; counter < 1; counter++) { pos_test = (alg_1_pos_results->buchi_intersection_check_stats[alg_2] @@ -478,7 +591,7 @@ void printBuchiIntersectionCheckStats estream << string(counter == -1 ? "N/A " : "failed") + ' '; - if (alg_1 != alg_2) + if (*alg_1 != alg_2) { estream << '('; if (pos_test) @@ -489,9 +602,9 @@ void printBuchiIntersectionCheckStats else estream << " "; - estream << ' ' + configuration.algorithmString(alg_1); + estream << ' ' + configuration.algorithmString(*alg_1); - if (alg_1 != alg_2) + if (*alg_1 != alg_2) { estream << ", ("; if (pos_test) @@ -507,10 +620,11 @@ void printBuchiIntersectionCheckStats } } - if (no_errors_to_report) + if (nothing_to_report) + estream << string(indent, ' ') + "not performed"; + else if (no_errors_to_report) estream << string(20, ' ') + "no failures detected"; - - estream << "\n\n"; + estream << "\n"; estream.flush(); } @@ -537,30 +651,46 @@ void printAllStats { Exceptional_ostream estream(&stream, ios::failbit | ios::badbit); - estream << string(indent, ' ') + configuration.algorithmString(algorithm) - + '\n'; - estream.flush(); + if (configuration.global_options.verbosity >= 3) + estream << string(indent, ' ') + configuration.algorithmString(algorithm) + + '\n'; for (int counter = 0; counter < 2; counter++) { - estream << string(indent + 2, ' ') - + (counter == 0 ? "Positive" : "Negated") + " formula:\n" - + string(indent + 4, ' ') + "Büchi automaton:\n"; + if (configuration.global_options.verbosity <= 2) + { + if (counter == 1) + estream << '\n'; + estream << string(indent, ' '); + changeStreamFormatting(stream, 2, 0, ios::right); + estream << algorithm << ' '; + restoreStreamFormatting(stream); + estream << (counter == 0 ? '+' : '-') << ' '; + } + else + { + estream << string(indent + 2, ' ') + + (counter == 0 ? "Positive" : "Negated") + " formula:\n" + + string(indent + 4, ' ') + "Büchi automaton:\n"; + } printBuchiAutomatonStats(stream, indent + 6, algorithm, counter); if (configuration.global_options.do_comp_test || configuration.global_options.do_cons_test) { - estream << string(indent + 4, ' ') + "Product automaton:\n"; + if (configuration.global_options.verbosity >= 3) + estream << string(indent + 4, ' ') + "Product automaton:\n"; printProductAutomatonStats(stream, indent + 6, algorithm, counter); - estream << string(indent + 4, ' ') + "Accepting cycles:\n"; + if (configuration.global_options.verbosity >= 3) + estream << string(indent + 4, ' ') + "Accepting cycles:\n"; printAcceptanceCycleStats(stream, indent + 6, algorithm, counter); } } if (configuration.global_options.do_cons_test) { - estream << string(indent + 2, ' ') + "Result consistency check:\n"; + if (configuration.global_options.verbosity >= 3) + estream << string(indent + 2, ' ') + "Result consistency check:\n"; printConsistencyCheckStats(stream, indent + 4, algorithm); } @@ -626,10 +756,8 @@ void printCollectiveCrossComparisonStats break; default : - if (configuration.global_options.statespace_generation_mode - & Configuration::PATH - && (algorithm_x == round_info.number_of_translators - || algorithm_y == round_info.number_of_translators)) + if (configuration.isInternalAlgorithm(algorithm_x) + || configuration.isInternalAlgorithm(algorithm_y)) { estream << string(21, ' '); return; @@ -951,8 +1079,11 @@ void printCollectiveStats(ostream& stream, int indent) algorithm < round_info.number_of_translators; ++algorithm) { + if (configuration.isInternalAlgorithm(algorithm)) + continue; + estream << '\n' + string((i > 0 ? 4 : 2) + indent, ' ') - + *(configuration.algorithms[algorithm].name) + '\n'; + + configuration.algorithms[algorithm].name + '\n'; switch (i) { @@ -965,14 +1096,14 @@ void printCollectiveStats(ostream& stream, int indent) unsigned long int failures_to_compute_automaton; unsigned long int automaton_count; unsigned long int number_of_successful_instances; - unsigned long int total_number_of_states; - unsigned long int total_number_of_transitions; + BIGUINT total_number_of_states; + BIGUINT total_number_of_transitions; const TestStatistics& stats = final_statistics[algorithm]; estream << string(2 + indent, ' ') - + string(configuration.algorithms[algorithm].name - ->length(), '='); + + string(configuration.algorithms[algorithm].name. + length(), '='); for (int k = 0; k < 2; k++) { @@ -1117,7 +1248,7 @@ void printCollectiveStats(ostream& stream, int indent) if (k == 0) { - unsigned long int total_number_of_acceptance_sets; + BIGUINT total_number_of_acceptance_sets; double buchi_generation_time; estream << '\n' + string(22 + indent, ' ') @@ -1350,7 +1481,7 @@ void printCollectiveStats(ostream& stream, int indent) if (algorithm_x + i < number_of_algorithms) { algorithm_name = - (*(configuration.algorithms[algorithm_x + i].name)).substr(0, 20); + configuration.algorithms[algorithm_x + i].name.substr(0, 20); estream << "| " + algorithm_name + string(21 - algorithm_name.length(), ' '); } @@ -1360,6 +1491,9 @@ void printCollectiveStats(ostream& stream, int indent) for (algorithm_y = 0; algorithm_y < number_of_algorithms; algorithm_y++) { + if (configuration.isInternalAlgorithm(algorithm_y)) + continue; + estream << "\n " + ind + string(26, '-'); for (int i = 0; i < 2; ++i) @@ -1370,7 +1504,7 @@ void printCollectiveStats(ostream& stream, int indent) estream << '+'; algorithm_name - = (*(configuration.algorithms[algorithm_y].name)).substr(0, 20); + = configuration.algorithms[algorithm_y].name.substr(0, 20); bool algorithm_name_printed = false; legend = 1; diff --git a/lbtt/src/StatDisplay.h b/lbtt/src/StatDisplay.h index a4a947ad5..212aa6091 100644 --- a/lbtt/src/StatDisplay.h +++ b/lbtt/src/StatDisplay.h @@ -42,6 +42,11 @@ extern Configuration configuration; namespace StatDisplay { +void printStatTableHeader /* Displays a table */ + (ostream& stream, int indent); /* header for test + * statistics. + */ + void printBuchiAutomatonStats /* Displays information */ (ostream& stream, /* about a Büchi */ int indent, /* automaton. */ @@ -74,17 +79,15 @@ void printConsistencyCheckStats /* Displays the result */ algorithm); void printCrossComparisonStats /* Displays information */ - (ostream& stream, /* about the model */ - int indent, /* checking result */ - vector::size_type /* check. */ - algorithm); + (ostream& stream, int indent, /* about the model */ + const IntervalList& algorithms); /* checking result */ + /* cross-comparison */ + /* check. */ void printBuchiIntersectionCheckStats /* Displays the results */ (ostream& stream, int indent, /* of the Büchi automata */ - vector::size_type /* emptiness checks. */ - algorithm); + const IntervalList& algorithms); /* intersection */ + /* emptiness checks. */ void printAllStats /* A shorthand for */ (ostream& stream, /* showing all the */ diff --git a/lbtt/src/StateSpaceProduct.h b/lbtt/src/StateSpaceProduct.h new file mode 100644 index 000000000..87fab2fbd --- /dev/null +++ b/lbtt/src/StateSpaceProduct.h @@ -0,0 +1,430 @@ +/* + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 + * Heikki Tauriainen + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef STATESPACEPRODUCT_H +#define STATESPACEPRODUCT_H + +#include +#include "BitArray.h" +#include "BuchiAutomaton.h" +#include "EdgeContainer.h" +#include "Graph.h" +#include "StateSpace.h" + +using namespace std; + +namespace Graph +{ + +/****************************************************************************** + * + * A class with operations for checking the product of a Büchi automaton and + * a state space for emptiness. + * + *****************************************************************************/ + +class StateSpaceProduct +{ +public: + StateSpaceProduct /* Constructor. */ + (const Graph& a, + const Graph& s); + + /* default copy constructor */ + + ~StateSpaceProduct(); /* Destructor. */ + + /* default assignment operator */ + + bool empty() const; /* Tells whether the + * product of a Büchi + * automaton and a state + * space is (trivially) + * empty. + */ + + unsigned long int numberOfAcceptanceSets() const; /* Tells the number of + * acceptance sets in a + * Büchi automaton in its + * product with a state + * space. + */ + + const BuchiAutomaton::BuchiState& firstComponent /* Mappings between a */ + (const Graph::size_type /* product state */ + state_id) const; /* identifier and */ + const StateSpace::State& secondComponent /* states of the Büchi */ + (const Graph::size_type /* automaton and the */ + state_id) const; /* state space forming + * the product. + */ + + void mergeAcceptanceInformation /* Merges the acceptance */ + (const Graph::Node& state, /* sets associated with */ + const Graph::Node&, /* a state in the Büchi */ + BitArray& acceptance_sets) const; /* automaton into a + * collection of sets. + */ + + void mergeAcceptanceInformation /* Merges the acceptance */ + (const Graph::Edge& /* sets associated with */ + buchi_transition, /* a transition in the */ + const Graph::Edge&, /* Büchi automaton into */ + BitArray& acceptance_sets) const; /* a collection of sets. */ + + void validateEdgeIterators /* Ensures that a pair */ + (const Graph::Node& /* of transition */ + buchi_state, /* iterators points to a */ + const Graph::Node& /* transition beginning */ + system_state, /* from a given state in */ + GraphEdgeContainer::const_iterator& /* the product of a */ + buchi_transition, /* Büchi automaton and */ + GraphEdgeContainer::const_iterator& /* a state space. */ + system_transition) const; + + void incrementEdgeIterators /* Updates a pair of */ + (const Graph::Node& /* transition iterators */ + buchi_state, /* to make them point to */ + const Graph::Node& /* the "next" transition */ + system_state, /* starting from a given */ + GraphEdgeContainer::const_iterator& /* state in the product */ + buchi_transition, /* of a Büchi automaton */ + GraphEdgeContainer::const_iterator& /* and a state space. */ + system_transition) const; + +private: + const BuchiAutomaton& buchi_automaton; /* Büchi automaton + * associated with the + * product. + */ + + const StateSpace& statespace; /* State space associated + * with the product. + */ +}; + + + +/****************************************************************************** + * + * Inline function definitions for class StateSpaceProduct. + * + *****************************************************************************/ + +/* ========================================================================= */ +inline StateSpaceProduct::StateSpaceProduct + (const Graph& a, const Graph& s) : + buchi_automaton(static_cast(a)), + statespace(static_cast(s)) +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class StateSpaceProduct. Initializes a new + * object with operations for checking the emptiness of the + * product of a Büchi automaton and a state space. + * + * Arguments: a -- A constant reference to a Graph + * object, assumed to be a BuchiAutomaton. + * s -- A constant reference to a Graph + * object, assumed to be a StateSpace. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +inline StateSpaceProduct::~StateSpaceProduct() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class StateSpaceProduct. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +inline bool StateSpaceProduct::empty() const +/* ---------------------------------------------------------------------------- + * + * Description: Tells whether the product of `this->buchi_automaton' and + * `this->statespace' is (trivially) empty. + * + * Arguments: None. + * + * Returns: true iff either the Büchi automaton or the state space has + * no states. + * + * ------------------------------------------------------------------------- */ +{ + return (buchi_automaton.empty() || statespace.empty()); +} + +/* ========================================================================= */ +inline unsigned long int StateSpaceProduct::numberOfAcceptanceSets() const +/* ---------------------------------------------------------------------------- + * + * Description: Tells the number of acceptance sets in the Büchi automaton + * associated with a StateSpaceProduct object. + * + * Arguments: None. + * + * Returns: The number of acceptance sets in the automaton. + * + * ------------------------------------------------------------------------- */ +{ + return buchi_automaton.numberOfAcceptanceSets(); +} + +/* ========================================================================= */ +inline const BuchiAutomaton::BuchiState& StateSpaceProduct::firstComponent + (const Graph::size_type state_id) const +/* ---------------------------------------------------------------------------- + * + * Description: Function for accessing states in the Büchi automaton + * associated with a StateSpaceProduct object. + * + * Argument: state_id -- Identifier of a state in the automaton. + * + * Returns: A constant reference to a state in the automaton. + * + * ------------------------------------------------------------------------- */ +{ + return buchi_automaton[state_id]; +} + +/* ========================================================================= */ +inline const StateSpace::State& StateSpaceProduct::secondComponent + (const Graph::size_type state_id) const +/* ---------------------------------------------------------------------------- + * + * Description: Function for accessing states in the state space associated + * with a StateSpaceProduct object. + * + * Argument: state_id -- Identifier of a state in the state space. + * + * Returns: A constant reference to a state in the state space. + * + * ------------------------------------------------------------------------- */ +{ + return statespace[state_id]; +} + +/* ========================================================================= */ +inline void StateSpaceProduct::mergeAcceptanceInformation + (const Graph::Node& state, + const Graph::Node&, BitArray& acceptance_sets) const +/* ---------------------------------------------------------------------------- + * + * Description: Merges the acceptance sets associated with a state of a Büchi + * automaton into a collection of sets. + * + * Arguments: state -- A constant reference to a state in the + * automaton. + * acceptance_sets -- A reference to a BitArray for storing + * the result. The BitArray should have + * capacity for (at least) + * `this->buchi_automaton + * .numberOfAcceptanceSets()' bits. + * (The second argument is needed to allow the class + * StateSpaceProduct to be used for instantiating the Product + * template; see file Product.h.) + * + * Returns: Nothing. After the operation, `acceptance_sets[i] == true' + * holds if `state.acceptanceSets().test(i) == true' for all + * 0 <= i < `this->buchi_automaton.numberOfAcceptanceSets()'. + * + * ------------------------------------------------------------------------- */ +{ + acceptance_sets.bitwiseOr + (static_cast(state).acceptanceSets(), + numberOfAcceptanceSets()); +} + +/* ========================================================================= */ +inline void StateSpaceProduct::mergeAcceptanceInformation + (const Graph::Edge& buchi_transition, + const Graph::Edge&, BitArray& acceptance_sets) const +/* ---------------------------------------------------------------------------- + * + * Description: Merges the acceptance sets associated with a transition of a + * Büchi automaton into a collection of sets. + * + * Arguments: transition -- A constant reference to a transition in + * the automaton. + * acceptance_sets -- A reference to a BitArray for storing + * the result. The BitArray should have + * capacity for (at least) + * `this->buchi_automaton + * .numberOfAcceptanceSets()' bits. + * (The second argument is needed to allow the class + * StateSpaceProduct to be used for instantiating the Product + * template; see file Product.h.) + * + * Returns: Nothing. After the operation, `acceptance_sets[i] == true' + * holds if `transition.acceptanceSets().test(i) == true' for + * all + * 0 <= i < `this->buchi_automaton.numberOfAcceptanceSets()'. + * + * ------------------------------------------------------------------------- */ +{ + acceptance_sets.bitwiseOr + (static_cast(buchi_transition) + .acceptanceSets(), + numberOfAcceptanceSets()); +} + +/* ========================================================================= */ +inline void StateSpaceProduct::validateEdgeIterators + (const Graph::Node& buchi_state, + const Graph::Node& system_state, + GraphEdgeContainer::const_iterator& buchi_transition, + GraphEdgeContainer::const_iterator& system_transition) const +/* ---------------------------------------------------------------------------- + * + * Description: Checks whether a pair of transition iterators corresponds to + * a transition beginning from a state in the product of a Büchi + * automaton and a state space; if this is not the case, makes + * the iterators point to a valid transition beginning from the + * product state (or to the "end" of the collection of + * transitions beginning from the product state if no valid + * transition can be found by incrementing the iterators). + * + * Arguments: buchi_state, -- These variables determine the state + * system_state, in the product; `buchi_state' and + * `system_state' should be references to + * states in `this->buchi_automaton' and + * `this->statespace', respectively. + * buchi_transition, -- References to the transition + * system_transition iterators. It is assumed that + * `buchi_transition' and + * `system_transition' initially point to + * two transitions starting from + * `buchi_state' and `system_state', + * respectively. + * + * Returns: Nothing. Upon return, `buchi_transition' and + * `system_transition' will either equal + * `buchi_state.edges().end()' and `system_state.edges().end()', + * respectively, or they will point to a pair of transitions + * beginning from `buchi_state' and `system_state' such that + * this pair of transitions corresponds to a transition starting + * from the product state determined by `buchi_state' and + * `system_state'. + * + * ------------------------------------------------------------------------- */ +{ + const GraphEdgeContainer& buchi_transitions = buchi_state.edges(); + const GraphEdgeContainer& system_transitions = system_state.edges(); + + if (buchi_transition == buchi_transitions.end()) + { + system_transition = system_transitions.end(); + return; + } + if (system_transition == system_transitions.end()) + { + buchi_transition = buchi_transitions.end(); + return; + } + + while (!static_cast + (*buchi_transition)->enabled + (static_cast(system_state) + .positiveAtoms(), + statespace.numberOfPropositions())) + { + ++buchi_transition; + if (buchi_transition == buchi_transitions.end()) + { + system_transition = system_transitions.end(); + return; + } + } + + system_transition = system_transitions.begin(); +} + +/* ========================================================================= */ +inline void StateSpaceProduct::incrementEdgeIterators + (const Graph::Node& buchi_state, + const Graph::Node& system_state, + GraphEdgeContainer::const_iterator& buchi_transition, + GraphEdgeContainer::const_iterator& system_transition) const +/* ---------------------------------------------------------------------------- + * + * Description: Increments a pair of transition iterators to point to the + * "next" transition beginning from a state in the product of a + * Büchi automaton and a state space. If no "next" transition + * exists, makes the iterators point to the "end" of the + * collection of transitions beginning from the product state. + * + * Arguments: buchi_state, -- These variables determine the state + * system_state, in the product; `buchi_state' and + * `system_state' should be references to + * states in `this->buchi_automaton' and + * `this->statespace', respectively. + * buchi_transition, -- References to the transition + * system_transition iterators. It is assumed that + * `buchi_transition' and + * `system_transition' initially point to + * two transitions starting from + * `buchi_state' and `system_state', + * respectively. + * + * Returns: Nothing. Upon return, `buchi_transition' and + * `system_transition' will either equal + * `buchi_state.edges().end()' and `system_state.edges().end()', + * respectively, or they will point to a pair of transitions + * beginning from `buchi_state' and `system_state' such that + * this pair of transitions corresponds to a transition starting + * from the product state determined by `buchi_state' and + * `system_state'. + * + * ------------------------------------------------------------------------- */ +{ + const GraphEdgeContainer& buchi_transitions = buchi_state.edges(); + const GraphEdgeContainer& system_transitions = system_state.edges(); + + ++system_transition; + if (system_transition == system_transitions.end()) + { + do + { + ++buchi_transition; + if (buchi_transition == buchi_transitions.end()) + return; + } + while (!static_cast + (*buchi_transition)->enabled + (static_cast(system_state) + .positiveAtoms(), + statespace.numberOfPropositions())); + + system_transition = system_transitions.begin(); + } +} + +} + +#endif /* !STATESPACEPRODUCT_H */ diff --git a/lbtt/src/StringUtil.cc b/lbtt/src/StringUtil.cc index 048db9542..c1ff6d428 100644 --- a/lbtt/src/StringUtil.cc +++ b/lbtt/src/StringUtil.cc @@ -18,6 +18,8 @@ */ #include +#include +#include #include #include "StringUtil.h" @@ -100,6 +102,211 @@ void sliceString while (last_non_slicechar_pos != s.npos && last_slicechar_pos != s.npos); } +/* ========================================================================= */ +string toLowerCase(const string& s) +/* ---------------------------------------------------------------------------- + * + * Description: Converts a string to lower case. + * + * Argument: s -- String to process. + * + * Returns: The string in lower case. + * + * ------------------------------------------------------------------------- */ +{ + string result; + for (string::size_type pos = 0; pos < s.length(); ++pos) + result += tolower(s[pos]); + return result; +} + +/* ========================================================================= */ +bool interpretSpecialCharacters(const char c, bool& escape, char& quotechar) +/* ---------------------------------------------------------------------------- + * + * Description: Updates the values of `escape' and `quotechar' based on their + * original values and the value of `c'. Used for scanning + * through a string possibly containing quotes and escaped + * characters. + * + * Arguments: c -- A character. + * escape -- A truth value telling whether `c' was escaped. + * quotechar -- 0 == `c' was read outside of quotes. + * `'' == `c' was read inside single quotes. + * `"' == `c' was read inside double quotes. + * + * Returns: True if `c' had a special meaning (for example, if it was a + * begin/end quote character) in the state determined by the + * original values of `escape' and `quotechar'. + * + * ------------------------------------------------------------------------- */ +{ + if (escape) + { + escape = false; + return false; + } + + switch (c) + { + case '\\' : + if (quotechar != '\'') + { + escape = true; + return true; + } + break; + + case '\'' : case '"' : + if (quotechar == 0) + { + quotechar = c; + return true; + } + else if (c == quotechar) + { + quotechar = 0; + return true; + } + break; + + default : + break; + } + + return false; +} + +/* ========================================================================= */ +string unquoteString(const string& s) +/* ---------------------------------------------------------------------------- + * + * Description: Removes (unescaped) single and double quotes and escape + * characters from a string. + * + * Argument: s -- String to process. + * + * Returns: A string with the quotes and escape characters removed. + * + * ------------------------------------------------------------------------- */ +{ + string result; + char quotechar = 0; + bool escape = false; + + for (string::size_type pos = 0; pos < s.size(); ++pos) + { + if (!interpretSpecialCharacters(s[pos], escape, quotechar)) + result += s[pos]; + } + + return result; +} + +/* ========================================================================= */ +string::size_type findInQuotedString + (const string& s, const string& chars, QuoteMode type) +/* ---------------------------------------------------------------------------- + * + * Description: Finds a character in a string (respecting quotes). + * + * Arguments: s -- String to process. + * chars -- A sting of characters to be searched in `s'. + * type -- The extent of the search. + * GLOBAL - Apply the search to the entire + * string. + * INSIDE_QUOTES - Restrict the search to + * unescaped characters between + * quotes. + * OUTSIDE_QUOTES - Restrict the search to + * unescaped characters outside + * quotes. + * + * Returns: If `s' contains one of the characters in `chars' in a part + * of the string that matches `type', the position of the + * character in `s', and string::npos otherwise. + * + * ------------------------------------------------------------------------- */ +{ + char quotechar = 0; + bool escape = false; + + for (string::size_type pos = 0; pos < s.size(); ++pos) + { + if ((type == GLOBAL || (!escape && + ((type == INSIDE_QUOTES && quotechar != 0) + || (type == OUTSIDE_QUOTES && quotechar == 0)))) + && chars.find_first_of(s[pos]) != string::npos) + return pos; + + interpretSpecialCharacters(s[pos], escape, quotechar); + } + + return string::npos; +} + +/* ========================================================================= */ +string substituteInQuotedString + (const string& s, const string& chars, const string& substitutions, + QuoteMode type) +/* ---------------------------------------------------------------------------- + * + * Description: Substitutes characters in a string with other characters. + * + * Arguments: s -- String to process. + * chars -- A string of characters, each of which + * should be substituted in `s' with the + * character at the corresponding + * position of the string `substitutions'. + * substitutions -- Characters to substitute. The length of + * this string should equal the length of + * `chars'. + * type -- The extent of substitution. + * GLOBAL - Apply the substitutions + * globally (the default). + * INSIDE_QUOTES - Apply the substitutions + * to unescaped characters + * only inside quotes that + * have not been escaped + * with a backslash. + * OUTSIDE_QUOTES - Apply the substitutions + * to unescaped characters + * only outside quotes + * that have not been + * escaped with a + * backslash. + * It is not recommended to substitute the + * special characters ', " and \ with other + * characters if they have special meaning in + * `s'. + * + * Returns: A string with the substitutions. + * + * ------------------------------------------------------------------------- */ +{ + string result; + char quotechar = 0; + bool escape = false; + + for (string::size_type pos = 0; pos < s.size(); ++pos) + { + char c = s[pos]; + if (type == GLOBAL || (!escape && + ((type == INSIDE_QUOTES && quotechar != 0) + || (type == OUTSIDE_QUOTES && quotechar == 0)))) + { + string::size_type subst_pos = chars.find_first_of(c); + if (subst_pos != string::npos) + c = substitutions[subst_pos]; + } + result += c; + + interpretSpecialCharacters(s[pos], escape, quotechar); + } + + return result; +} + /* ========================================================================= */ unsigned long int parseNumber(const string& number_string) /* ---------------------------------------------------------------------------- @@ -115,9 +322,11 @@ unsigned long int parseNumber(const string& number_string) * ------------------------------------------------------------------------- */ { char* endptr; + unsigned long int number = strtoul(number_string.c_str(), &endptr, 10); - if (*endptr != '\0') + if (*endptr != '\0' || number_string.empty() + || number_string.find_first_of("-") != string::npos) throw NotANumberException("expected a nonnegative integer, got `" + number_string + "'"); @@ -125,98 +334,254 @@ unsigned long int parseNumber(const string& number_string) } /* ========================================================================= */ -void parseInterval - (const string& token, - set, ALLOC(unsigned long int) >& - number_set, - unsigned long int min, unsigned long int max) +int parseInterval + (const string& token, unsigned long int& min, unsigned long int& max) /* ---------------------------------------------------------------------------- * - * Description: Parses a string for a list of number intervals, storing all - * the numbers into a set. + * Description: Reads the lower and upper bound from an "interval string" + * into two unsigned long integer variables. * - * Arguments: token -- A reference to a constant string containing - * the list of intervals. A legal list of - * intervals has the following syntax: + * Arguments: token -- A reference to a constant "interval string" of + * the format + * + * ::= "*" // 0 + * | // 1 + * | // 2 + * | // 3 + * | // 4 + * where is an unsigned long integer (not + * containing a minus sign), and is either + * "-" or "...". The meaning of the various cases + * is as follows: + * 0 All integers between 0 and ULONG_MAX. + * 1 A point interval consisting of a single + * value. + * 2 An interval from 0 to a given upper + * bound. + * 3 An interval from a given lower bound to + * ULONG_MAX. + * 4 A bounded interval. + * min, max -- References to two unsigned long integers for + * storing the lower and upper bound of the + * interval. * - * - * ::= - * | '*' // all numbers in the - * // interval - * // [min, max] - * | '-' // all numbers in the - * // interval - * // [min, number] - * | '-' // all numbers in the - * // interval - * // [number, max] - * | '-' - * | ',' - * - * number_set -- A reference to a set of unsigned long - * integers for storing the result. - * min -- Minimum bound for the numbers. - * max -- Maximum bound for the numbers. - * - * Note: `min' and `max' are only used to determine the limits - * for only partially defined intervals. The check that - * the explicitly specified values in the interval list - * are within these bounds is left to the caller when - * examining the result set. - * - * Returns: Nothing. + * Returns: A value telling the type of the specified interval, which is + * a bitwise or of the values LEFT_BOUNDED and RIGHT_BOUNDED + * depending on which bounds were given explicitly for the + * interval. (The lower and upper bounds of the interval itself + * are stored in the variables `min' and `max', respectively.) + * The function will throw a NotANumberException if the + * interval string is of an invalid format. * * ------------------------------------------------------------------------- */ { - vector intervals; + unsigned long int tmp_min = 0; + unsigned long int tmp_max = ULONG_MAX; + int interval_type = UNBOUNDED; - sliceString(token, ",", intervals); - string::size_type dash_pos; + if (token != "*") + { + string::size_type pos(token.find_first_of("-")); + if (pos == string::npos) + pos = token.find("..."); + string value(token.substr(0, pos)); - number_set.clear(); + if (!value.empty()) + { + tmp_min = parseNumber(value); + if (pos == string::npos) + tmp_max = tmp_min; + interval_type |= LEFT_BOUNDED; + } + + if (pos != string::npos) + value = token.substr(pos + (token[pos] == '-' ? 1 : 3)); + + if (!value.empty()) + { + tmp_max = parseNumber(value); + interval_type |= RIGHT_BOUNDED; + } + else if (!(interval_type & LEFT_BOUNDED)) + throw NotANumberException("invalid format for interval"); + } + + min = tmp_min; + max = tmp_max; + + return interval_type; +} + +/* ========================================================================= */ +void parseIntervalList + (const string& token, IntervalList& intervals, unsigned long int min, + unsigned long int max, vector* extra_tokens) +/* ---------------------------------------------------------------------------- + * + * Description: Parses a string of number intervals into an IntervalList. + * + * Arguments: token -- A reference to a constant comma-separated + * list of interval strings (see documentation + * for the parseInterval function). + * intervals -- A reference to an IntervalList to be used + * for storing the result. + * min -- Absolute lower bound for the numbers. + * Numbers lower than this bound will not be + * stored in the result set. + * max -- Absolute upper bound for the numbers. + * Numbers greater than this bound will not be + * stored in the result set. + * extra_tokens -- If not 0, all tokens that cannot be + * recognized as valid interval strings will + * be stored in the vector of strings to which + * this variable points. Otherwise the + * function will throw a NotANumberException. + * + * Returns: Nothing. Throws an IntervalRangeException if any of the + * intervals in the list does not fit in the closed range + * [min,max]. + * + * ------------------------------------------------------------------------- */ +{ + vector interval_strings; + int interval_type; + + intervals.clear(); + sliceString(token, ",", interval_strings); for (vector::const_iterator - interval = intervals.begin(); - interval != intervals.end(); - ++interval) + i = interval_strings.begin(); + i != interval_strings.end(); + ++i) { - if (*interval == "*") + unsigned long int i_start, i_end; + + try { - for (unsigned long int i = min; i <= max; i++) - number_set.insert(i); - break; + interval_type = parseInterval(*i, i_start, i_end); } - - dash_pos = (*interval).find_first_of("-"); - - if (dash_pos == (*interval).npos) - number_set.insert(parseNumber(*interval)); - else + catch (const NotANumberException&) { - if (dash_pos == 0) + if (extra_tokens != 0) { - unsigned long int number = parseNumber((*interval).substr(1)); - for (unsigned long int i = min; i <= number; i++) - number_set.insert(i); - } - else if (dash_pos == (*interval).length() - 1) - { - unsigned long int number = - parseNumber((*interval).substr(0, (*interval).length() - 1)); - for (unsigned long int i = number; i <= max; i++) - number_set.insert(i); + extra_tokens->push_back(*i); + continue; } else - { - unsigned long int min_bound = - parseNumber((*interval).substr(0, dash_pos)); - unsigned long int max_bound = - parseNumber((*interval).substr(dash_pos + 1)); - - for (unsigned long int i = min_bound; i <= max_bound; i++) - number_set.insert(i); - } + throw; } + + if (interval_type & LEFT_BOUNDED) + { + if (i_start < min || i_start > max) + throw IntervalRangeException(i_start); + } + else if (i_start < min) + i_start = min; + + if (interval_type & RIGHT_BOUNDED) + { + if (i_end < min || i_end > max) + throw IntervalRangeException(i_end); + } + else if (i_end > max) + i_end = max; + + intervals.merge(i_start, i_end); + } +} + +/* ========================================================================= */ +void parseTime + (const string& time_string, unsigned long int& hours, + unsigned long int& minutes, unsigned long int& seconds) +/* ---------------------------------------------------------------------------- + * + * Description: Parses a "time string", i.e., a string of the form + * ([0-9]+"h")([0-9]+"min")?([0-9]+"s")? + * | ([0-9]+"min")([0-9]+"s")? + * | ([0-9]+"s") + * (where 'h', 'min' and 's' correspond to hours, minutes and + * seconds, respectively) and stores the numbers into three + * unsigned long integers. The case of the unit symbols is not + * relevant. + * + * Arguments: time_string -- String to process. + * hours -- A reference to an unsigned long integer for + * storing the number of hours. + * minutes -- A reference to an unsigned long integer for + * storing the number of minutes. + * seconds -- A reference to an unsigned long integer for + * storing the number of seconds. + * + * Time components left unspecified in `time_string' will get + * the value 0. + * + * Returns: Nothing. Throws an Exception if the given string is not of + * the correct format. + * + * ------------------------------------------------------------------------- */ +{ + bool hours_present = false, minutes_present = false, seconds_present = false; + hours = minutes = seconds = 0; + + if (time_string.empty()) + throw Exception("invalid time format"); + + string::size_type pos1 = 0; + string s; + + while (pos1 < time_string.length()) + { + string::size_type pos2 = time_string.find_first_not_of("0123456789", pos1); + if (pos2 >= time_string.length()) + throw Exception("invalid time format"); + + unsigned long int val; + + try + { + val = parseNumber(time_string.substr(pos1, pos2 - pos1)); + } + catch (const NotANumberException&) + { + throw Exception("invalid time format"); + } + + switch (tolower(time_string[pos2])) + { + case 'h' : + if (hours_present || minutes_present || seconds_present) + throw Exception("invalid time format"); + hours_present = true; + hours = val; + break; + + case 'm' : + if (minutes_present + || seconds_present + || pos2 + 2 >= time_string.length() + || tolower(time_string[pos2 + 1]) != 'i' + || tolower(time_string[pos2 + 2]) != 'n') + throw Exception("invalid time format"); + minutes_present = true; + minutes = val; + pos2 += 2; + break; + + case 's' : + if (seconds_present) + throw Exception("invalid time format"); + seconds_present = true; + seconds = val; + break; + + default : /* 's' */ + throw Exception("invalid time format"); + break; + } + + pos1 = pos2 + 1; } } diff --git a/lbtt/src/StringUtil.h b/lbtt/src/StringUtil.h index 65a9fa790..4965cf64f 100644 --- a/lbtt/src/StringUtil.h +++ b/lbtt/src/StringUtil.h @@ -31,6 +31,7 @@ #include #include "LbttAlloc.h" #include "Exception.h" +#include "IntervalList.h" using namespace std; @@ -62,17 +63,65 @@ void sliceString /* Breaks a string into */ * separators. */ +string toLowerCase(const string& s); /* Converts a string to + * lower case. + */ + +string unquoteString(const string& s); /* Removes unescaped quotes + * from a string and + * interprets escaped + * characters. + */ + +enum QuoteMode {GLOBAL, INSIDE_QUOTES, /* Enumeration type used */ + OUTSIDE_QUOTES}; /* for controlling the + * behavior of the + * substitute function. + */ + +string::size_type findInQuotedString /* Finds a character in */ + (const string& s, const string& chars, /* a string (respecting */ + QuoteMode type = GLOBAL); /* quotes). */ + +string substituteInQuotedString /* Replaces characters */ + (const string& s, const string& chars, /* in a string with */ + const string& substitutions, /* other characters. */ + QuoteMode type = GLOBAL); + unsigned long int parseNumber /* Converts a string to */ (const string& number_string); /* an unsigned long * integer. */ -void parseInterval /* Converts a string of */ - (const string& token, /* number intervals to */ - set, /* the corresponding set */ - ALLOC(unsigned long int) >& number_set, /* of unsigned long */ - unsigned long int min, /* integers. */ - unsigned long int max); + enum IntervalStringType {UNBOUNDED = 0, /* Type for an interval */ + LEFT_BOUNDED = 1, /* string (see the */ + RIGHT_BOUNDED = 2}; /* documentation of + * the parseInterval + * function in + * StringUtil.cc). + */ + +int parseInterval /* Reads the lower and */ + (const string& token, /* upper bounds from a */ + unsigned long int& min, /* "number interval */ + unsigned long int& max); /* string" into two + * unsigned long integer + * variables. + */ + +void parseIntervalList /* Converts a list of */ + (const string& token, /* number intervals to */ + IntervalList& intervals, /* the set of unsigned */ + unsigned long int min, /* long integers */ + unsigned long int max, /* corresponding to the */ + vector* extra_tokens /* union of the */ + = 0); /* intervals. */ + +void parseTime /* Parses a time string. */ + (const string& time_string, + unsigned long int& hours, + unsigned long int& minutes, + unsigned long int& seconds); @@ -133,6 +182,38 @@ public: +/****************************************************************************** + * + * A class for reporting "out of range" errors for numbers when parsing + * intervals. + * + *****************************************************************************/ + +class IntervalRangeException : public Exception +{ +public: + IntervalRangeException /* Constructor. */ + (const unsigned long int number, + const string& message = "number out of range"); + + /* default copy constructor */ + + ~IntervalRangeException() throw(); /* Destructor. */ + + IntervalRangeException& operator= /* Assignment operator. */ + (const IntervalRangeException& e); + + unsigned long int getNumber() const; /* Returns the number + * associated with the + * exception object. + */ + +private: + const unsigned long int invalid_number; +}; + + + /****************************************************************************** * * Inline function definitions for class NotANumberException. @@ -189,6 +270,82 @@ inline NotANumberException& NotANumberException::operator= return *this; } + + +/****************************************************************************** + * + * Inline function definitions for class IntervalRangeException. + * + *****************************************************************************/ + +/* ========================================================================= */ +inline IntervalRangeException::IntervalRangeException + (const unsigned long int number, const string& message) : + Exception(message), invalid_number(number) +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class IntervalRangeException. Creates an + * exception object. + * + * Arguments: number -- A constant unsigned long integer specifying a + * number that does not fit in an interval. + * message -- A reference to a constant string containing an + * error message. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +inline IntervalRangeException::~IntervalRangeException() throw() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class IntervalRangeException. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +inline unsigned long int IntervalRangeException::getNumber() const +/* ---------------------------------------------------------------------------- + * + * Description: Returns the number associated with the IntervalRangeException + * object. + * + * Arguments: None. + * + * Returns: The number associated with the object. + * + * ------------------------------------------------------------------------- */ +{ + return invalid_number; +} + +/* ========================================================================= */ +inline IntervalRangeException& IntervalRangeException::operator= + (const IntervalRangeException& e) +/* ---------------------------------------------------------------------------- + * + * Description: Assignment operator for class IntervalRangeException. + * Copies the contents of an exception object to another. + * + * Arguments: e -- A reference to a constant IntervalRangeException. + * + * Returns: A reference to the object assigned to. + * + * ------------------------------------------------------------------------- */ +{ + Exception::operator=(e); + return *this; +} + } #endif /* !STRINGUTIL_H */ diff --git a/lbtt/src/TempFsysName.cc b/lbtt/src/TempFsysName.cc new file mode 100644 index 000000000..51dc9c335 --- /dev/null +++ b/lbtt/src/TempFsysName.cc @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2004 + * Heikki Tauriainen + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_FCNTL_H +#include +#endif /* HAVE_FCNTL_H */ +#include +#include "Exception.h" +#include "StringUtil.h" +#include "TempFsysName.h" + +/****************************************************************************** + * + * Number of generated temporary names. + * + *****************************************************************************/ + +unsigned long int TempFsysName::number_of_allocated_names = 0; + + + +/****************************************************************************** + * + * Function definitions for class TempFsysName. + * + *****************************************************************************/ + +/* ========================================================================= */ +const char* TempFsysName::allocate + (const char* prefix, const NameType t, const bool literal) +/* ---------------------------------------------------------------------------- + * + * Description: Associates a TempFsysName object with a temporary name. (As + * a side effect, the function actually creates an empty + * temporary file or a directory to reserve the name in the file + * system. The file or directory should never be removed + * explicitly; it is removed automatically when the TempFsysName + * object is destroyed or another call is made to + * `this->allocate'.) + * + * Arguments: prefix -- Pointer to a C-style string containing a prefix + * for the temporary name (empty by default). If + * `literal == true', `prefix' (if nonempty) is + * assumed to contain the full path for the + * temporary file or directory; otherwise the + * function will reserve a temporary name in the + * `P_tmpdir' directory. This name will consist + * of the value of `prefix' (if nonempty), followed + * by the current value of + * `TempFsysName::number_of_allocated_names' and + * the current process id, separated by dots. + * t -- Determines the type of the name (file or a + * directory). + * literal -- See above. + * + * Returns: A pointer to a constant C-style string containing the + * temporary name. The function throws an IOException if the + * name allocation or the file or directory creation fails. + * + * ------------------------------------------------------------------------- */ +{ + releaseName(); + + using ::StringUtil::toString; + string tempname; + + try + { + if (!literal || strlen(prefix) == 0) + { + tempname = toString(P_tmpdir) + "/"; + if (strlen(prefix)) + tempname += string(prefix) + "."; + tempname += toString(number_of_allocated_names) + "." + + toString(getpid()); + ++number_of_allocated_names; + } + else + tempname = prefix; + + name = new char[tempname.length() + 1]; + strcpy(name, tempname.c_str()); + } + catch (const bad_alloc&) + { + name = 0; + throw IOException + ("unable to allocate a temporary name in the file system"); + } + + type = t; + if (t == FILE) + { + int fd = open(name, O_RDWR | O_CREAT | O_EXCL, S_IREAD | S_IWRITE); + if (fd == -1) + throw IOException("unable to create a temporary file"); + close(fd); + } + else if (mkdir(name, S_IRWXU) == -1) + throw IOException("unable to create a temporary directory"); + + return name; +} diff --git a/lbtt/src/TempFsysName.h b/lbtt/src/TempFsysName.h new file mode 100644 index 000000000..f2845ebb6 --- /dev/null +++ b/lbtt/src/TempFsysName.h @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2004 + * Heikki Tauriainen + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef TEMPFSYSNAME_H +#define TEMPFSYSNAME_H + +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif /* HAVE_UNISTD_H */ + +/****************************************************************************** + * + * A class for temporary file system names. + * + *****************************************************************************/ + +class TempFsysName +{ +public: + TempFsysName(); /* Constructor. */ + + ~TempFsysName(); /* Destructor. */ + + enum NameType { FILE, DIRECTORY }; /* Types of temporary + * file system names. + */ + + const char* allocate /* Allocates a name */ + (const char* prefix = "", /* in the file system. */ + const NameType t = FILE, + const bool literal = false); + + const char* get() const; /* Tells the name. */ + +private: + TempFsysName(const TempFsysName&); /* Prevent copying and */ + TempFsysName& operator=(const TempFsysName&); /* assignment of + * TempFsysName objects. + */ + + void releaseName(); /* Frees a name in the file + * system. + */ + + char* name; /* Temporary name. */ + + NameType type; /* Tells whether the name + * refers to a file or a + * directory. + */ + + static unsigned long int /* Counter for the */ + number_of_allocated_names; /* number of generated + * temporary names. + */ +}; + + + +/****************************************************************************** + * + * Inline function definitions for class TempFsysName. + * + *****************************************************************************/ + +/* ========================================================================= */ +inline TempFsysName::TempFsysName() : name(static_cast(0)) +/* ---------------------------------------------------------------------------- + * + * Description: Constructor for class TempFsysName. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ +} + +/* ========================================================================= */ +inline TempFsysName::~TempFsysName() +/* ---------------------------------------------------------------------------- + * + * Description: Destructor for class TempFsysName. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + releaseName(); +} + +/* ========================================================================= */ +inline const char* TempFsysName::get() const +/* ---------------------------------------------------------------------------- + * + * Description: Tells the name associated with the TempFsysName object. + * + * Arguments: None. + * + * Returns: A pointer to a constant C-style string containing the name + * associated with the TempFsysName object. If the TempFsysName + * object has not yet been (successfully) associated with a file + * using TempFsysName::allocate, this pointer has the value 0. + * + * ------------------------------------------------------------------------- */ +{ + return name; +} + +/* ========================================================================= */ +inline void TempFsysName::releaseName() +/* ---------------------------------------------------------------------------- + * + * Description: Deallocates the memory reserved for `this->name' and frees + * the name also in the file system by removing the file or + * directory associated with the object. If the name + * is associated with a directory, the directory is assumed to + * be empty. + * + * Arguments: None. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + if (name == static_cast(0)) + return; + if (type == FILE) + remove(name); + else + rmdir(name); + delete[] name; + name = 0; +} + +#endif /* !TEMPFSYSNAME_H */ diff --git a/lbtt/src/TestOperations.cc b/lbtt/src/TestOperations.cc index 12f2f91ec..5e3b3ed64 100644 --- a/lbtt/src/TestOperations.cc +++ b/lbtt/src/TestOperations.cc @@ -18,28 +18,50 @@ */ #include +#include +#include #include #include +#include +#ifdef HAVE_SYS_STAT_H +#include +#endif /* HAVE_SYS_STAT_H */ +#ifdef HAVE_SYS_TIMES_H #include +#endif /* HAVE_SYS_TIMES_H */ +#ifdef HAVE_SYS_TYPES_H +#include +#endif /* HAVE_SYS_TYPES_H */ +#ifdef HAVE_SYS_WAIT_H +#include +#endif /* HAVE_SYS_WAIT_H */ #ifdef HAVE_UNISTD_H #include #endif /* HAVE_UNISTD_H */ +#ifdef HAVE_FCNTL_H +#include +#endif /* HAVE_FCNTL_H */ #include "BitArray.h" #include "BuchiAutomaton.h" +#include "BuchiProduct.h" #include "DispUtil.h" +#include "Product.h" +#include "IntervalList.h" #include "LtlFormula.h" #include "PathEvaluator.h" -#include "ProductAutomaton.h" #include "Random.h" -#include "SccIterator.h" +#include "SccCollection.h" #include "SharedTestData.h" #include "PathIterator.h" #include "StateSpace.h" +#include "StateSpaceProduct.h" #include "StatDisplay.h" #include "StringUtil.h" +#include "TempFsysName.h" #include "TestOperations.h" #include "TestRoundInfo.h" -#include "TestStatistics.h" + + /****************************************************************************** * @@ -55,6 +77,19 @@ using namespace ::StatDisplay; using namespace ::StringUtil; using namespace ::DispUtil; +/****************************************************************************** + * + * Timeout handler. + * + *****************************************************************************/ + +bool timeout = false; + +void timeoutHandler(int) +{ + timeout = true; +} + /* ========================================================================= */ void openFile (const char* filename, ifstream& stream, ios::openmode mode, int indent) @@ -129,13 +164,19 @@ void openFile } /* ========================================================================= */ -void removeFile(const char* filename, int indent) +void openFile(const char* filename, int& fd, int flags, int indent) /* ---------------------------------------------------------------------------- * - * Description: Removes a file. + * Description: Function for opening a file for input/output using file + * descriptors. * * Arguments: filename -- A pointer to a constant C-style string with - * the name of the file to be removed. + * the name of the file to be opened. + * fd -- A reference to an int that should be associated + * with the file descriptor of the file. This + * variable will have the value -1 if the + * operation fails. + * flags -- An integer specifying the open mode. * indent -- Number of spaces to leave to the left of * messages given to the user. * @@ -143,10 +184,52 @@ void removeFile(const char* filename, int indent) * * ------------------------------------------------------------------------- */ { - printText(string("", 5, indent); + printText(string("<") + (flags & O_CREAT ? "creat" : "open") + "ing `" + + filename + "'>", + 5, + indent); - if (remove(filename) == 0) + if (flags & O_CREAT) + fd = open(filename, flags, S_IRUSR | S_IWUSR); + else + fd = open(filename, flags); + + if (fd == -1) + { + printText(" error\n", 5); + + if (flags & O_CREAT) + throw FileCreationException(string("`") + filename + "'"); + else + throw FileOpenException(string("`") + filename + "'"); + } + else printText(" ok\n", 5); +} + +/* ========================================================================= */ +void truncateFile(const char* filename, int indent) +/* ---------------------------------------------------------------------------- + * + * Description: Truncates a file. + * + * Arguments: filename -- A pointer to a constant C-style string with + * the name of the file to be truncated. + * indent -- Number of spaces to leave to the left of + * messages given to the user. + * + * Returns: Nothing. + * + * ------------------------------------------------------------------------- */ +{ + printText(string("", 5, indent); + + int fd = open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + if (fd != -1) + { + close(fd); + printText(" ok\n", 5); + } else printText(" error\n", 5); } @@ -617,9 +700,9 @@ void verifyFormulaOnPath() if (printText("Model checking formula using internal algorithm\n", 2, 4)) printText("", 4, 6); - test_results[round_info.number_of_translators].automaton_stats[0]. + test_results[round_info.number_of_translators - 1].automaton_stats[0]. emptiness_check_result.clear(); - test_results[round_info.number_of_translators].automaton_stats[1]. + test_results[round_info.number_of_translators - 1].automaton_stats[1]. emptiness_check_result.clear(); try @@ -637,17 +720,18 @@ void verifyFormulaOnPath() s++) { if (path_evaluator.getResult(s)) - test_results[round_info.number_of_translators].automaton_stats[0]. + test_results[round_info.number_of_translators - 1].automaton_stats[0]. emptiness_check_result.setBit(s); else - test_results[round_info.number_of_translators].automaton_stats[1]. + test_results[round_info.number_of_translators - 1].automaton_stats[1]. emptiness_check_result.setBit(s); } } catch (const UserBreakException&) { - if (!printText(" user break\n\n", 4)) - printText("[User break]\n\n", 2, 6); + if (!printText(" aborted (user break)", 4)) + printText("[User break]", 2, 6); + printText("\n\n", 2); if (round_info.transcript_file.is_open()) writeToTranscript("User break while model checking formulas. No tests " @@ -657,8 +741,9 @@ void verifyFormulaOnPath() } catch (const bad_alloc&) { - if (!printText(" out of memory\n\n", 4)) - printText("[Out of memory]\n\n", 2, 6); + if (!printText(" aborted (out of memory)", 4)) + printText("[Out of memory]", 2, 6); + printText("\n\n", 2); if (round_info.transcript_file.is_open()) writeToTranscript("Out of memory while model checking formulas. No " @@ -671,9 +756,9 @@ void verifyFormulaOnPath() printText(" ok\n", 4); printText("\n", 2); - test_results[round_info.number_of_translators].automaton_stats[0]. + test_results[round_info.number_of_translators - 1].automaton_stats[0]. emptiness_check_performed = true; - test_results[round_info.number_of_translators].automaton_stats[1]. + test_results[round_info.number_of_translators - 1].automaton_stats[1]. emptiness_check_performed = true; } @@ -699,17 +784,18 @@ void writeFormulaeToFiles() if (!round_info.formula_in_file[f]) { - printText(string("\n", - 5, - 6); - try { - openFile(round_info.formula_file_name[f], formula_file, + openFile(round_info.formula_file_name[f]->get(), formula_file, ios::out | ios::trunc, 6); + printText(string("get() + + "'>\n", + 5, + 6); + round_info.formulae[configuration.formula_options. output_mode == Configuration::NNF ? f @@ -763,11 +849,10 @@ void generateBuchiAutomaton = test_results[algorithm_id].automaton_stats[f]; if (automaton_stats.buchiAutomatonComputed()) - printText("Büchi automaton (cached):\n", 2, 8); + printText("Büchi automaton (cached):\n", 3, 8); else { - if (!printText("Büchi automaton:\n", 3, 8)) - printText("Computing Büchi automaton\n", 2, 8); + printText("Büchi automaton:\n", 3, 8); const Configuration::AlgorithmInformation& algorithm = configuration.algorithms[algorithm_id]; @@ -778,129 +863,340 @@ void generateBuchiAutomaton struct tms timing_information_begin, timing_information_end; + string failure_reason; + int stdout_capture_fileno = -1, stderr_capture_fileno = -1; int exitcode; - string command_line; + + sigset_t sigint_mask; + sigemptyset(&sigint_mask); + sigaddset(&sigint_mask, SIGINT); + + struct sigaction timeout_sa; + timeout_sa.sa_handler = timeoutHandler; + sigemptyset(&timeout_sa.sa_mask); + timeout_sa.sa_flags = 0; + + pid_t pid = 0; + + truncateFile(round_info.automaton_file_name->get(), 10); + truncateFile(round_info.cout_capture_file->get(), 10); + truncateFile(round_info.cerr_capture_file->get(), 10); try { - /* - * Generate the command line to be used for executing the program that - * is supposed to generate the automaton from a formula. The name of the - * executable and any extra command line parameters are obtained from - * the program configuration data structures. The command line will be - * of the form - * [path to executable] [additional parameters] [input file] - * [output file] 1>[stdout capture file] - * 2>[stderr capture file], - * so the program used for generating the automaton is assumed to follow - * this format for passing the necessary input and output filenames. - * Note: This kind of output redirection requires that the call to - * `system' invokes a POSIX compatible shell! - * - * The output of stdout and stderr will be redirected to two temporary - * files. - */ - - command_line = *(algorithm.path_to_program) + ' '; - - if (algorithm.extra_parameters != 0) - command_line += *(algorithm.extra_parameters) + ' '; - - command_line += string(round_info.formula_file_name[f]) - + ' ' + round_info.automaton_file_name - + " 1>" + round_info.cout_capture_file - + " 2>" + round_info.cerr_capture_file; - - if (!printText("", 5, 10)) - printText("", 4, 10); - - /* - * Execute the translator and record its running time (user time) into - * `automaton_stats.buchi_generation_time'. (The variable will have a - * negative value if the time could not be determined.) - */ - automaton_stats.buchi_generation_time = -1.0; - times(&timing_information_begin); - - exitcode = system(command_line.c_str()); - - times(&timing_information_end); - - if (timing_information_begin.tms_utime != static_cast(-1) - && timing_information_begin.tms_cutime != static_cast(-1) - && timing_information_end.tms_utime != static_cast(-1) - && timing_information_end.tms_cutime != static_cast(-1)) - automaton_stats.buchi_generation_time - = static_cast(timing_information_end.tms_utime - + timing_information_end.tms_cutime - - timing_information_begin.tms_utime - - timing_information_begin.tms_cutime) - / sysconf(_SC_CLK_TCK); - /* - * Nonzero exit codes from the external program are interpreted as - * errors. In this case, throw an exception indicating that the program - * execution failed. + * Redirect standard output and standard error to files. */ - if (exitcode != 0) + try { + openFile(round_info.cout_capture_file->get(), stdout_capture_fileno, + O_CREAT | O_WRONLY | O_TRUNC, 10); + + openFile(round_info.cerr_capture_file->get(), stderr_capture_fileno, + O_CREAT | O_WRONLY | O_TRUNC, 10); + } + catch (const FileCreationException&) + { + if (stdout_capture_fileno != -1) + { + close(stdout_capture_fileno); + stdout_capture_fileno = -1; + } + printText(string(9, ' '), 4); + throw Exception(string("redirection of standard ") + + (stdout_capture_fileno == -1 ? "output" : "error") + + " failed (" + + string(strerror(errno)) + ")"); + } + + /* Execute the external program. */ + + if (!printText("", 5, 10)) + printText("", 4, 10); + + int error_number; + int error_pipe[2]; /* used for communicating errors in exec() */ + + double elapsed_time = -1.0; + + if (pipe(error_pipe) == -1) + error_number = errno; + else + { + algorithm.parameters[algorithm.num_parameters + 1] + = const_cast(round_info.formula_file_name[f]->get()); + algorithm.parameters[algorithm.num_parameters + 2] + = const_cast(round_info.automaton_file_name->get()); + + times(&timing_information_begin); + pid = fork(); + switch (pid) + { + case 0 : /* child */ + close(error_pipe[0]); + + if (dup2(stdout_capture_fileno, STDOUT_FILENO) != -1 + && dup2(stderr_capture_fileno, STDERR_FILENO) != -1) + execvp(algorithm.parameters[0], algorithm.parameters); + + /* dup2 or exec failed: write the value of errno to error_pipe */ + + write(error_pipe[1], static_cast(&errno), + sizeof(int)); + close(error_pipe[1]); + exit(0); + + case -1 : /* fork failed */ + pid = 0; + error_number = errno; + close(error_pipe[0]); + close(error_pipe[1]); + break; + + default : /* parent */ + /* Block SIGINT signals while the child process is running. */ + + if (configuration.global_options.handle_breaks) + sigprocmask(SIG_BLOCK, &sigint_mask, static_cast(0)); + + /* Install handler for timeouts if necessary. */ + + if (configuration.global_options.translator_timeout > 0) + { + sigaction(SIGALRM, &timeout_sa, + static_cast(0)); + timeout = false; + alarm(configuration.global_options.translator_timeout); + } + + close(error_pipe[1]); + + if (waitpid(pid, &exitcode, 0) == -1) /* waitpid failed */ + { + error_number = errno; + if (error_number == EINTR /* failure due to timeout */ + && configuration.global_options.translator_timeout > 0 + && timeout) + { + /* + * Try to terminate the child process three times with SIGTERM + * (sleeping for one second between the tries). If the child + * fails to respond, try to terminate the child one more time + * with SIGKILL. + */ + + int sig = SIGTERM; + unsigned int delay = 1; + for (int attempts_to_terminate = 0; attempts_to_terminate < 4; + ++attempts_to_terminate) + { + kill(pid, sig); + sleep(delay); + if (waitpid(pid, &exitcode, WNOHANG) != 0) + { + times(&timing_information_end); + pid = 0; + break; + } + if (attempts_to_terminate == 2) + { + sig = SIGKILL; + delay = 5; + } + } + } + else + pid = 0; + } + else /* child exited successfully */ + { + times(&timing_information_end); + pid = 0; + + /* + * If there is something to be read from error_pipe, then there + * was an error in replacing the child process with the external + * program (and the pipe contains the value of errno in this + * case). + */ + + if (read(error_pipe[0], static_cast(&error_number), + sizeof(int)) == 0) + error_number = 0; + } + + close(error_pipe[0]); + + /* Restore signal handlers and remove any pending alarms. */ + + if (configuration.global_options.translator_timeout > 0) + { + timeout_sa.sa_handler = SIG_DFL; + sigaction(SIGALRM, &timeout_sa, + static_cast(0)); + alarm(0); + } + + if (configuration.global_options.handle_breaks) + sigprocmask(SIG_UNBLOCK, &sigint_mask, + static_cast(0)); + + if (pid == 0 + && timing_information_begin.tms_utime + != static_cast(-1) + && timing_information_begin.tms_cutime + != static_cast(-1) + && timing_information_end.tms_utime != static_cast(-1) + && timing_information_end.tms_cutime + != static_cast(-1)) + elapsed_time = static_cast + (timing_information_end.tms_utime + + timing_information_end.tms_cutime + - timing_information_begin.tms_utime + - timing_information_begin.tms_cutime) + / sysconf(_SC_CLK_TCK); + + break; + } + } + + close(stdout_capture_fileno); + close(stderr_capture_fileno); + + /* + * If pid != 0 at this point, then a timeout occurred, but lbtt was + * unable to terminate the child process. The exception handler will + * in this case throw an unexpected exception (see below) so that lbtt + * will terminate (for example, it is not safe to use the temporary + * file names any longer if the (still running) child process happens to + * write to them). + */ + + if (pid != 0) + { + stdout_capture_fileno = stderr_capture_fileno = -1; + throw Exception("could not terminate child process"); + } + + if (error_number != 0) /* pipe, fork, dup2, execvp or waitpid failed */ + { + stdout_capture_fileno = stderr_capture_fileno = -1; ExecFailedException e; - e.changeMessage("Execution of `" + *(algorithm.path_to_program) - + "' failed" - + (automaton_stats.buchi_generation_time >= 0.0 - ? " (" - + toString(automaton_stats.buchi_generation_time, - 2) - + " seconds elapsed)" - : string("")) - + " with exit status " + toString(exitcode)); + + if (configuration.global_options.translator_timeout > 0 && timeout) + e.changeMessage("Timeout after " + toString(elapsed_time, 2) + + " seconds (user time)."); + else + e.changeMessage("Execution of `" + string(algorithm.parameters[0]) + + "' failed (" + string(strerror(error_number)) + + ")"); throw e; } - printText(" ok\n", 5); + automaton_stats.buchi_generation_time = elapsed_time; + + /* + * Nonzero exit codes from the external program are interpreted as + * errors. The same holds if the program was aborted by a signal. In + * these cases, throw an exception indicating that the program + * execution failed. + */ + + if (WIFSIGNALED(exitcode) + || (WIFEXITED(exitcode) && WEXITSTATUS(exitcode) != 0)) + { + ExecFailedException e; + failure_reason = "`" + string(algorithm.parameters[0]) + "' "; + + if (WIFSIGNALED(exitcode)) + { + failure_reason += "aborted by signal " + + toString(WTERMSIG(exitcode)) +#ifdef HAVE_STRSIGNAL + + " (" + strsignal(WTERMSIG(exitcode)) + ")" +#endif /* HAVE_STRSIGNAL */ + ; + if (WTERMSIG(exitcode) == SIGINT || WTERMSIG(exitcode) == SIGQUIT) + raise(WTERMSIG(exitcode)); + } + else + failure_reason += "exited with exit status " + + toString(WEXITSTATUS(exitcode)); + + e.changeMessage(failure_reason + + (automaton_stats.buchi_generation_time >= 0.0 + ? " after " + + toString(automaton_stats.buchi_generation_time, + 2) + + " seconds" + : string(""))); + + throw e; + } + + printText(" ok\n", 4); /* * Read the automaton description into memory from the result file. */ ifstream automaton_file; - openFile(round_info.automaton_file_name, automaton_file, ios::in, 10); + openFile(round_info.automaton_file_name->get(), automaton_file, ios::in, + 10); - printText("", 5, 10); + printText("", 4, 10); - automaton_file >> *buchi_automaton; - - printText(" ok\n", 4); + try + { + automaton_file >> *buchi_automaton; + } + catch (const bad_alloc&) + { + throw Exception("out of memory"); + } automaton_file.close(); + printText(" ok\n", 4); + automaton_stats.buchi_automaton = buchi_automaton; } catch (...) { delete buchi_automaton; - printText(" error\n", 4); - printText("Error", 2, 10); + if (user_break) + { + if (!printText(" aborted (user break)", 4)) + printText("[User break]", 1, + configuration.global_options.verbosity <= 2 ? 0 : 10); + printText("\n\n", 1); + + if (round_info.transcript_file.is_open()) + writeToTranscript("User break while generating Büchi automaton (" + + configuration.algorithmString(algorithm_id) + + ", " + + (f == 0 ? "posi" : "nega") + "tive formula)\n"); + + throw UserBreakException(); + } if (round_info.transcript_file.is_open()) - { writeToTranscript("Büchi automaton generation failed (" + configuration.algorithmString(algorithm_id) + ", " + (f == 0 ? "posi" : "nega") - + "tive formula)"); - - if (automaton_stats.buchi_generation_time >= 0.0) - round_info.transcript_file << string(8, ' ') + "Elapsed time: " - + toString(automaton_stats. - buchi_generation_time, - 2) - + " seconds (user time)\n"; - } + + "tive formula)" + + (automaton_stats.buchi_generation_time >= 0.0 + ? "\n" + string(8, ' ') + "Elapsed time: " + + toString(automaton_stats. + buchi_generation_time, + 2) + + " seconds (user time)" + : string(""))); try { @@ -908,95 +1204,104 @@ void generateBuchiAutomaton } catch (const ExecFailedException& e) { - printText(string(": ") + e.what(), 2); + if (configuration.global_options.translator_timeout > 0 && timeout) + { + if (!printText(" aborted (timeout)", 4)) + printText("[Timeout]", 1, + configuration.global_options.verbosity <= 2 ? 0 : 10); + } + else + { + if (!printText(string(" error: ") + e.what(), 4)) + printText("[Failed to execute translator]", 1, + configuration.global_options.verbosity <= 2 ? 0 : 10); + } + printText("\n", 3); if (round_info.transcript_file.is_open()) - round_info.transcript_file << string(8, ' ') - + "Program execution failed with exit " - "status " - + toString(exitcode); + round_info.transcript_file << string(8, ' ') + e.what() + "\n"; } catch (const BuchiAutomaton::AutomatonParseException& e) { - printText(string(" parsing input: ") + e.what(), 2); + if (!printText(string(" error parsing input: ") + e.what(), 4)) + printText("[Error parsing automaton]", 1, + configuration.global_options.verbosity <= 2 ? 0 : 10); + printText("\n", 3); if (round_info.transcript_file.is_open()) round_info.transcript_file << string(8, ' ') + "Error reading automaton: " - + e.what(); + + e.what() + + "\n"; } catch (const Exception& e) { - printText(string(": ") + e.what(), 2); + if (!printText(string(" lbtt internal error: ") + e.what(), 4)) + printText("[lbtt internal error]", 1, + configuration.global_options.verbosity <= 2 ? 0 : 10); + printText("\n", 3); if (round_info.transcript_file.is_open()) round_info.transcript_file << string(8, ' ') + "lbtt internal error: " - + e.what(); - } - catch (const bad_alloc&) - { - printText(": out of memory", 2); - if (round_info.transcript_file.is_open()) - round_info.transcript_file << string(8, ' ') - + "Out of memory while reading " - "automaton"; - } - - printText("\n", 2); - - if (round_info.transcript_file.is_open()) - round_info.transcript_file << '\n'; - - removeFile(round_info.automaton_file_name, 10); - - try - { - const char* msg = "Contents of stdout"; - - if (configuration.global_options.verbosity >= 3) - printFileContents(cout, msg, round_info.cout_capture_file, 10, "> "); - if (round_info.transcript_file.is_open()) - printFileContents(round_info.transcript_file, msg, - round_info.cout_capture_file, 8, "> "); - } - catch (const IOException&) - { + + e.what() + + "\n"; } try { - const char* msg = "Contents of stderr:"; + if (stdout_capture_fileno != -1) + { + const char* msg = "Contents of stdout"; - if (configuration.global_options.verbosity >= 3) - printFileContents(cout, msg, round_info.cerr_capture_file, 10, "> "); - if (round_info.transcript_file.is_open()) - printFileContents(round_info.transcript_file, msg, - round_info.cerr_capture_file, 8, "> "); + if (configuration.global_options.verbosity >= 3) + printFileContents(cout, msg, round_info.cout_capture_file->get(), + 10, "> "); + if (round_info.transcript_file.is_open()) + printFileContents(round_info.transcript_file, msg, + round_info.cout_capture_file->get(), 10, "> "); + } + + if (stderr_capture_fileno != -1) + { + const char* msg = "Contents of stderr:"; + + if (configuration.global_options.verbosity >= 3) + printFileContents(cout, msg, round_info.cerr_capture_file->get(), + 10, "> "); + if (round_info.transcript_file.is_open()) + printFileContents(round_info.transcript_file, msg, + round_info.cerr_capture_file->get(), 10, "> "); + } } catch (const IOException&) { } if (round_info.transcript_file.is_open()) + { round_info.transcript_file << '\n'; + round_info.transcript_file.flush(); + } - removeFile(round_info.cout_capture_file, 10); - removeFile(round_info.cerr_capture_file, 10); + if (pid != 0) /* fatal error, lbtt should be terminated */ + throw Exception + ("fatal internal error while generating Büchi automaton"); throw BuchiAutomatonGenerationException(); } - removeFile(round_info.automaton_file_name, 10); - if (configuration.global_options.verbosity >= 3) { - printFileContents(cout, "Contents of stdout:", - round_info.cout_capture_file, 10, "> "); - printFileContents(cout, "Contents of stderr:", - round_info.cerr_capture_file, 10, "> "); + try + { + printFileContents(cout, "Contents of stdout:", + round_info.cout_capture_file->get(), 10, "> "); + printFileContents(cout, "Contents of stderr:", + round_info.cerr_capture_file->get(), 10, "> "); + } + catch (const IOException&) + { + } } - removeFile(round_info.cout_capture_file, 10); - removeFile(round_info.cerr_capture_file, 10); - printText("", 4, 10); pair buchi_stats @@ -1028,143 +1333,10 @@ void generateBuchiAutomaton printText(" ok\n", 4); } - if (configuration.global_options.verbosity >= 3) + if (configuration.global_options.verbosity >= 1) printBuchiAutomatonStats(cout, 10, algorithm_id, f); } -/* ========================================================================= */ -void generateProductAutomaton - (int f, - vector::size_type algorithm_id) -/* ---------------------------------------------------------------------------- - * - * Description: Computes the product of a Büchi automaton with a state - * space. - * - * Arguments: f -- Indicates the Büchi automaton with which - * the product should be computed. 0 - * corresponds to the automaton obtained from - * the positive, 1 corresponds to the one - * obtained from the negated formula. - * algorithm_id -- Identifier of the LTL-to-Büchi translator - * used for generating the Büchi automata. - * - * Returns: Nothing. The result is stored in - * 'round_info.product_automaton'. - * - * ------------------------------------------------------------------------- */ -{ - using ::Graph::ProductAutomaton; - - AutomatonStats& automaton_stats - = test_results[algorithm_id].automaton_stats[f]; - - if (automaton_stats.emptiness_check_performed) - return; - - if (printText("Product automaton:\n", 3, 8)) - printText("", 4, 10); - else - printText("Computing product automaton\n", 2, 8); - - final_statistics[algorithm_id].product_automaton_count[f]++; - - ProductAutomaton* product_automaton = 0; - - /* - * Initialize the product automaton. The variable - * `configuration.global_options.product_mode' is used to determine whether - * the product is to be computed with respect to all the states of the - * state space or only the initial state of the state space. - */ - - try - { - product_automaton = new ProductAutomaton(); - product_automaton->computeProduct - (*automaton_stats.buchi_automaton, *round_info.statespace, - configuration.global_options.product_mode == Configuration::GLOBAL); - } - catch (const ProductAutomaton::ProductSizeException&) - { - if (!printText(" error (product may be too large)\n\n", 4)) - printText("[Product may be too large]\n\n", 2, 10); - - if (round_info.transcript_file.is_open()) - writeToTranscript("Product automaton generation aborted (" - + configuration.algorithmString(algorithm_id) - + ", " - + (f == 0 ? "posi" : "nega") + "tive formula)" - + ". Product may be too large.\n"); - - delete product_automaton; - - throw ProductAutomatonGenerationException(); - } - catch (const UserBreakException&) - { - if (!printText(" user break\n\n", 4)) - printText("[User break]\n\n", 2, 10); - - if (round_info.transcript_file.is_open()) - writeToTranscript("User break while generating product automaton (" - + configuration.algorithmString(algorithm_id) - + ", " - + (f == 0 ? "posi" : "nega") + "tive formula)\n"); - - delete product_automaton; - - throw; - } - catch (const bad_alloc&) - { - if (product_automaton != 0) - delete product_automaton; - - if (!printText(" out of memory\n", 4)) - printText("[Out of memory]\n", 2, 10); - - if (round_info.transcript_file.is_open()) - writeToTranscript("Out of memory while generating product " - "automaton (" - + configuration.algorithmString(algorithm_id) - + ", " - + (f == 0 ? "posi" : "nega") + "tive formula)\n"); - - throw ProductAutomatonGenerationException(); - } - - round_info.product_automaton = product_automaton; - - /* - * Determine the number of states and transitions in the product. - */ - - if (printText(" ok\n", 4)) - printText("", 4, 10); - - pair product_stats = - product_automaton->stats(); - - automaton_stats.number_of_product_states = product_stats.first; - automaton_stats.number_of_product_transitions = product_stats.second; - - /* - * Update product automaton statistics for the given algorithm. - */ - - final_statistics[algorithm_id].total_number_of_product_states[f] - += automaton_stats.number_of_product_states; - final_statistics[algorithm_id].total_number_of_product_transitions[f] - += automaton_stats.number_of_product_transitions; - - printText(" ok\n", 4); - - if (configuration.global_options.verbosity >= 3) - printProductAutomatonStats(cout, 10, algorithm_id, f); -} - /* ========================================================================= */ void performEmptinessCheck (int f, @@ -1194,30 +1366,71 @@ void performEmptinessCheck AutomatonStats& automaton_stats = test_results[algorithm_id].automaton_stats[f]; - if (automaton_stats.emptiness_check_performed) - printText("Accepting cycles (cached):\n", 2, 8); - else + const bool result_cached = automaton_stats.emptiness_check_performed; + + printText("Product automaton" + + string(result_cached ? " (cached)" : "") + + ":\n", + 3, + 8); + + if (!result_cached) { - if (printText("Accepting cycles:\n", 3, 8)) - printText("", 4, 10); - else - printText("Searching for accepting cycles\n", 2, 8); + printText("", 4, 10); + + final_statistics[algorithm_id].product_automaton_count[f]++; + + using ::Graph::StateSpaceProduct; + using ::Graph::Product; try { - round_info.product_automaton->emptinessCheck - (automaton_stats.emptiness_check_result); - + Product + product(*automaton_stats.buchi_automaton, *round_info.statespace); + + const pair::size_type, unsigned long int> + product_stats = product.globalEmptinessCheck + (automaton_stats.buchi_automaton->initialState(), + automaton_stats.emptiness_check_result, + round_info.real_emptiness_check_size); + + printText(" ok\n", 4); + + automaton_stats.number_of_product_states = product_stats.first; + automaton_stats.number_of_product_transitions = product_stats.second; + + final_statistics[algorithm_id].total_number_of_product_states[f] + += automaton_stats.number_of_product_states; + final_statistics[algorithm_id].total_number_of_product_transitions[f] + += automaton_stats.number_of_product_transitions; automaton_stats.emptiness_check_performed = true; } + catch (const Product::SizeException&) + { + if (!printText(" aborted (product may be too large)", 4)) + printText("[Product may be too large]", 1, + configuration.global_options.verbosity <= 2 ? 0 : 10); + printText("\n", 3); + + if (round_info.transcript_file.is_open()) + writeToTranscript("Product automaton generation aborted (" + + configuration.algorithmString(algorithm_id) + + ", " + + (f == 0 ? "posi" : "nega") + "tive formula)" + + ". Product may be too large.\n"); + + throw ProductAutomatonGenerationException(); + } catch (const UserBreakException&) { - if (!printText(" user break\n\n", 4)) - printText("[User break]\n\n", 2, 10); - + if (!printText(" aborted (user break)", 4)) + printText("[User break]", 1, + configuration.global_options.verbosity <= 2 ? 0 : 10); + printText("\n\n", 1); + if (round_info.transcript_file.is_open()) - writeToTranscript("User break while searching for accepting cycles (" - + configuration.algorithmString(algorithm_id) + writeToTranscript("User break while generating product automaton (" + + configuration.algorithmString(algorithm_id) + ", " + (f == 0 ? "posi" : "nega") + "tive formula)\n"); @@ -1225,24 +1438,32 @@ void performEmptinessCheck } catch (const bad_alloc&) { - if (!printText(" out of memory\n", 4)) - printText("[Out of memory]\n", 2, 10); + if (!printText(" aborted (out of memory)", 4)) + printText("[Out of memory]", 1, + configuration.global_options.verbosity <= 2 ? 0 : 10); + printText("\n", 3); if (round_info.transcript_file.is_open()) - writeToTranscript("Out of memory while searching for accepting cycles " - "(" + writeToTranscript("Out of memory while generating product " + "automaton (" + configuration.algorithmString(algorithm_id) + ", " + (f == 0 ? "posi" : "nega") + "tive formula)\n"); - throw EmptinessCheckFailedException(); + throw ProductAutomatonGenerationException(); } - - printText(" ok\n", 4); } - if (configuration.global_options.verbosity >= 3) + if (configuration.global_options.verbosity >= 1) + printProductAutomatonStats(cout, 10, algorithm_id, f); + + printText("Accepting cycles" + string(result_cached ? " (cached)" : "") + + ":\n", + 3, + 8); + if (configuration.global_options.verbosity >= 1) printAcceptanceCycleStats(cout, 10, algorithm_id, f); + } /* ========================================================================= */ @@ -1277,8 +1498,6 @@ void performConsistencyCheck if (printText("Result consistency check:\n", 3, 6)) printText("", 4, 8); - else - printText("Checking consistency of the model checking results\n", 2, 6); /* * The consistency check will succeed if `result' is still true at the end @@ -1319,7 +1538,7 @@ void performConsistencyCheck printText((result ? " ok\n" : " failed\n"), 4); - if (configuration.global_options.verbosity >= 3) + if (configuration.global_options.verbosity >= 2) printConsistencyCheckStats(cout, 8, algorithm_id); } @@ -1340,7 +1559,7 @@ void compareResults() if (printText("Model checking result cross-comparison:\n", 3, 4)) printText("", 4, 6); else - printText("Comparing model checking results\n", 2, 4); + printText("Comparing model checking results", 2, 4); bool result = true; AutomatonStats* alg_1_stats; @@ -1408,6 +1627,9 @@ void compareResults() } } + IntervalList algorithms; + algorithms.merge(0, round_info.number_of_translators - 1); + if (!result) { round_info.error = true; @@ -1416,13 +1638,20 @@ void compareResults() { writeToTranscript("Model checking result cross-comparison check failed"); printCrossComparisonStats(round_info.transcript_file, 8, - configuration.algorithms.size()); + algorithms); } } printText((result ? " ok\n" : " failed\n"), 4); - if (configuration.global_options.verbosity >= 3) - printCrossComparisonStats(cout, 6, test_results.size()); + if (configuration.global_options.verbosity == 2) + { + if (!result) + round_info.cout << " [failed]"; + round_info.cout << '\n'; + round_info.cout.flush(); + } + else if (configuration.global_options.verbosity >= 3) + printCrossComparisonStats(cout, 6, algorithms); } /* ========================================================================= */ @@ -1438,15 +1667,14 @@ void performBuchiIntersectionCheck() * * ------------------------------------------------------------------------- */ { - using ::Graph::SccIterator; - if (printText("Büchi automata intersection emptiness check:\n", 3, 4)) printText("\n", 4, 6); else - printText("Checking Büchi automata intersections for emptiness\n", 2, 4); + printText("Checking Büchi automata intersections for emptiness", 2, 4); bool result = true; - BuchiAutomaton* automaton_intersection; + + ::Graph::BuchiProduct::clearSatisfiabilityCache(); for (vector::size_type alg_1 = 0; @@ -1458,6 +1686,10 @@ void performBuchiIntersectionCheck() alg_2 < round_info.number_of_translators; ++alg_2) { + if (configuration.isInternalAlgorithm(alg_1) + || configuration.isInternalAlgorithm(alg_2)) + continue; + try { if (test_results[alg_1].automaton_stats[0]. @@ -1477,12 +1709,9 @@ void performBuchiIntersectionCheck() && test_results[alg_2].automaton_stats[1]. buchiAutomatonComputed()) { - automaton_intersection = 0; - - automaton_intersection - = BuchiAutomaton::intersect - (*(test_results[alg_1].automaton_stats[0].buchi_automaton), - *(test_results[alg_2].automaton_stats[1].buchi_automaton)); + using ::Graph::BuchiAutomaton; + using ::Graph::BuchiProduct; + using ::Graph::Product; /* * Scan the nontrivial maximal strongly connected components of @@ -1492,85 +1721,15 @@ void performBuchiIntersectionCheck() * fails. */ - for (SccIterator scc(*automaton_intersection); - !scc.atEnd(); ++scc) - { - /* - * MSCC nontriviality check (an MSCC is nontrivial iff it has at - * least two states or if it consists of a single state - * connected to itself). - */ + const BuchiAutomaton& a1 + = *(test_results[alg_1].automaton_stats[0].buchi_automaton); + const BuchiAutomaton& a2 + = *(test_results[alg_2].automaton_stats[1].buchi_automaton); - if (scc->size() > 1 - || (scc->size() == 1 - && automaton_intersection->connected(*(scc->begin()), - *(scc->begin())))) - { - const unsigned long int number_of_acceptance_sets - = automaton_intersection->numberOfAcceptanceSets(); - BitArray acceptance_sets(number_of_acceptance_sets); - acceptance_sets.clear(number_of_acceptance_sets); + Product product(a1, a2); - unsigned long int accept_set; - unsigned long int acceptance_set_counter = 0; - - for (set, - ALLOC(GraphEdgeContainer::size_type) >::const_iterator - state = scc->begin(); - state != scc->end() - && acceptance_set_counter < number_of_acceptance_sets; - ++state) - { - accept_set = acceptance_set_counter; - while (accept_set < number_of_acceptance_sets) - { - if ((*automaton_intersection)[*state].acceptanceSets(). - test(accept_set)) - { - acceptance_sets.setBit(accept_set); - if (accept_set == acceptance_set_counter) - { - do - acceptance_set_counter++; - while (acceptance_set_counter - < number_of_acceptance_sets - && acceptance_sets[acceptance_set_counter]); - accept_set = acceptance_set_counter; - continue; - } - } - accept_set++; - } - } - - if (acceptance_set_counter == number_of_acceptance_sets) - { - test_results[alg_1].automaton_stats[0]. - buchi_intersection_check_stats[alg_2] = 0; - test_results[alg_2].automaton_stats[1]. - buchi_intersection_check_stats[alg_1] = 0; - - final_statistics[alg_1]. - buchi_intersection_check_failures[alg_2]++; - if (alg_1 != alg_2) - final_statistics[alg_2]. - buchi_intersection_check_failures[alg_1]++; - - result = false; - - printText(": failed\n", 4); - - break; - } - } - } - - delete automaton_intersection; - automaton_intersection = 0; - - if (test_results[alg_1].automaton_stats[0]. - buchi_intersection_check_stats[alg_2] == -1) + if (!product.localEmptinessCheck(a1.initialState(), + a2.initialState())) { test_results[alg_1].automaton_stats[0]. buchi_intersection_check_stats[alg_2] = 1; @@ -1579,6 +1738,23 @@ void performBuchiIntersectionCheck() printText(": ok\n", 4); } + else + { + test_results[alg_1].automaton_stats[0]. + buchi_intersection_check_stats[alg_2] = 0; + test_results[alg_2].automaton_stats[1]. + buchi_intersection_check_stats[alg_1] = 0; + + final_statistics[alg_1] + .buchi_intersection_check_failures[alg_2]++; + if (alg_1 != alg_2) + final_statistics[alg_2] + .buchi_intersection_check_failures[alg_1]++; + + result = false; + + printText(": failed\n", 4); + } final_statistics[alg_1]. buchi_intersection_checks_performed[alg_2]++; @@ -1595,8 +1771,9 @@ void performBuchiIntersectionCheck() } catch (const UserBreakException&) { - if (!printText(" user break\n\n", 4)) - printText("[User break]\n\n", 2, 6); + if (!printText(": aborted (user break)", 4)) + printText(" [User break]", 2, 6); + printText("\n\n", 2); if (round_info.transcript_file.is_open()) writeToTranscript("User break during Büchi automata intersection " @@ -1607,30 +1784,19 @@ void performBuchiIntersectionCheck() + configuration.algorithmString(alg_2) + "\n\n"; - if (automaton_intersection != 0) - { - delete automaton_intersection; - automaton_intersection = 0; - } - throw; } catch (const bad_alloc&) { - if (automaton_intersection != 0) - { - delete automaton_intersection; - automaton_intersection = 0; - } - - if (!printText(" out of memory\n", 4)) - printText("[Out of memory: (+) " + if (!printText(": aborted (out of memory)", 4)) + printText(" [Out of memory: (+) " + configuration.algorithmString(alg_1) + ", (-) " + configuration.algorithmString(alg_2) - + "]\n", + + "]", 2, 6); + printText("\n", 2); if (round_info.transcript_file.is_open()) writeToTranscript("Out of memory during Büchi automata " @@ -1644,6 +1810,9 @@ void performBuchiIntersectionCheck() } } + IntervalList algorithms; + algorithms.merge(0, round_info.number_of_translators - 1); + if (!result) { round_info.error = true; @@ -1652,13 +1821,44 @@ void performBuchiIntersectionCheck() { writeToTranscript("Büchi automata intersection emptiness check failed"); printBuchiIntersectionCheckStats - (round_info.transcript_file, 8, round_info.number_of_translators); + (round_info.transcript_file, 8, algorithms); + round_info.transcript_file << '\n'; } + + if (configuration.global_options.verbosity == 2) + round_info.cout << " [failed]"; } - if (configuration.global_options.verbosity >= 3) - printBuchiIntersectionCheckStats - (cout, 6, round_info.number_of_translators); + if (configuration.global_options.verbosity == 2) + { + round_info.cout << '\n'; + round_info.cout.flush(); + } + else if (configuration.global_options.verbosity >= 3) + { + printBuchiIntersectionCheckStats(cout, 6, algorithms); + round_info.cout << '\n'; + round_info.cout.flush(); + } } } + + + +/****************************************************************************** + * + * Definitions for static members for specializations of the Product template. + * + *****************************************************************************/ + +namespace Graph +{ + +template <> +Product* Product::product = 0; + +template <> +Product* Product::product = 0; + +} diff --git a/lbtt/src/TestOperations.h b/lbtt/src/TestOperations.h index f96ccdac3..9c31a0ad0 100644 --- a/lbtt/src/TestOperations.h +++ b/lbtt/src/TestOperations.h @@ -29,6 +29,7 @@ #include "Configuration.h" #include "Exception.h" #include "StateSpace.h" +#include "TestStatistics.h" using namespace std; @@ -53,7 +54,11 @@ void openFile /* Opens a file for */ (const char* filename, ofstream& stream, /* writing. */ ios::openmode mode, int indent = 0); -void removeFile /* Removes a file. */ +void openFile /* Opens a file for */ + (const char* filename, int& fd, int flags, /* input/output using */ + int indent = 0); /* file descriptors. */ + +void truncateFile /* Truncates a file. */ (const char* filename, int indent = 0); void printFileContents /* Displays the contents */ @@ -97,13 +102,6 @@ void generateBuchiAutomaton /* Generates a B * for the conversion. */ -void generateProductAutomaton /* Computes the */ - (int f, /* synchronous product */ - vector /* and a state space. */ - ::size_type - algorithm_id); - void performEmptinessCheck /* Performs an emptiness */ (int f, /* check on a product */ vector(0)), - formula_in_file(2, false) + formula_in_file(2, false), automaton_file_name(0), cout_capture_file(0), + cerr_capture_file(0) /* ---------------------------------------------------------------------------- * * Description: Constructor for class TestRoundInfo. Creates a new @@ -225,6 +225,7 @@ inline TestRoundInfo::TestRoundInfo() : * * ------------------------------------------------------------------------- */ { + formula_file_name[0] = formula_file_name[1] = 0; } /* ========================================================================= */ diff --git a/lbtt/src/TestStatistics.h b/lbtt/src/TestStatistics.h index 70cc6110e..71fb30de8 100644 --- a/lbtt/src/TestStatistics.h +++ b/lbtt/src/TestStatistics.h @@ -23,16 +23,16 @@ #include #include #include +#include "EdgeContainer.h" +#include "Graph.h" #include "LbttAlloc.h" #include "BuchiAutomaton.h" #include "Configuration.h" -#include "ProductAutomaton.h" #include "StateSpace.h" using namespace std; using Graph::BuchiAutomaton; using Graph::StateSpace; -using Graph::ProductAutomaton; /****************************************************************************** * @@ -107,7 +107,7 @@ struct AutomatonStats * Büchi automaton. */ - ProductAutomaton::size_type /* Number of stats in a */ + ::Graph::Graph::size_type /* Number of stats in a */ number_of_product_states; /* product automaton. */ unsigned long int number_of_product_transitions; /* Number of transitions in @@ -270,29 +270,21 @@ struct TestStatistics * checks performed. */ - unsigned long int /* Total number of */ - total_number_of_buchi_states[2]; /* states in all the - * generated Büchi - * automata. + BIGUINT total_number_of_buchi_states[2]; /* Total number of states + * in all the generated + * Büchi automata. */ - unsigned long int /* Total number of */ - total_number_of_buchi_transitions[2]; /* transitions in all + BIGUINT total_number_of_buchi_transitions[2]; /* Total number of + * transitions in all * the generated Büchi * automata. */ - unsigned long int /* Total number of sets */ - total_number_of_acceptance_sets[2]; /* of accepting states - * in all the generated - * Büchi automata. - */ - - unsigned long int /* Total number of */ - total_number_of_msccs[2]; /* maximal strongly - * connected components - * in the generated - * Büchi automata. + BIGUINT total_number_of_acceptance_sets[2]; /* Total number of sets of + * accepting states in all + * the generated Büchi + * automata. */ double total_buchi_generation_time[2]; /* Total time used when @@ -300,14 +292,13 @@ struct TestStatistics * automata. */ - unsigned long int /* Total number of */ - total_number_of_product_states[2]; /* states in all the - * generated product - * automata. + BIGUINT total_number_of_product_states[2]; /* Total number of states + * in all the generated + * product automata. */ - unsigned long int /* Total number of */ - total_number_of_product_transitions[2]; /* transitions in all the + BIGUINT total_number_of_product_transitions[2]; /* Total number of + * transitions in all the * generated product * automata. */ @@ -556,7 +547,6 @@ inline TestStatistics::TestStatistics total_number_of_buchi_states[i] = 0; total_number_of_buchi_transitions[i] = 0; total_number_of_acceptance_sets[i] = 0; - total_number_of_msccs[i] = 0; total_number_of_product_states[i] = 0; total_number_of_product_transitions[i] = 0; total_buchi_generation_time[i] = 0.0; diff --git a/lbtt/src/UserCommandReader.cc b/lbtt/src/UserCommandReader.cc index 364b7024b..a91b71403 100644 --- a/lbtt/src/UserCommandReader.cc +++ b/lbtt/src/UserCommandReader.cc @@ -28,7 +28,6 @@ #include #endif /* HAVE_SSTREAM */ #include "DispUtil.h" -#include "ProductAutomaton.h" #include "SharedTestData.h" #include "StatDisplay.h" #include "StringUtil.h" @@ -118,9 +117,6 @@ void executeUserCommands() { #endif /* HAVE_READLINE */ - ProductAutomaton* product_automaton = 0; - pair last_computed_product_automaton; - signal(SIGPIPE, SIG_IGN); while (1) @@ -170,35 +166,50 @@ void executeUserCommands() } #endif /* HAVE_ISATTY */ +#ifdef HAVE_READLINE + if (line != static_cast(0) /* line may be 0 on EOF */ + && input_line.find_first_not_of(" \t") != string::npos) + { + add_history(line); + free(line); + } +#endif /* HAVE_READLINE */ + + /* + * If the input line contains an unescaped pipe symbol ('|') outside + * quotes, extract an external command from the line. + */ + external_command = ""; - string::size_type pipe_pos = input_line.find_first_of('|'); - if (pipe_pos != string::npos) + string::size_type pos + = findInQuotedString(input_line, "|", OUTSIDE_QUOTES); + + if (pos != string::npos) { string::size_type nonspace_pos - = input_line.find_first_not_of(" \t", pipe_pos + 1); + = input_line.find_first_not_of(" \t", pos + 1); if (nonspace_pos != string::npos) { external_command = input_line.substr(nonspace_pos); - input_line = input_line.substr(0, pipe_pos); + input_line = input_line.substr(0, pos); } } - sliceString(input_line, " \t", input_tokens); + sliceString(substituteInQuotedString(input_line, " \t", "\n\n", + OUTSIDE_QUOTES), + "\n", + input_tokens); user_break = false; + + if (!input_tokens.empty() || !external_command.empty()) + { + round_info.cout << '\n'; + round_info.cout.flush(); + } if (!input_tokens.empty()) { -#ifdef HAVE_READLINE - if (line != static_cast(0)) /* line may be 0 on EOF */ - { - add_history(line); - free(line); - } -#endif /* HAVE_READLINE */ - round_info.cout << '\n'; - round_info.cout.flush(); - token = parseCommand(input_tokens[0]); if (token == CONTINUE || token == SKIP) @@ -406,8 +417,7 @@ void executeUserCommands() case BUCHIANALYZE : verifyArgumentCount(input_tokens, 2, 2); printAutomatonAnalysisResults(*output_stream, indent, - parseNumber(input_tokens[1]), - parseNumber(input_tokens[2])); + input_tokens); if (output_file != 0) round_info.cout << " Büchi automaton intersection emptiness " "check analysis"; @@ -430,8 +440,8 @@ void executeUserCommands() break; case FORMULA : - verifyArgumentCount(input_tokens, 0, 0); - printFormula(*output_stream, indent, formula_type); + verifyArgumentCount(input_tokens, 0, 1); + printFormula(*output_stream, indent, formula_type, input_tokens); if (output_file != 0) round_info.cout << string(" ") + (formula_type ? "Formula" @@ -458,8 +468,7 @@ void executeUserCommands() case RESULTANALYZE : verifyArgumentCount(input_tokens, 2, 3); printCrossComparisonAnalysisResults - (*output_stream, indent, formula_type, input_tokens, - product_automaton, last_computed_product_automaton); + (*output_stream, indent, formula_type, input_tokens); if (output_file != 0) round_info.cout << " Model checking result cross-comparison " "analysis"; @@ -501,8 +510,8 @@ void executeUserCommands() break; default : - throw CommandErrorException("Unknown command (`" - + input_tokens[0] + "')."); + throw CommandErrorException("Unknown command (`" + input_tokens[0] + + "')."); } if (output_string != 0) @@ -513,21 +522,17 @@ void executeUserCommands() FILE* output_pipe = popen(external_command.c_str(), "w"); if (output_pipe == NULL) throw ExecFailedException(external_command); - int status = fputs(outstring.c_str(), output_pipe); - if (status != EOF) - fflush(output_pipe); + fputs(outstring.c_str(), output_pipe); pclose(output_pipe); - round_info.cout << '\n'; - round_info.cout.flush(); - if (status == EOF) - throw IOException("Error writing to pipe."); } - else if (output_file != 0) + else { - round_info.cout << string(redirection_info.second - ? " appended" - : " written") - + " to `" + redirection_info.first + "'.\n\n"; + if (output_file != 0) + round_info.cout << string(redirection_info.second + ? " appended" + : " written") + + " to `" + redirection_info.first + "'.\n"; + round_info.cout << '\n'; round_info.cout.flush(); } } @@ -560,16 +565,6 @@ void executeUserCommands() } } - if (product_automaton != 0) - { - ::DispUtil::printText - ("", 4, 2); - - delete product_automaton; - - ::DispUtil::printText(" ok\n", 4); - } - #ifdef HAVE_READLINE } catch (...) @@ -582,7 +577,7 @@ void executeUserCommands() #endif /* HAVE_READLINE */ signal(SIGPIPE, SIG_DFL); -} +} /* ========================================================================= */ TokenType parseCommand(const string& token) @@ -597,19 +592,6 @@ TokenType parseCommand(const string& token) * * ------------------------------------------------------------------------- */ { - -/* - * gcc versions prior to version 3 do not conform to the C++ standard in their - * support for the string::compare functions. Use a macro to fix this if - * necessary. - */ - -#ifdef __GNUC__ -#if __GNUC__ < 3 -#define compare(start,end,str,dummy) compare(str,start,end) -#endif -#endif - TokenType token_type = UNKNOWN; string::size_type len = token.length(); bool ambiguous = false; @@ -706,8 +688,19 @@ TokenType parseCommand(const string& token) break; case 'i' : - if (token.compare(1, len - 1, "nconsistencies", len - 1) == 0) - token_type = INCONSISTENCIES; + if (len < 2) + ambiguous = true; + else if (token[1] == 'm') + { + if (token.compare(2, len - 2, "plementations", len - 2) == 0) + token_type = ALGORITHMS; + } + else if (token[1] == 'n') + { + if (token.compare(2, len - 2, "consistencies", len - 2) == 0) + token_type = INCONSISTENCIES; + } + break; case 'q' : @@ -787,6 +780,11 @@ TokenType parseCommand(const string& token) } break; + case 't' : + if (token.compare(1, len - 1, "ranslators", len - 1) == 0) + token_type = ALGORITHMS; + break; + case 'v' : if (token.compare(1, len - 1, "erbosity", len - 1) == 0) token_type = VERBOSITY; @@ -797,12 +795,6 @@ TokenType parseCommand(const string& token) throw CommandErrorException("Ambiguous command."); return token_type; - -#ifdef __GNUC__ -#if __GNUC__ < 3 -#undef compare -#endif -#endif } /* ========================================================================= */ @@ -867,13 +859,13 @@ pair parseRedirection if (token.length() > 2) { append = true; - filename = token.substr(2); + filename = unquoteString(token.substr(2)); input_tokens.pop_back(); } } else { - filename = token.substr(1); + filename = unquoteString(token.substr(1)); input_tokens.pop_back(); } } @@ -882,10 +874,9 @@ pair parseRedirection { string& token = *(input_tokens.rbegin() + 1); - if (token[0] == '>' && (token.length() == 1 - || (token.length() == 2 && token[1] == '>'))) + if (token == ">" || token == ">>") { - filename = input_tokens.back(); + filename = unquoteString(input_tokens.back()); append = (token.length() == 2); input_tokens.pop_back(); input_tokens.pop_back(); @@ -921,8 +912,7 @@ bool parseFormulaType(vector& input_tokens) { formula_type = (input_tokens[1] != "-"); - if (input_tokens[1].length() == 1 - && (input_tokens[1][0] == '+' || input_tokens[1][0] == '-')) + if (input_tokens[1] == "+" || input_tokens[1] == "-") input_tokens.erase(input_tokens.begin()); } diff --git a/lbtt/src/UserCommands.cc b/lbtt/src/UserCommands.cc index baf7e456b..e5ae8c72e 100644 --- a/lbtt/src/UserCommands.cc +++ b/lbtt/src/UserCommands.cc @@ -18,11 +18,14 @@ */ #include +#include "BuchiProduct.h" #include "DispUtil.h" #include "Exception.h" #include "PathEvaluator.h" +#include "Product.h" #include "SharedTestData.h" #include "StatDisplay.h" +#include "StateSpaceProduct.h" #include "StringUtil.h" #include "TestRoundInfo.h" #include "TestStatistics.h" @@ -45,84 +48,98 @@ using namespace ::StringUtil; using namespace ::UserCommandInterface; /* ========================================================================= */ -void computeProductAutomaton - (ProductAutomaton*& product_automaton, - const BuchiAutomaton& buchi_automaton, - pair& last_automaton, - const pair& new_automaton) +unsigned long int parseAlgorithmId(const string& id) /* ---------------------------------------------------------------------------- * - * Description: Computes a product automaton. + * Description: Parses an algorithm identifier (either a symbolic or a + * numeric one). * - * Arguments: product_automaton -- A reference to a pointer giving the - * storage location of the generated - * automaton. - * buchi_automaton -- The Büchi automaton to be used for - * computing the product automaton. - * last_automaton -- A pair telling the algorithm and the - * formula last used for computing an - * automaton (for testing whether the - * result is already available). - * new_automaton -- A pair telling the algorithm and the - * formula which are to be used for - * computing the automaton. + * Argument: id -- String containing the identifier. * - * Returns: Nothing. + * Returns: The numeric identifier of the algorithm. Throws a + * CommandErrorException if the identifier is not recognizable + * as a proper algorithm identifier. * * ------------------------------------------------------------------------- */ { - if (product_automaton == 0 || last_automaton != new_automaton) + unsigned long int result; + string unquoted_id = unquoteString(id); + + try { - if (product_automaton != 0) + result = parseNumber(unquoted_id); + verifyNumber(result, round_info.number_of_translators, + "Implementation identifier out of range"); + } + catch (const NotANumberException&) + { + map, ALLOC(unsigned long int) > + ::const_iterator id_finder + = configuration.algorithm_names.find(unquoted_id); + if (id_finder == configuration.algorithm_names.end()) + throw CommandErrorException + ("Unknown implementation identifier (`" + unquoted_id + "')."); + result = id_finder->second; + } + + return result; +} + +/* ========================================================================= */ +void parseAlgorithmIdList(const string& ids, IntervalList& algorithms) +/* ---------------------------------------------------------------------------- + * + * Description: Parses a list of algorithm identifiers specified either as + * comma-separated intervals or algorithm identifiers. + * + * Arguments: ids -- A constant reference to a string containing + * the list of algorithm identifiers. + * algorithms -- A reference to an IntervalList for storing + * the numeric identifiers of the algorithms. + * + * Returns: Nothing. Throws a CommandErrorException if the identifier + * list includes a string not recognizable as an algorithm + * identifier. + * + * ------------------------------------------------------------------------- */ +{ + /* + * Make a copy of `ids' in which each comma (',') within double quotes is + * substituted with a newline. This is necessary to handle symbolic + * algorithm identifiers with commas correctly. + */ + + string id_string = substituteInQuotedString(ids, ",", "\n", INSIDE_QUOTES); + + try + { + vector nonnumeric_algorithm_ids; + + parseIntervalList(id_string, algorithms, 0, + round_info.number_of_translators - 1, + &nonnumeric_algorithm_ids); + + for (vector::iterator + id = nonnumeric_algorithm_ids.begin(); + id != nonnumeric_algorithm_ids.end(); + ++id) { - printText("", 4, 2); - - delete product_automaton; - product_automaton = 0; - - printText(" ok\n", 4); + *id = unquoteString(substituteInQuotedString(*id, "\n", ",")); + map, ALLOC(unsigned long int) > + ::const_iterator + id_finder = configuration.algorithm_names.find(*id); + if (id_finder == configuration.algorithm_names.end()) + throw CommandErrorException + ("Unknown implementation identifier (`" + *id + "')."); + algorithms.merge(id_finder->second); } - - printText("", 0, 2); - - try - { - product_automaton = new ProductAutomaton(); - product_automaton->computeProduct - (buchi_automaton, *(round_info.statespace), - configuration.global_options.product_mode == Configuration::GLOBAL); - } - catch (...) - { - if (product_automaton != 0) - { - delete product_automaton; - product_automaton = 0; - } - - printText(" error\n", 0); - - try - { - throw; - } - catch (const ::Graph::ProductAutomaton::ProductSizeException&) - { - throw CommandErrorException("Product may be too large"); - } - catch (const UserBreakException&) - { - throw CommandErrorException("User break"); - } - catch (const bad_alloc&) - { - throw CommandErrorException("Out of memory"); - } - } - - printText(" ok\n", 0); - - last_automaton = new_automaton; + } + catch (const IntervalRangeException& e) + { + throw CommandErrorException + (string("Implementation identifier out of range (") + + toString(e.getNumber()) + + ")."); } } @@ -158,64 +175,13 @@ void printAlgorithmList(ostream& stream, int indent) + "abled)\n"; } - estream << '\n'; estream.flush(); } -/* ========================================================================= */ -void synchronizePrefixAndCycle - (deque::size_type, - ALLOC(Graph::Graph::size_type) >& prefix, - deque::size_type, - ALLOC(Graph::Graph::size_type) >& cycle) -/* ---------------------------------------------------------------------------- - * - * Description: Function for `synchronizing' a sequence of states consisting - * of a prefix and a repeating cycle. This means removing from - * the end of the prefix the longest sequence of states that - * forms a postfix of the cycle. The states in the cycle will be - * `rotated' accordingly. - * - * Arguments: prefix -- A reference to a deque of state identifiers - * forming the prefix of the state sequence. - * cycle -- A reference to a deque of state identifiers - * representing the states in the repeating cycle. - * - * Returns: Nothing. - * - * ------------------------------------------------------------------------- */ -{ - if (cycle.empty()) - throw CommandErrorException("internal error"); - - while (!prefix.empty() && prefix.back() == cycle.back()) - { - prefix.pop_back(); - cycle.push_front(cycle.back()); - cycle.pop_back(); - } - - Graph::Graph::size_type state_id = cycle.front(); - deque::size_type, - ALLOC(Graph::Graph::size_type) >::const_iterator - s; - - for (s = cycle.begin() + 1; s != cycle.end() && *s == state_id; ++s) - ; - - if (s == cycle.end()) - { - cycle.clear(); - cycle.push_front(state_id); - } -} - /* ========================================================================= */ void printCrossComparisonAnalysisResults (ostream& stream, int indent, bool formula_type, - const vector& input_tokens, - ProductAutomaton*& product_automaton, - pair& last_product_automaton) + const vector& input_tokens) /* ---------------------------------------------------------------------------- * * Description: Implements the user command `resultanalysis', i.e., analyzes @@ -232,14 +198,6 @@ void printCrossComparisonAnalysisResults * input_tokens -- A reference to a vector * containing the arguments of the * command. - * product_automaton -- A reference to a pointer telling - * the storage location of the - * generated product automaton. - * last_product_automaton -- A pair telling the algorithm and - * the formula last used for - * computing a product automaton - * (for testing whether the result - * is already available). * * Returns: Nothing. * @@ -255,33 +213,36 @@ void printCrossComparisonAnalysisResults "model checking result cross-comparison test " "is enabled."); - if (input_tokens[1] == "p") + algorithm1 = parseAlgorithmId(input_tokens[1]); + algorithm2 = parseAlgorithmId(input_tokens[2]); + + if (algorithm1 == algorithm2) + throw CommandErrorException("Implementation identifiers must be " + "different."); + + /* + * Arrange the algorithm identifiers such that `algorithm1' never refers to + * the internal model checking algorithm (swap `algorithm1' and `algorithm2' + * if necessary). + */ + + if (configuration.isInternalAlgorithm(algorithm1)) { path_compare = true; - algorithm1 = parseNumber(input_tokens[2]); - algorithm2 = round_info.number_of_translators; - } - else - { - algorithm1 = parseNumber(input_tokens[1]); - if (input_tokens[2] == "p") - { - path_compare = true; - algorithm2 = round_info.number_of_translators; - } - else - algorithm2 = parseNumber(input_tokens[2]); + algorithm1 ^= algorithm2; + algorithm2 ^= algorithm1; + algorithm1 ^= algorithm2; } + if (configuration.isInternalAlgorithm(algorithm2)) + path_compare = true; + if (path_compare && !(configuration.global_options.statespace_generation_mode & Configuration::PATH)) throw CommandErrorException("This feature is available only when using " "paths as state spaces."); - verifyNumber(algorithm1, round_info.number_of_translators, - "No such implementation"); - int formula = (formula_type ? 0 : 1); int generator_formula = formula; @@ -292,17 +253,6 @@ void printCrossComparisonAnalysisResults const AutomatonStats* stats2; stats1 = &test_results[algorithm1].automaton_stats[formula]; - - if (!path_compare) - { - verifyNumber(algorithm2, round_info.number_of_translators, - "No such implementation"); - - if (algorithm1 == algorithm2) - throw CommandErrorException("Implementation identifiers must be " - "different."); - } - stats2 = &test_results[algorithm2].automaton_stats[formula]; if (!stats1->crossComparisonPerformed(algorithm2)) @@ -312,7 +262,7 @@ void printCrossComparisonAnalysisResults "performed between " + configuration.algorithmString(algorithm1) + " and " + configuration.algorithmString(algorithm2) - + ".\n", + + ".", 78); return; } @@ -324,7 +274,7 @@ void printCrossComparisonAnalysisResults "results given by " + configuration.algorithmString(algorithm1) + " and " - + configuration.algorithmString(algorithm2) + ".\n", + + configuration.algorithmString(algorithm2) + ".", 78); return; } @@ -374,7 +324,8 @@ void printCrossComparisonAnalysisResults state = parseNumber(input_tokens[3]); - verifyNumber(state, round_info.statespace->size(), "No such state"); + verifyNumber(state, round_info.statespace->size(), + "State identifier out of range"); if (state >= round_info.real_emptiness_check_size) { @@ -386,7 +337,7 @@ void printCrossComparisonAnalysisResults + configuration.algorithmString(algorithm2) + " in state " + toString(state) - + " of the state space.\n", + + " of the state space.", 78); return; } @@ -401,7 +352,7 @@ void printCrossComparisonAnalysisResults + configuration.algorithmString(algorithm2) + " in state " + toString(state) - + " of the state space.\n", + + " of the state space.", 78); return; } @@ -420,40 +371,37 @@ void printCrossComparisonAnalysisResults ? algorithm2 : algorithm1); - deque system_prefix; - deque system_cycle; - deque - automaton_prefix; - deque - automaton_cycle; + ::Graph::Product::Witness witness; if (!path_compare || accepting_algorithm == algorithm1) { - /* - * Compute the synchronous product of the automaton and the state space. - */ - - computeProductAutomaton(product_automaton, - *(test_results[accepting_algorithm]. - automaton_stats[formula].buchi_automaton), - last_product_automaton, - make_pair(accepting_algorithm, formula_type)); - /* * Search the product automaton for an accepting cycle. */ - pair, - deque > - execution; + const BuchiAutomaton& automaton + = *test_results[accepting_algorithm].automaton_stats[formula] + .buchi_automaton; + + ::Graph::Product product + (automaton, *round_info.statespace); try { printText("", 0, 2); - product_automaton->findAcceptingExecution(state, execution); + + product.findWitness(automaton.initialState(), state, witness); + + if (witness.cycle.first.empty()) + throw Exception + ("UserCommands::printCrossComparisonAnalysisResults(...): internal " + "error [witness construction failed]"); + } + catch (const UserBreakException&) + { + printText(" user break\n", 0); + throw; } catch (...) { @@ -462,71 +410,27 @@ void printCrossComparisonAnalysisResults } printText(" ok\n\n", 0); - - /* - * Separate the parallel executions of the system and the automaton into - * prefixes and cycles. - */ - - while (!execution.first.empty()) - { - automaton_prefix.push_back(execution.first.front().first); - system_prefix.push_back(execution.first.front().second); - execution.first.pop_front(); - } - - while (!execution.second.empty()) - { - automaton_cycle.push_back(execution.second.front().first); - system_cycle.push_back(execution.second.front().second); - execution.second.pop_front(); - } - - synchronizePrefixAndCycle(automaton_prefix, automaton_cycle); - synchronizePrefixAndCycle(system_prefix, system_cycle); } else { - StateSpace::size_type loop_state + const StateSpace::size_type loop_state = (*((*round_info.statespace)[round_info.statespace->size() - 1]. edges().begin()))->targetNode(); + const StateSpace::size_type loop_length + = round_info.statespace->size() - loop_state; - if (state < loop_state) + for ( ; state < loop_state; ++state) + witness.prefix.second.push_back(StateSpace::PathElement(state, 0)); + + state -= loop_state; + for (StateSpace::size_type s = 0; s < loop_length; ++s) { - for (StateSpace::size_type path_state = state; path_state < loop_state; - path_state++) - system_prefix.push_back(path_state); - - for (StateSpace::size_type path_state = loop_state; - path_state < round_info.statespace->size(); - path_state++) - system_cycle.push_back(path_state); - } - else - { - for (StateSpace::size_type path_state = state; - path_state < round_info.statespace->size(); - path_state++) - system_cycle.push_back(path_state); - - for (StateSpace::size_type path_state = loop_state; - path_state < state; - path_state++) - system_cycle.push_back(path_state); + witness.cycle.second.push_back + (StateSpace::PathElement(state + loop_state, 0)); + state = (state + 1) % loop_length; } } - state = system_prefix.size(); - - /* - * Construct a path in the state space from the system prefix and the cycle. - */ - - vector path; - - path.insert(path.end(), system_prefix.begin(), system_prefix.end()); - path.insert(path.end(), system_cycle.begin(), system_cycle.end()); - /* * Write information about the execution to the output stream. That is, * display the states in the infinite execution (a prefix and a cycle) @@ -538,7 +442,7 @@ void printCrossComparisonAnalysisResults estream << string(indent + 2, ' ') + "Execution M:\n"; - printPath(stream, indent + 4, system_prefix, system_cycle, + printPath(stream, indent + 4, witness.prefix.second, witness.cycle.second, *(round_info.statespace)); estream << string(indent + 4, ' ') @@ -557,8 +461,8 @@ void printCrossComparisonAnalysisResults Ltl::PathEvaluator path_evaluator; bool result = path_evaluator.evaluate - (*round_info.formulae[generator_formula], *(round_info.statespace), path, - state); + (*round_info.formulae[generator_formula], witness.prefix.second, + witness.cycle.second, *round_info.statespace); path_evaluator.print(stream, indent + 4); @@ -573,14 +477,19 @@ void printCrossComparisonAnalysisResults : configuration.algorithmString(result ? rejecting_algorithm : accepting_algorithm)) - + ".\n", + + ".", 78); if (!result) + { + estream << '\n'; printAcceptingCycle(stream, indent + 2, accepting_algorithm, - automaton_prefix, automaton_cycle, + witness.prefix.first, witness.cycle.first, *(test_results[accepting_algorithm]. - automaton_stats[formula].buchi_automaton)); + automaton_stats[formula].buchi_automaton), + witness.prefix.second, witness.cycle.second, + *round_info.statespace); + } } /* ========================================================================= */ @@ -610,10 +519,7 @@ void printConsistencyAnalysisResults "model checking result consistency check is " "enabled."); - unsigned long int algorithm_id = parseNumber(input_tokens[1]); - - verifyNumber(algorithm_id, round_info.number_of_translators, - "No such implementation"); + unsigned long int algorithm_id = parseAlgorithmId(input_tokens[1]); if (test_results[algorithm_id].consistency_check_result == -1) { @@ -621,7 +527,7 @@ void printConsistencyAnalysisResults "Model checking result consistency check was not performed " "on implementation " + configuration.algorithmString(algorithm_id) - + ".\n", + + ".", 78); return; } @@ -631,7 +537,7 @@ void printConsistencyAnalysisResults printTextBlock(stream, indent, "Implementation " + configuration.algorithmString(algorithm_id) - + " passed the model checking result consistency check.\n", + + " passed the model checking result consistency check.", 78); return; } @@ -671,7 +577,8 @@ void printConsistencyAnalysisResults */ state = parseNumber(input_tokens[2]); - verifyNumber(state, round_info.statespace->size(), "No such state"); + verifyNumber(state, round_info.statespace->size(), + "State identifier out of range"); if (state >= round_info.real_emptiness_check_size) { @@ -680,7 +587,7 @@ void printConsistencyAnalysisResults "performed on implementation " + configuration.algorithmString(algorithm_id) + " in state " + toString(state) + " of the state " - "space.\n", + "space.", 78); return; } @@ -694,14 +601,14 @@ void printConsistencyAnalysisResults "consistency check on implementation " + configuration.algorithmString(algorithm_id) + " in state " + toString(state) + " of the state " - "space.\n", + "space.", 78); return; } } vector path; - deque prefix, cycle; + StateSpace::Path prefix, cycle; map, ALLOC(StateSpace::size_type) > ordering; @@ -728,10 +635,10 @@ void printConsistencyAnalysisResults loop_state = ordering[state]; for (StateSpace::size_type s = 0; s < loop_state; s++) - prefix.push_back(path[s]); + prefix.push_back(StateSpace::PathElement(path[s], 0)); for (StateSpace::size_type s = loop_state; s < path.size(); s++) - cycle.push_back(path[s]); + cycle.push_back(StateSpace::PathElement(path[s], 0)); estream << string(indent + 2, ' ') + "Execution M:\n"; printPath(stream, indent + 4, prefix, cycle, *(round_info.statespace)); @@ -741,8 +648,7 @@ void printConsistencyAnalysisResults Ltl::PathEvaluator path_evaluator; bool result = path_evaluator.evaluate - (*round_info.formulae[formula], *(round_info.statespace), path, - loop_state); + (*round_info.formulae[formula], prefix, cycle, *round_info.statespace); path_evaluator.print(stream, indent + 4); @@ -751,14 +657,14 @@ void printConsistencyAnalysisResults + "satisfied in the execution. It seems that the automaton " "constructed for the " + (result ? "posi" : "nega") - + "tive formula rejects the execution incorrectly.\n", + + "tive formula rejects the execution incorrectly.", 78); } /* ========================================================================= */ void printAutomatonAnalysisResults - (ostream& stream, int indent, unsigned long int algorithm1, - unsigned long int algorithm2) + (ostream& stream, int indent, + const vector& input_tokens) /* ---------------------------------------------------------------------------- * * Description: Implements the user command `buchianalysis', i.e., analyzes @@ -766,11 +672,11 @@ void printAutomatonAnalysisResults * for two Büchi automata constructed for the formula and its * negation. * - * Arguments: stream -- A reference to an output stream. - * indent -- Number of spaces to leave on the left of the - * output. - * algorithm1, -- Identifiers of the algorithms for which the - * algorithm2 should be performed. + * Arguments: stream -- A reference to an output stream. + * indent -- Number of spaces to leave on the left of + * the output. + * input_tokens -- A reference to a vector containing the + * arguments of the command. * * Returns: Nothing. * @@ -783,10 +689,14 @@ void printAutomatonAnalysisResults "Büchi automata intersection emptiness check " "is enabled."); - verifyNumber(algorithm1, round_info.number_of_translators, - "No such implementation"); - verifyNumber(algorithm2, round_info.number_of_translators, - "No such implementation"); + unsigned long int algorithm1 = parseAlgorithmId(input_tokens[1]); + unsigned long int algorithm2 = parseAlgorithmId(input_tokens[2]); + + if (configuration.isInternalAlgorithm(algorithm1) + || configuration.isInternalAlgorithm(algorithm2)) + throw CommandErrorException + ("This feature is not available for lbtt's internal " + "model checking algorithm."); int test_result = test_results[algorithm1].automaton_stats[0]. @@ -806,7 +716,7 @@ void printAutomatonAnalysisResults : " (positive formula) and " + configuration.algorithmString(algorithm2) + " (negative formula)") - + ".\n", + + ".", 78); return; @@ -838,318 +748,170 @@ void printAutomatonAnalysisResults estream << "\n\n"; estream.flush(); - /* - * Compute the intersection of the two automata. - */ - - BuchiAutomaton* a = 0; + ::Graph::Product::Witness witness; try { - map, ALLOC(BuchiAutomaton::StateIdPair) > - intersection_state_mapping; + printText("", 0, 2); - try - { - printText("", 0, 2); + const BuchiAutomaton& automaton_1 + = *(test_results[algorithm1].automaton_stats[0].buchi_automaton); + const BuchiAutomaton& automaton_2 + = *(test_results[algorithm2].automaton_stats[1].buchi_automaton); - a = BuchiAutomaton::intersect - (*(test_results[algorithm1].automaton_stats[0].buchi_automaton), - *(test_results[algorithm2].automaton_stats[1].buchi_automaton), - &intersection_state_mapping); - } - catch (...) - { - printText(" error\n", 0); - throw; - } + ::Graph::Product product(automaton_1, automaton_2); + product.findWitness(automaton_1.initialState(), automaton_2.initialState(), + witness); - printText(" ok\n", 0); - - /* - * Search the intersection automaton for an accepting execution. This is - * done `indirectly' as follows: - * - * 1. Convert the intersection automaton into a StateSpace, i.e., - * construct a StateSpace which shares a similar transition - * relation with the automaton. - * 2. Construct a `dummy' single-state Büchi automaton which - * accepts all inputs. - * 3. Compute the synchronous product of the state space and the - * automaton. The obtained product automaton again has the same - * transition relation as the original intersection automaton - * (state identifiers may have been permuted; however, the - * product automaton contains information about the - * correspondence between the state identifiers of the product - * automaton and the original intersection automaton). - * 4. Switch the roles of the state space and the Büchi automaton in - * the product to effectively transfer state acceptance - * information to the product automaton from the automaton - * intersection. - * 5. Search an accepting cycle in the product automaton. This then - * corresponds to an accepting execution of the intersection - * automaton. - */ - - StateSpace automaton_as_statespace - (configuration.formula_options.formula_generator. - number_of_available_variables, - a->size()); - - /* - * 1. - */ - - for (BuchiAutomaton::size_type state = 0; state < a->size(); state++) - { - for (GraphEdgeContainer::const_iterator transition - = (*a)[state].edges().begin(); - transition != (*a)[state].edges().end(); - ++transition) - automaton_as_statespace.connect(state, (*transition)->targetNode()); - } - - /* - * 2. - */ - - BuchiAutomaton dummy_automaton(1, 0, 0); - dummy_automaton.connect(0, 0, &Ltl::True::construct()); - - /* - * 3. - */ - - ProductAutomaton p; - p.computeProduct(dummy_automaton, automaton_as_statespace, false); - - /* - * 4. - */ - - p.buchi_automaton = a; - p.statespace_size = 1; - - /* - * 5. - */ - - pair, - deque > - execution; - - try - { - printText("", - 0, 2); - - p.findAcceptingExecution(0, execution); - } - catch (...) - { - printText(" error\n", 0); - throw; - } - - printText(" ok\n\n", 0); - - /* - * Extract the state identifiers belonging to the execution of the - * intersection automaton from the result. - */ - - vector path; - - for (deque::const_iterator - state = execution.first.begin(); - state != execution.first.end(); - ++state) - path.push_back(state->first); - - const vector - ::size_type loop_pos = path.size(); - - for (deque::const_iterator - state = execution.second.begin(); - state != execution.second.end(); - ++state) - path.push_back(state->first); - - /* - * Construct an execution accepted by both of the automata. This is done - * by giving suitable truth assignments for the atomic propositions in - * 'path.size()' states, where `path' corresponds to an accepting run of - * the intersection automaton. (The state space representing the - * intersection automaton is reused for this purpose, since it is not - * needed any longer.) In addition, `prefix' and `cycle' (required for - * displaying the execution) are built to refer to the reused states. - */ - - deque prefix, cycle; - - /* - * Ensure that the state space is large enough to contain the execution - * (the execution may pass several times through a state in the - * intersection automaton). - */ - - if (automaton_as_statespace.size() < path.size()) - automaton_as_statespace.expand - (path.size() - automaton_as_statespace.size()); - - path.push_back(path[loop_pos]); /* use the first state of the cycle as a - * temporary sentinel element - */ - - for (vector - ::size_type state = 0; - state + 1 < path.size(); - ++state) - { - GraphEdgeContainer::const_iterator transition; - - for (transition = (*a)[path[state]].edges().begin(); - (*transition)->targetNode() != path[state + 1]; - ++transition) - ; - - automaton_as_statespace[state].positiveAtoms() - = static_cast(*transition) - ->guard().findPropositionalModel - (configuration.formula_options.formula_generator. - number_of_available_variables); - - (state < loop_pos ? prefix : cycle).push_back(state); - } - - path.pop_back(); /* remove the sentinel element */ - - delete a; - a = 0; - - /* - * Display the input sequence accepted by both automata. - */ - - estream << string(indent + 2, ' ') - + "Execution M accepted by both automata:\n"; - - printPath(stream, indent + 4, prefix, cycle, automaton_as_statespace); - - estream << '\n'; - - /* - * For each of the original automata, display the accepting runs that - * these automata have on the input sequence. - */ - - deque - aut_prefix; - - deque - aut_cycle; - - for (int i = 0; i < 2; i++) - { - aut_prefix.clear(); - aut_cycle.clear(); - - deque* - new_execution_states = &aut_prefix; - - for (vector - ::size_type state_id = 0; - state_id < path.size(); - ++state_id) - { - if (state_id == loop_pos) - new_execution_states = &aut_cycle; - new_execution_states->push_back - (i == 0 - ? intersection_state_mapping[path[state_id]].first - : intersection_state_mapping[path[state_id]].second); - } - - synchronizePrefixAndCycle(aut_prefix, aut_cycle); - - printAcceptingCycle(stream, indent + 2, - (i == 0 ? algorithm1 : algorithm2), - aut_prefix, aut_cycle, - *(test_results[(i == 0 ? algorithm1 : algorithm2)]. - automaton_stats[i].buchi_automaton)); - } - - /* - * Normalize the state identifiers in `path' to refer to the states that - * give the valuations for atomic propositions along the execution. - */ - - for (vector - ::size_type state = 0; - state < path.size(); - ++state) - path[state] = state; - - /* - * Display a proof or a refutation for the formula in the execution. - */ - - estream << string(indent + 2, ' ') - + "Analysis of the positive formula in the execution M:\n"; - - Ltl::PathEvaluator path_evaluator; - bool result = path_evaluator.evaluate - (*round_info.formulae[formula], automaton_as_statespace, path, loop_pos); - - path_evaluator.print(stream, indent + 4); - - printTextBlock(stream, indent + 2, - " \n The positive formula is " - + string(result ? "" : "not ") - + "satisfied in the execution. This suggests that the " - "Büchi automaton constructed for the " - + (result ? "nega" : "posi") + "tive formula " - + (algorithm1 == algorithm2 - ? "" - : "(the automaton constructed by implementation " - + configuration.algorithmString(result ? algorithm2 - : algorithm1) - + ") ") - + "is incorrect.\n", - 78); + if (witness.cycle.first.empty()) + throw Exception + ("UserCommands::printAutomatonAnalysisResults(...): internal error " + "[witness construction failed]"); + } + catch (const UserBreakException&) + { + printText(" user break\n", 0); + throw; } catch (...) { - if (a != 0) - delete a; + printText(" error\n", 0); + throw; } + + printText(" ok\n", 0); + + const unsigned long int valuation_size + = configuration.formula_options.formula_generator + .number_of_available_variables; + const StateSpace::size_type path_length + = witness.prefix.first.size() + witness.cycle.first.size(); + StateSpace path(valuation_size, path_length); + + StateSpace::size_type state = 0; + StateSpace::Path::const_iterator p1, p2; + StateSpace::Path path_prefix, path_cycle; + + for (int i = 0; i < 2; ++i) + { + const pair* witness_segment; + StateSpace::Path* path_segment; + + if (i == 0) + { + witness_segment = &witness.prefix; + path_segment = &path_prefix; + } + else + { + witness_segment = &witness.cycle; + path_segment = &path_cycle; + } + + for (p1 = witness_segment->first.begin(), + p2 = witness_segment->second.begin(); + p1 != witness_segment->first.end(); + ++p1, ++p2, ++state) + { + ::Ltl::LtlFormula* f + = &::Ltl::And::construct + (static_cast(p1->edge()) + .guard(), + static_cast(p2->edge()) + .guard()); + + path[state].positiveAtoms().copy + (f->findPropositionalModel(valuation_size - 1), valuation_size); + + path_segment->push_back(StateSpace::PathElement(state, 0)); + + ::Ltl::LtlFormula::destruct(f); + } + } + + /* + * Display the input sequence accepted by both automata. + */ + + estream << string(indent + 2, ' ') + + "Execution M accepted by both automata:\n"; + + printPath(stream, indent + 4, path_prefix, path_cycle, path); + + estream << '\n'; + + /* + * For each of the original automata, display the accepting runs that + * these automata have on the input sequence. + */ + + for (int i = 0; i < 2; ++i) + { + const BuchiAutomaton::Path* prefix; + const BuchiAutomaton::Path* cycle; + + if (i == 0) + { + prefix = &witness.prefix.first; + cycle = &witness.cycle.first; + } + else + { + prefix = &witness.prefix.second; + cycle = &witness.cycle.second; + } + + printAcceptingCycle(stream, indent + 2, (i == 0 ? algorithm1 : algorithm2), + *prefix, *cycle, + *(test_results[i == 0 ? algorithm1 : algorithm2] + .automaton_stats[i].buchi_automaton), + path_prefix, path_cycle, path); + + estream << '\n'; + } + + /* + * Display a proof or a refutation for the formula in the execution. + */ + + estream << string(indent + 2, ' ') + + "Analysis of the positive formula in the execution M:\n"; + + Ltl::PathEvaluator path_evaluator; + bool result = path_evaluator.evaluate(*round_info.formulae[formula], + path_prefix, path_cycle, path); + + path_evaluator.print(stream, indent + 4); + + printTextBlock(stream, indent + 2, + " \n The positive formula is " + string(result ? "" : "not ") + + "satisfied in the execution. This suggests that the " + "Büchi automaton constructed for the " + + (result ? "nega" : "posi") + "tive formula " + + (algorithm1 == algorithm2 + ? "" + : "(the automaton constructed by implementation " + + configuration.algorithmString(result ? algorithm2 + : algorithm1) + + ") ") + + "is incorrect.", + 78); } /* ========================================================================= */ void printPath - (ostream& stream, int indent, - const deque& prefix, - const deque& cycle, - const StateSpace& path) + (ostream& stream, int indent, const StateSpace::Path& prefix, + const StateSpace::Path& cycle, const StateSpace& path) /* ---------------------------------------------------------------------------- * - * Description: Writes information about a single execution path to a stream. + * Description: Writes information about a path in a state space to a stream. * * Arguments: stream -- A reference to an output stream. * indent -- Number of spaces to leave to the left of output. - * prefix -- A reference to a constant deque of state - * identifiers forming the prefix of the execution. - * cycle -- A reference to a constant deque of state - * identifiers forming the infinitely repeating - * cycle in the execution. - * path -- A reference to the constant state space from - * which the state identifiers in the deques are - * taken. + * prefix -- The prefix of the path. + * cycle -- The cycle of the path. + * path -- The state space to which the state identifiers in + * the prefix and the cycle refer. * * Returns: Nothing. * @@ -1157,65 +919,59 @@ void printPath { Exceptional_ostream estream(&stream, ios::badbit | ios::failbit); - const deque* - execution_states; + const StateSpace::Path* path_segment; - for (int counter = 0; counter < 2; counter++) + for (int i = 0; i < 2; ++i) { + if (i == 0 && prefix.empty()) + continue; + estream << string(indent, ' '); - - if (counter == 0) + if (i == 0) { - execution_states = &prefix; - estream << "prefix:"; + path_segment = &prefix; + estream << "prefix"; } else { - execution_states = &cycle; - estream << "cycle: "; + path_segment = &cycle; + estream << "cycle"; } + estream << ":\n"; - estream << string(6, ' ') + "< "; + bool first_printed; - if (!execution_states->empty()) + for (StateSpace::Path::const_iterator path_element = path_segment->begin(); + path_element != path_segment->end(); + ++path_element) { - bool first_printed; + if (path_element != path_segment->begin()) + estream << toString(path_element->node()) + "\n"; + estream << string(indent + 2, ' ') + 's' + toString(path_element->node()) + + " {"; - for (deque - ::const_iterator execution_state = execution_states->begin(); - execution_state != execution_states->end(); - ++execution_state) + first_printed = false; + for (unsigned long int proposition = 0; + proposition < path.numberOfPropositions(); + ++proposition) { - if (execution_state != execution_states->begin()) - estream << ",\n" + string(indent + 15, ' '); - - estream << 's' + toString(*execution_state) + " {"; - - first_printed = false; - - for (unsigned long int proposition = 0; - proposition < path.numberOfPropositions(); - proposition++) + if (path[path_element->node()].positiveAtoms().test(proposition)) { - if (path[*execution_state].positiveAtoms().test(proposition)) - { - if (first_printed) - estream << ", "; - else - first_printed = true; + if (first_printed) + estream << ','; + else + first_printed = true; - estream << 'p' + toString(proposition); - } + estream << 'p' + toString(proposition); } - - estream << '}'; } + + estream << "} --> s"; } - else - estream << "empty"; - - estream << " >\n"; + estream << toString(cycle.begin()->node()) + "\n"; } + + estream.flush(); } /* ========================================================================= */ @@ -1224,29 +980,34 @@ void printAcceptingCycle vector::size_type algorithm_id, - const deque& - prefix, - const deque& - cycle, - const BuchiAutomaton& automaton) + const BuchiAutomaton::Path& aut_prefix, + const BuchiAutomaton::Path& aut_cycle, + const BuchiAutomaton& automaton, const StateSpace::Path& path_prefix, + const StateSpace::Path& path_cycle, const StateSpace& path) /* ---------------------------------------------------------------------------- * - * Description: Writes information about a single automaton execution to a - * stream. + * Description: Writes information about an execution of a Büchi automaton to + * a stream. * * Arguments: stream -- A reference to an output stream. * indent -- Number of spaces to leave to the left of * output. * algorithm_id -- Identifier for an algorithm. - * prefix -- A reference to a constant deque of state - * identifiers forming the prefix of the - * execution. - * cycle -- A reference to a constant deque of state - * identifiers forming the infinitely - * repeating cycle in the execution. - * automaton -- A reference to a constant BuchiAutomaton - * from which the state identifiers in the - * deques are taken. + * aut_prefix -- The prefix of a path in a Büchi automaton. + * aut_cycle -- The cycle of a path in a Büchi automaton. + * automaton -- The Büchi automaton to which the state + * identifiers in `aut_prefix' and `aut_cycle' + * refer. + * path_prefix -- The prefix of a path in a state space. + * (This path is interpreted as the input for + * the Büchi automaton.) It is assumed that + * `path_prefix.size() == aut_prefix.size()'. + * path_cycle -- The cycle of a path in a state space. It + * is assumed that `path_cycle.size() == + * aut_cycle.size()'. + * path -- The state space to which the state + * identifiers in `path_prefix' and + * `path_cycle' refer. * * Returns: Nothing. * @@ -1254,10 +1015,6 @@ void printAcceptingCycle { Exceptional_ostream estream(&stream, ios::badbit | ios::failbit); - const deque* - execution_states; - bool first_printed; - printTextBlock(stream, indent, "On input M, the automaton constructed by implementation " + configuration.algorithmString(algorithm_id) + " (with " @@ -1267,76 +1024,97 @@ void printAcceptingCycle + ") has the following accepting execution:", 78); - for (int counter = 0; counter < 2; counter++) - { - estream << string(indent + 2, ' '); + const BuchiAutomaton::Path* aut_segment; + const StateSpace::Path* path_segment; + BuchiAutomaton::Path::const_iterator aut_state; + StateSpace::Path::const_iterator path_state; - if (counter == 0) + for (int i = 0; i < 2; ++i) + { + if (i == 0 && aut_prefix.empty()) + continue; + + estream << string(indent + 2, ' '); + if (i == 0) { estream << "prefix"; - execution_states = &prefix; + aut_segment = &aut_prefix; + path_segment = &path_prefix; } else { estream << "cycle"; - execution_states = &cycle; + aut_segment = &aut_cycle; + path_segment = &path_cycle; } - estream << ":\n"; - string execution_string = "<"; - - if (!execution_states->empty()) + bool first_printed = false; + for (aut_state = aut_segment->begin(), path_state = path_segment->begin(); + aut_state != aut_segment->end(); + ++aut_state, ++path_state) { - first_printed = false; - - for (deque::const_iterator - execution_state = execution_states->begin(); - execution_state != execution_states->end(); - ++execution_state) + estream << string(indent + 4, ' ') + toString(aut_state->node()) + ' '; + + const BitArray* bits; + for (int j = 0; j < 2; ++j) { - if (first_printed) - execution_string += ", "; - else - first_printed = true; - execution_string += toString(*execution_state); + bits = (j == 0 + ? &automaton[aut_state->node()].acceptanceSets() + : &static_cast + (aut_state->edge()).acceptanceSets()); + first_printed = false; - if (counter == 1) - { - bool first_acceptance_set_printed = false; - - for (unsigned long int accept_set = 0; - accept_set < automaton.numberOfAcceptanceSets(); - accept_set++) + for (unsigned long int accept_set = 0; + accept_set < automaton.numberOfAcceptanceSets(); + ++accept_set) + { + if (bits->test(accept_set)) { - if (automaton[*execution_state].acceptanceSets().test(accept_set)) + if (first_printed) + estream << ','; + else { - if (first_acceptance_set_printed) - execution_string += ", "; - else - { - first_acceptance_set_printed = true; - execution_string += " [acceptance sets: {"; - } - execution_string += toString(accept_set); + first_printed = true; + if (j == 1) + estream << "--"; + estream << '{'; } + estream << accept_set; } + } - if (first_acceptance_set_printed) - execution_string += "}]"; + if (first_printed) + { + estream << "}"; + if (j == 0) + estream << ' '; } } + + estream << "--> " + toString(aut_state->edge().targetNode()) + " [ {"; + bits = &path[path_state->node()].positiveAtoms(); + first_printed = false; + for (unsigned long int proposition = 0; + proposition < path.numberOfPropositions(); + ++proposition) + { + if (bits->test(proposition)) + { + if (first_printed) + estream << ','; + else + first_printed = true; + estream << 'p' + toString(proposition); + } + } + estream << "} |== "; + static_cast(aut_state->edge()) + .guard().print(estream); + estream << " ]\n"; } - else - execution_string += "empty"; - - execution_string += ">"; - - printTextBlock(stream, indent + 4, execution_string, 78); } - estream << '\n'; estream.flush(); } @@ -1367,16 +1145,17 @@ void printBuchiAutomaton { Exceptional_ostream estream(&stream, ios::badbit | ios::failbit); - unsigned long int algorithm = parseNumber(input_tokens[1]); - - verifyNumber(algorithm, round_info.number_of_translators, - "No such implementation"); - + unsigned long int algorithm = parseAlgorithmId(input_tokens[1]); int formula = (configuration.formula_options.output_mode == Configuration::NNF ? 0 : 2) + (formula_type ? 0 : 1); + if (configuration.isInternalAlgorithm(algorithm)) + throw CommandErrorException + ("This feature is not available for lbtt's internal " + "model checking algorithm."); + if (!test_results[algorithm].automaton_stats[formula_type ? 0 : 1]. buchiAutomatonComputed()) { @@ -1385,7 +1164,7 @@ void printBuchiAutomaton + configuration.algorithmString(algorithm) + " for the formula `" + toString(*round_info.formulae[formula]) - + "'.\n", + + "'.", 78); return; } @@ -1415,22 +1194,27 @@ void printBuchiAutomaton estream << string(indent + 2, ' ') + "The automaton is empty.\n"; else { - set, - ALLOC(unsigned long int) > states; + IntervalList states; if (input_tokens.size() == 2) input_tokens.push_back("*"); - parseInterval(input_tokens[2], states, 0, automaton->size() - 1); - - for (set, - ALLOC(unsigned long int) >::const_iterator - state = states.begin(); - state != states.end(); ++state) + try { - verifyNumber(*state, automaton->size(), - "State identifier out of range"); + parseIntervalList(input_tokens[2], states, 0, automaton->size() - 1); + } + catch (const IntervalRangeException& e) + { + throw CommandErrorException + (string("State identifier out of range (") + + toString(e.getNumber()) + + ")."); + } + for (IntervalList::const_iterator state = states.begin(); + state != states.end(); + ++state) + { estream << string(indent + 2, ' ') + "State " + toString(*state) + (*state == automaton->initialState() ? " (initial state)" : "") + ":\n"; @@ -1442,7 +1226,6 @@ void printBuchiAutomaton else if (fmt == Graph::DOT) automaton->print(stream, indent, Graph::DOT); - estream << '\n'; estream.flush(); } @@ -1472,13 +1255,10 @@ void evaluateFormula { Exceptional_ostream estream(&stream, ios::failbit | ios::badbit); - set, ALLOC(unsigned long int) > - algorithms; - set, ALLOC(unsigned long int) > - system_states; + IntervalList algorithms; + IntervalList system_states; string algorithm_name; int formula = (formula_type ? 0 : 1); - bool show_path_eval_results = false; if (!configuration.global_options.do_comp_test && !configuration.global_options.do_cons_test) @@ -1488,55 +1268,16 @@ void evaluateFormula if (round_info.statespace == 0) throw CommandErrorException("No state space was generated in this test " "round."); - string algorithm_list; + + /* + * If no list of algorithms was given as an argument, show the results of + * all algorithms. + */ if (input_tokens.size() < 2) - { - /* - * If no list of algorithms was given as an argument, show the results of - * all algorithms. - */ - - algorithm_list = "*"; input_tokens.push_back("*"); - show_path_eval_results - = ((configuration.global_options.statespace_generation_mode - & Configuration::PATH) != 0); - } - else - { - /* - * Otherwise parse the list of algorithms. Test also whether the internal - * model checking algorithm was included in the list. - */ - vector algorithm_list_elements; - sliceString(input_tokens[1], ",", algorithm_list_elements); - - for (vector::const_iterator alg - = algorithm_list_elements.begin(); - alg != algorithm_list_elements.end(); - ++alg) - { - if (algorithm_list.length() > 0) - algorithm_list += ','; - - if (*alg == "p") - { - if ((configuration.global_options.statespace_generation_mode - & Configuration::PATH) == 0) - throw CommandErrorException("This feature is available only when " - "using paths as state spaces."); - else - show_path_eval_results = true; - } - else - algorithm_list += *alg; - } - } - - parseInterval(algorithm_list, algorithms, 0, - round_info.number_of_translators - 1); + parseAlgorithmIdList(input_tokens[1], algorithms); /* * If no list of states was given, show information about all states. @@ -1547,8 +1288,18 @@ void evaluateFormula if (input_tokens.size() < 3) input_tokens.push_back("*"); - parseInterval(input_tokens[2], system_states, 0, - round_info.real_emptiness_check_size - 1); + try + { + parseIntervalList(input_tokens[2], system_states, 0, + round_info.real_emptiness_check_size - 1); + } + catch (const IntervalRangeException& e) + { + throw CommandErrorException + (string("State identifier out of range (") + + toString(e.getNumber()) + + ")."); + } estream << string(indent, ' ') + "Acceptance information:\n" + string(indent + 2, ' ') + "CTL* formula: E "; @@ -1560,35 +1311,26 @@ void evaluateFormula estream << '\n'; - for (set, - ALLOC(unsigned long int) >::const_iterator - state = system_states.begin(); + for (IntervalList::const_iterator state = system_states.begin(); state != system_states.end(); ++state) { - verifyNumber(*state, round_info.real_emptiness_check_size, - "State identifier out of range"); - estream << string(indent + 2, ' ') + "State " + toString(*state) + ":\n"; - for (set, - ALLOC(unsigned long int) >::const_iterator - algorithm = algorithms.begin(); - algorithm != algorithms.end(); - ++algorithm) + for (IntervalList::const_iterator algorithm = algorithms.begin(); + algorithm != algorithms.end(); + ++algorithm) { - verifyNumber(*algorithm, round_info.number_of_translators, - "No such implementation"); - - algorithm_name = configuration.algorithms[*algorithm].name - ->substr(0, 20); + algorithm_name = configuration.algorithms[*algorithm].name.substr(0, 20); estream << string(indent + 4, ' ') + toString(*algorithm) + ": " + algorithm_name + ':' + string(21 - algorithm_name.length(), ' '); if (!test_results[*algorithm].automaton_stats[formula]. emptiness_check_performed) - estream << "emptiness check not performed\n"; + estream << (configuration.isInternalAlgorithm(*algorithm) + ? "model checking result not available\n" + : "emptiness check not performed\n"); else estream << (test_results[*algorithm].automaton_stats[formula]. emptiness_check_result[*state] @@ -1596,28 +1338,15 @@ void evaluateFormula : "false") << '\n'; } - - if (show_path_eval_results) - { - estream << string(indent + 4, ' ') + "lbtt: "; - if (!test_results[round_info.number_of_translators]. - automaton_stats[formula].emptiness_check_performed) - estream << "no model checking result available\n"; - else - estream << (test_results[round_info.number_of_translators]. - automaton_stats[formula].emptiness_check_result[*state] - ? "true" - : "false") - << '\n'; - } } - estream << '\n'; estream.flush(); } /* ========================================================================= */ -void printFormula(ostream& stream, int indent, bool formula_type) +void printFormula + (ostream& stream, int indent, bool formula_type, + const vector& input_tokens) /* ---------------------------------------------------------------------------- * * Description: Implements the user command `formula', i.e., displays the @@ -1630,6 +1359,9 @@ void printFormula(ostream& stream, int indent, bool formula_type) * indent -- Number of spaces to leave on the left of * the output. * formula_type -- Identifies the formula to be displayed. + * input_tokens -- A reference to a vector of strings + * containing the arguments of the user + * command. * * Returns: Nothing. * @@ -1641,16 +1373,29 @@ void printFormula(ostream& stream, int indent, bool formula_type) Exceptional_ostream estream(&stream, ios::failbit | ios::badbit); - estream << string(indent, ' ') + "Formula:" + string(17, ' '); - round_info.formulae[formula_type ? 2 : 3]->print(estream); - - if (configuration.formula_options.output_mode == Configuration::NNF) + if (input_tokens.size() <= 1 || input_tokens[1] == "normal") { - estream << '\n' + string(indent, ' ') + "In negation normal form: "; - round_info.formulae[formula_type ? 0 : 1]->print(estream); + estream << string(indent, ' '); + round_info.formulae[formula_type ? 2 : 3]->print(estream); } + else if (input_tokens[1] == "nnf") + { + estream << string(indent, ' '); + if (configuration.formula_options.output_mode != Configuration::NNF) + { + ::Ltl::LtlFormula* f + = round_info.formulae[formula_type ? 2 : 3]->nnfClone(); + f->print(estream); + ::Ltl::LtlFormula::destruct(f); + } + else + round_info.formulae[formula_type ? 0 : 1]->print(estream); + } + else + throw CommandErrorException + ("`" + input_tokens[1] + "' is not a valid formula mode."); - estream << "\n\n"; + estream << '\n'; estream.flush(); } @@ -1679,16 +1424,19 @@ void printCommandHelp TokenType command = _NO_INPUT; if (input_tokens.size() > 1) + { command = parseCommand(input_tokens[1]); - - if (command == UNKNOWN) - estream << string(indent, ' ') + "Unknown command (`" + input_tokens[1] - + "').\n\n"; + if (command == UNKNOWN) + estream << string(indent, ' ') + "Unknown command (`" + input_tokens[1] + + "').\n\n"; + } switch (command) { case ALGORITHMS : - estream << string(indent, ' ') + "algorithms\n"; + estream << string(indent, ' ') + "algorithms\n" + + string(indent, ' ') + "implementations\n" + + string(indent, ' ') + "translators\n"; printTextBlock(stream, indent + 4, "List all implementations currently available for " @@ -1794,21 +1542,21 @@ void printCommandHelp "positive formula. Leaving the list of implementations " "or the list of states unspecified will display the " "results for all implementations or all system states, " - "respectively. If using paths as state spaces, the " - "special symbol \"p\" in the implementation list will " - "show also the results obtained with the internal " - "model checking algorithm.", + "respectively.", 78); break; case FORMULA : - estream << string(indent, ' ') + "formula [\"+\"|\"-\"]\n"; + estream << string(indent, ' ') + + "formula [\"+\"|\"-\"] [\"normal\"|\"nnf\"]\n"; printTextBlock(stream, indent + 4, "Display the LTL formula used in this test round for " "generating Büchi automata (\"+\" denotes the positive " - "formula, \"-\" the negated formula). If no formula is " - "specified, show the positive formula.", + "formula, \"-\" the negated formula; \"normal\" and " + "\"nnf\" select between the display mode). If no formula " + "(display mode) is specified, show the positive formula " + "(the formula as generated).", 78); break; @@ -1852,14 +1600,9 @@ void printCommandHelp "Analyze a contradiction in the model checking results " "of two implementations on a formula (\"+\" denotes the " "positive formula, \"-\" the negated formula). If no " - "formula is specified, use the positive formula. When " - "using paths as state spaces, one of implementation " - "identifiers can be replaced by the symbol \"p\" to " - "analyze the result given by some implementation against " - "that given by the internal model checking algorithm. " - "The optional argument `state' can be used to specify " - "the state of the state space in which to do the " - "analysis.", + "formula is specified, use the positive formula. The " + "optional argument `state' can be used to specify the " + "state of the state space in which to do the analysis.", 78); break; @@ -1933,6 +1676,7 @@ void printCommandHelp "evaluate\n" "formula\n" "help\n" + "implementations\n" "inconsistencies\n" "quit\n" "resultanalysis\n" @@ -1940,6 +1684,7 @@ void printCommandHelp "skip\n" "statespace\n" "statistics\n" + "translators\n" "verbosity\n", 78); @@ -1964,7 +1709,6 @@ void printCommandHelp break; } - estream << '\n'; estream.flush(); } @@ -1990,14 +1734,18 @@ void printInconsistencies { Exceptional_ostream estream(&stream, ios::failbit | ios::badbit); - set, ALLOC(unsigned long int) > - number_set; + IntervalList algorithms; if (!configuration.global_options.do_cons_test) throw CommandErrorException("This command is available only when the " "model checking result consistency check is " "enabled."); + if (input_tokens.size() == 1) + input_tokens.push_back("*"); + + parseAlgorithmIdList(input_tokens[1], algorithms); + estream << string(indent, ' ') + "Model checking result consistency check " "results for round " + toString(round_info.current_round) + ":\n"; @@ -2011,23 +1759,13 @@ void printInconsistencies : 2] << '\n'; - if (input_tokens.size() == 1) - input_tokens.push_back("*"); - - parseInterval(input_tokens[1], number_set, 0, - round_info.number_of_translators - 1); - - for (set, - ALLOC(unsigned long int) >::const_iterator - algorithm = number_set.begin(); - algorithm != number_set.end(); ++algorithm) + for (IntervalList::const_iterator algorithm = algorithms.begin(); + algorithm != algorithms.end(); + ++algorithm) { - estream << '\n'; - - verifyNumber(*algorithm, round_info.number_of_translators, - "No such implementation"); - - estream << string(indent, ' ') + configuration.algorithmString(*algorithm) + estream << '\n' + + string(indent, ' ') + + configuration.algorithmString(*algorithm) + '\n'; if (test_results[*algorithm].consistency_check_result == -1) @@ -2075,7 +1813,6 @@ void printInconsistencies } } - estream << '\n'; estream.flush(); } @@ -2100,43 +1837,42 @@ void printTestResults * ------------------------------------------------------------------------- */ { Exceptional_ostream estream(&stream, ios::failbit | ios::badbit); - set, ALLOC(unsigned long int) > - number_set; - - estream << string(indent, ' ') + "Test results for round " - + toString(round_info.current_round) << ":\n"; + IntervalList algorithms; if (input_tokens.size() == 1) input_tokens.push_back("*"); - parseInterval(input_tokens[1], number_set, 0, - round_info.number_of_translators - 1); + parseAlgorithmIdList(input_tokens[1], algorithms); - for (set, - ALLOC(unsigned long int) >::const_iterator - algorithm = number_set.begin(); - algorithm != number_set.end(); ++algorithm) - { - verifyNumber(*algorithm, round_info.number_of_translators, - "No such implementation"); + estream << string(indent, ' ') + "Test results for round " + + toString(round_info.current_round) << ":\n\n"; + if (configuration.global_options.verbosity <= 2) + printStatTableHeader(stream, indent); + + for (IntervalList::const_iterator algorithm = algorithms.begin(); + algorithm != algorithms.end(); + ++algorithm) printAllStats(stream, indent, *algorithm); - if (configuration.global_options.do_comp_test) - { - estream << string(indent, ' ') - + "Model checking result cross-comparison:\n"; - printCrossComparisonStats(stream, indent + 2, *algorithm); - } + if (configuration.global_options.verbosity <= 2) + estream << '\n'; - if (configuration.global_options.do_intr_test) - { - estream << string(indent, ' ') - + "Büchi automata intersection emptiness check:\n"; - printBuchiIntersectionCheckStats(stream, indent + 2, *algorithm); - } - estream.flush(); + if (configuration.global_options.do_comp_test) + { + estream << string(indent, ' ') + + "Model checking result cross-comparison:\n"; + printCrossComparisonStats(stream, indent + 2, algorithms); } + + if (configuration.global_options.do_intr_test) + { + estream << string(indent, ' ') + + "Büchi automata intersection emptiness check:\n"; + printBuchiIntersectionCheckStats(stream, indent + 2, algorithms); + } + + estream.flush(); } /* ========================================================================= */ @@ -2161,8 +1897,7 @@ void printStateSpace * ------------------------------------------------------------------------- */ { Exceptional_ostream estream(&stream, ios::failbit | ios::badbit); - set, ALLOC(unsigned long int) > - number_set; + IntervalList states; if (!configuration.global_options.do_comp_test && !configuration.global_options.do_cons_test) @@ -2175,22 +1910,28 @@ void printStateSpace if (fmt == Graph::NORMAL) { - estream << string(indent, ' ') + "State space information:\n"; - if (input_tokens.size() == 1) input_tokens.push_back("*"); - parseInterval(input_tokens[1], number_set, 0, - round_info.statespace->size() - 1); - - for (set, - ALLOC(unsigned long int) >::const_iterator - state = number_set.begin(); - state != number_set.end(); ++state) + try { - verifyNumber(*state, round_info.statespace->size(), - "State identifier out of range"); + parseIntervalList(input_tokens[1], states, 0, + round_info.statespace->size() - 1); + } + catch (const IntervalRangeException& e) + { + throw CommandErrorException + (string("State identifier out of range (") + + toString(e.getNumber()) + + ")."); + } + estream << string(indent, ' ') + "State space information:\n"; + + for (IntervalList::const_iterator state = states.begin(); + state != states.end(); + ++state) + { estream << string(indent, ' ') + "State " + toString(*state) + (*state == round_info.statespace->initialState() ? " (initial state)" @@ -2205,7 +1946,6 @@ void printStateSpace else if (fmt == Graph::DOT) round_info.statespace->print(stream, indent, Graph::DOT); - estream << '\n'; estream.flush(); } @@ -2263,31 +2003,25 @@ void changeAlgorithmState * * ------------------------------------------------------------------------- */ { - set, ALLOC(unsigned long int) > - algorithms; + IntervalList algorithms; if (input_tokens.size() < 2) input_tokens.push_back("*"); - parseInterval(input_tokens[1], algorithms, 0, - round_info.number_of_translators - 1); + parseAlgorithmIdList(input_tokens[1], algorithms); - for (set, - ALLOC(unsigned long int) >::const_iterator - alg = algorithms.begin(); - alg != algorithms.end(); alg++) + for (IntervalList::const_iterator algorithm = algorithms.begin(); + algorithm != algorithms.end(); + ++algorithm) { - verifyNumber(*alg, round_info.number_of_translators, - "No such implementation"); - printText(string(enable ? "En" : "Dis") + "abling implementation " - + configuration.algorithmString(*alg) + + configuration.algorithmString(*algorithm) + ".\n", 0, 2); - configuration.algorithms[*alg].enabled = enable; + configuration.algorithms[*algorithm].enabled = enable; } round_info.cout << '\n'; diff --git a/lbtt/src/UserCommands.h b/lbtt/src/UserCommands.h index 31167eae2..756717387 100644 --- a/lbtt/src/UserCommands.h +++ b/lbtt/src/UserCommands.h @@ -23,13 +23,17 @@ #include #include #include +#include #include #include #include #include "LbttAlloc.h" #include "BuchiAutomaton.h" #include "Configuration.h" -#include "ProductAutomaton.h" +#include "EdgeContainer.h" +#include "Graph.h" +#include "IntervalList.h" +#include "Product.h" #include "StateSpace.h" using namespace std; @@ -45,38 +49,26 @@ extern Configuration configuration; namespace UserCommands { -void computeProductAutomaton /* Computes a product */ - (ProductAutomaton*& product_automaton, /* automaton. */ - const BuchiAutomaton& buchi_automaton, - pair& last_automaton, - const pair& - new_automaton); +unsigned long int parseAlgorithmId /* Parses an */ + (const string& id); /* implementation + * identifier. + */ + +void parseAlgorithmId /* Parses a list of */ + (const string& ids, IntervalList& algorithms); /* implementation + * identifiers. + */ void printAlgorithmList /* Displays a list of */ (ostream& stream, int indent); /* algorithms used in * the tests. */ -void synchronizePrefixAndCycle /* Synchronizes a prefix */ - (deque /* with a cycle in a */ - ::size_type, /* sequence of graph */ - ALLOC(Graph::Graph /* state identifiers. */ - ::size_type) >& - prefix, - deque - ::size_type, - ALLOC(Graph::Graph - ::size_type) >& - cycle); - void printCrossComparisonAnalysisResults /* Analyzes a */ (ostream& stream, int indent, /* contradiction between */ bool formula_type, /* test results of two */ const vector& /* implementations. */ - input_tokens, - ProductAutomaton*& product_automaton, - pair& - last_product_automaton); + input_tokens); void printConsistencyAnalysisResults /* Analyzes a */ (ostream& stream, int indent, /* contradicition in the */ @@ -87,19 +79,15 @@ void printConsistencyAnalysisResults /* Analyzes a */ void printAutomatonAnalysisResults /* Analyzes a */ (ostream& stream, int indent, /* contradiction in the */ - unsigned long int algorithm1, /* Büchi automata */ - unsigned long int algorithm2); /* intersection + const vector& /* Büchi automata */ + input_tokens); /* intersection * emptiness check. */ void printPath /* Displays information */ (ostream& stream, int indent, /* about a single */ - const deque& - prefix, - const deque& - cycle, + const StateSpace::Path& prefix, /* system execution. */ + const StateSpace::Path& cycle, const StateSpace& path); void printAcceptingCycle /* Displays information */ @@ -108,13 +96,12 @@ void printAcceptingCycle /* Displays information */ ALLOC(Configuration::AlgorithmInformation) > ::size_type algorithm_id, - const deque& - prefix, - const deque& - cycle, - const BuchiAutomaton& automaton); + const BuchiAutomaton::Path& aut_prefix, + const BuchiAutomaton::Path& aut_cycle, + const BuchiAutomaton& automaton, + const StateSpace::Path& path_prefix, + const StateSpace::Path& path_cycle, + const StateSpace& statespace); void printBuchiAutomaton /* Displays information */ (ostream& stream, int indent, /* about a Büchi */ @@ -129,7 +116,9 @@ void evaluateFormula /* Displays information */ void printFormula /* Displays a formula */ (ostream& stream, int indent, /* used for testing. */ - bool formula_type); + bool formula_type, + const vector& + input_tokens); void printCommandHelp /* Displays help about */ (ostream& stream, int indent, /* user commands. */ diff --git a/lbtt/src/main.cc b/lbtt/src/main.cc index a8b02892c..c217ac4f7 100644 --- a/lbtt/src/main.cc +++ b/lbtt/src/main.cc @@ -36,6 +36,7 @@ #include "Random.h" #include "SharedTestData.h" #include "StatDisplay.h" +#include "TempFsysName.h" #include "TestOperations.h" #include "TestRoundInfo.h" #include "TestStatistics.h" @@ -43,17 +44,6 @@ using namespace std; -/****************************************************************************** - * - * Handler for the SIGINT signal. - * - *****************************************************************************/ - -RETSIGTYPE breakHandler(int) -{ - user_break = true; -} - /****************************************************************************** @@ -105,13 +95,120 @@ vector /* Overall test */ +/****************************************************************************** + * + * Functions for allocating and deallocating temporary file names. + * + *****************************************************************************/ + +static void allocateTempFilenames() +{ + using SharedTestData::round_info; + round_info.formula_file_name[0] = new TempFsysName; + round_info.formula_file_name[0]->allocate("lbtt"); + round_info.formula_file_name[1] = new TempFsysName; + round_info.formula_file_name[1]->allocate("lbtt"); + round_info.automaton_file_name = new TempFsysName; + round_info.automaton_file_name->allocate("lbtt"); + round_info.cout_capture_file = new TempFsysName; + round_info.cout_capture_file->allocate("lbtt"); + round_info.cerr_capture_file = new TempFsysName; + round_info.cerr_capture_file->allocate("lbtt"); +} + +static void deallocateTempFilenames() +{ + using SharedTestData::round_info; + if (round_info.formula_file_name[0] != 0) + { + delete round_info.formula_file_name[0]; + round_info.formula_file_name[0] = 0; + } + if (round_info.formula_file_name[1] != 0) + { + delete round_info.formula_file_name[1]; + round_info.formula_file_name[1] = 0; + } + if (round_info.automaton_file_name != 0) + { + delete round_info.automaton_file_name; + round_info.automaton_file_name = 0; + } + if (round_info.cout_capture_file != 0) + { + delete round_info.cout_capture_file; + round_info.cout_capture_file = 0; + } + if (round_info.cerr_capture_file != 0) + { + delete round_info.cerr_capture_file; + round_info.cerr_capture_file = 0; + } +} + + + +/****************************************************************************** + * + * Handler for the SIGINT signal. + * + *****************************************************************************/ + +static void breakHandler(int) +{ + user_break = true; +} + + + +/****************************************************************************** + * + * Default handler for signals that terminate the process. + * + *****************************************************************************/ + +static void abortHandler(int signum) +{ + deallocateTempFilenames(); + struct sigaction s; + s.sa_handler = SIG_DFL; + sigemptyset(&s.sa_mask); + s.sa_flags = 0; + sigaction(signum, &s, static_cast(0)); + raise(signum); +} + + + +/****************************************************************************** + * + * Function for installing signal handlers. + * + *****************************************************************************/ + +static void installSignalHandler(int signum, void (*handler)(int)) +{ + struct sigaction s; + sigaction(signum, static_cast(0), &s); + + if (s.sa_handler != SIG_IGN) + { + s.sa_handler = handler; + sigemptyset(&s.sa_mask); + s.sa_flags = 0; + sigaction(signum, &s, static_cast(0)); + } +} + + + /****************************************************************************** * * Test loop. * *****************************************************************************/ -void testLoop() +bool testLoop() { using namespace DispUtil; using namespace SharedTestData; @@ -136,13 +233,6 @@ void testLoop() ? round_info.next_round_to_run : global_options.number_of_rounds + 1); - if (tmpnam(round_info.formula_file_name[0]) == 0 - || tmpnam(round_info.formula_file_name[1]) == 0 - || tmpnam(round_info.automaton_file_name) == 0 - || tmpnam(round_info.cout_capture_file) == 0 - || tmpnam(round_info.cerr_capture_file) == 0) - throw Exception("unable to allocate names for temporary files"); - /* * If a name for the error log file was given in the configuration, create * the file. @@ -221,19 +311,6 @@ void testLoop() formula_random_state[i] = static_cast(LRAND(0, LONG_MAX)); #endif /* HAVE_RAND48 */ - /* - * If using paths as state spaces, include the internal model checking - * algorithm in the set of algorithms. - */ - - if (global_options.statespace_generation_mode & Configuration::PATH) - { - Configuration::AlgorithmInformation lbtt_info - = {new string("lbtt"), new string(), new string(), true}; - - configuration.algorithms.push_back(lbtt_info); - } - /* * Intialize the vector for storing the test results for each * implementation and the vector for collecting overall test statistics for @@ -271,21 +348,9 @@ void testLoop() = (round_info.current_round < round_info.next_round_to_run); if (!round_info.skip) - { - if (!printText(string("Round ") + toString(round_info.current_round) - + " of " + toString(global_options.number_of_rounds) - + "\n\n", - 2)) - { - if (global_options.verbosity == 1) - { - if (round_info.current_round > 1) - round_info.cout << ' '; - round_info.cout << round_info.current_round; - round_info.cout.flush(); - } - } - } + printText(string("Round ") + toString(round_info.current_round) + + " of " + toString(global_options.number_of_rounds) + "\n\n", + 2); try { @@ -399,7 +464,7 @@ void testLoop() if (user_break) { - printText("[User break]\n\n", 2, 4); + printText("[User break]\n\n", 1, 4); throw UserBreakException(); } @@ -414,12 +479,17 @@ void testLoop() if (global_options.statespace_generation_mode & Configuration::PATH && (global_options.do_cons_test || global_options.do_comp_test) - && (!test_results[round_info.number_of_translators]. - automaton_stats[0].emptiness_check_performed)) + && (!test_results[round_info.number_of_translators - 1]. + automaton_stats[0].emptiness_check_performed) + && configuration.algorithms[round_info.number_of_translators - 1]. + enabled) verifyFormulaOnPath(); if (!round_info.error) { + if (global_options.verbosity == 2) + ::StatDisplay::printStatTableHeader(round_info.cout, 4); + unsigned long int num_enabled_implementations = 0; for (unsigned long int algorithm_id = 0; @@ -431,81 +501,61 @@ void testLoop() num_enabled_implementations++; + if (configuration.isInternalAlgorithm(algorithm_id)) + continue; + printText(configuration.algorithmString(algorithm_id) + '\n', - 2, 4); + 3, 4); for (int counter = 0; counter < 2; counter++) { if (user_break) { - printText("[User break]\n\n", 2, 4); + printText("[User break]\n\n", 1, 4); throw UserBreakException(); } - printText(string(counter == 1 ? "Negated" : "Positive") - + " formula:\n", - 2, - 6); + if (global_options.verbosity == 1 + || global_options.verbosity == 2) + { + if (counter == 1) + round_info.cout << '\n'; + if (global_options.verbosity == 1) + round_info.cout << round_info.current_round << ' '; + else + round_info.cout << string(4, ' '); + changeStreamFormatting(cout, 2, 0, ios::right); + round_info.cout << algorithm_id << ' '; + restoreStreamFormatting(cout); + round_info.cout << (counter == 0 ? '+' : '-') << ' '; + round_info.cout.flush(); + } + else + printText(string(counter == 1 ? "Negated" : "Positive") + + " formula:\n", + 3, + 6); try { - try - { - round_info.product_automaton = 0; + /* + * Generate a Büchi automaton using the current algorithm. + * `counter' determines the formula which is to be + * translated into an automaton; 0 denotes the positive and + * 1 the negated formula. + */ + generateBuchiAutomaton(counter, algorithm_id); + + if (global_options.do_cons_test || global_options.do_comp_test) + { /* - * Generate a Büchi automaton using the current algorithm. - * `counter' determines the formula which is to be - * translated into an automaton; 0 denotes the positive and - * 1 the negated formula. + * Find the system states from which an accepting + * execution cycle can be reached by checking the product + * automaton for emptiness. */ - generateBuchiAutomaton(counter, algorithm_id); - - if (global_options.do_cons_test - || global_options.do_comp_test) - { - /* - * Compute the product of the Büchi automaton with the - * state space. - */ - - generateProductAutomaton(counter, algorithm_id); - - /* - * Find the system states from which an accepting - * execution cycle can be reached by checking the product - * automaton for emptiness. - */ - - performEmptinessCheck(counter, algorithm_id); - - /* - * If a product automaton was computed in this test round - * (it might have not if the emptiness checking result was - * already available), release the memory allocated for - * the product automaton. - */ - - if (round_info.product_automaton != 0) - { - printText("", 4, 8); - - delete round_info.product_automaton; - round_info.product_automaton = 0; - - printText(" ok\n", 4); - } - } - } - catch (...) - { - if (round_info.product_automaton != 0) - { - delete round_info.product_automaton; - round_info.product_automaton = 0; - } - throw; + performEmptinessCheck(counter, algorithm_id); } } catch (const BuchiAutomatonGenerationException&) @@ -541,7 +591,13 @@ void testLoop() emptiness_check_performed) performConsistencyCheck(algorithm_id); - printText("\n", 2); + printText("\n", 1); + } + + if (global_options.verbosity == 2) + { + round_info.cout << '\n'; + round_info.cout.flush(); } if (num_enabled_implementations > 0) @@ -553,10 +609,7 @@ void testLoop() * results obtained using the different algorithms. */ - if (num_enabled_implementations >= 2 - || (num_enabled_implementations == 1 - && global_options.statespace_generation_mode - & Configuration::PATH)) + if (num_enabled_implementations >= 2) compareResults(); } @@ -592,25 +645,17 @@ void testLoop() * the testing should be paused to wait for user commands. */ + if (round_info.error) + round_info.all_tests_successful = false; + if (round_info.error && global_options.interactive == Configuration::ONERROR) round_info.next_round_to_stop = round_info.current_round; if (round_info.next_round_to_stop == round_info.current_round) - { - if (global_options.verbosity == 1) - { - round_info.cout << '\n'; - round_info.cout.flush(); - } - ::UserCommandInterface::executeUserCommands(); - } } - for (int i = 0; i < 2; i++) - removeFile(round_info.formula_file_name[i], 2); - if (round_info.path_iterator != 0) delete round_info.path_iterator; else if (round_info.statespace != 0) @@ -661,11 +706,13 @@ void testLoop() round_info.transcript_file.close(); } - if (global_options.verbosity >= 1) + if (global_options.verbosity >= 2) printCollectiveStats(cout, 0); if (round_info.formula_input_file.is_open()) round_info.formula_input_file.close(); + + return round_info.all_tests_successful; } @@ -688,15 +735,27 @@ int main(int argc, char* argv[]) if (!e.line_info.empty()) cerr << ":" << configuration.global_options.cfg_filename << ":" << e.line_info; - cerr << ": " << e.what() << endl; - exit(-1); + cerr << ":" << e.what() << endl; + exit(2); } if (configuration.global_options.verbosity >= 3) configuration.print(cout); user_break = false; - signal(SIGINT, breakHandler); + + installSignalHandler(SIGHUP, abortHandler); + installSignalHandler(SIGINT, + configuration.global_options.handle_breaks + ? breakHandler + : abortHandler); + installSignalHandler(SIGQUIT, abortHandler); + installSignalHandler(SIGABRT, abortHandler); + installSignalHandler(SIGPIPE, abortHandler); + installSignalHandler(SIGALRM, abortHandler); + installSignalHandler(SIGTERM, abortHandler); + installSignalHandler(SIGUSR1, abortHandler); + installSignalHandler(SIGUSR2, abortHandler); #ifdef HAVE_OBSTACK_H obstack_alloc_failed_handler = &ObstackAllocator::failure; @@ -708,18 +767,26 @@ int main(int argc, char* argv[]) try { - testLoop(); + allocateTempFilenames(); + if (!testLoop()) + { + deallocateTempFilenames(); + return 1; + } } catch (const Exception& e) { - cerr << argv[0] << ": " << e.what() << endl; - exit(-1); + deallocateTempFilenames(); + cerr << endl << argv[0] << ": " << e.what() << endl; + exit(3); } catch (const bad_alloc&) { - cerr << argv[0] << ": out of memory" << endl; - exit(-1); + deallocateTempFilenames(); + cerr << endl << argv[0] << ": out of memory" << endl; + exit(3); } + deallocateTempFilenames(); return 0; } diff --git a/lbtt/src/translate.cc b/lbtt/src/translate.cc index 645e3a37f..eab9a9037 100644 --- a/lbtt/src/translate.cc +++ b/lbtt/src/translate.cc @@ -45,6 +45,17 @@ char** command_line_arguments; +/****************************************************************************** + * + * Pointer to an object providing operations for translating a formula into an + * automaton. + * + *****************************************************************************/ + +static TranslatorInterface* translator = 0; + + + /****************************************************************************** * * A function for showing warnings to the user. @@ -60,21 +71,46 @@ void printWarning(const string& msg) /****************************************************************************** * - * Signal handler for debugging purposes. + * Handler for SIGINT, SIGQUIT, SIGABRT and SIGTERM. * *****************************************************************************/ -RETSIGTYPE signalHandler(int signal_number) +static void signalHandler(int signal_number) { - cerr << string(command_line_arguments[0]) + ": received signal " - << signal_number - << endl; - signal(signal_number, SIG_DFL); + if (translator != 0) + delete translator; + struct sigaction s; + s.sa_handler = SIG_DFL; + sigemptyset(&s.sa_mask); + s.sa_flags = 0; + sigaction(signal_number, &s, static_cast(0)); raise(signal_number); } +/****************************************************************************** + * + * Function for installing signal handlers. + * + *****************************************************************************/ + +static void installSignalHandler(int signum) +{ + struct sigaction s; + sigaction(signum, static_cast(0), &s); + + if (s.sa_handler != SIG_IGN) + { + s.sa_handler = signalHandler; + sigemptyset(&s.sa_mask); + s.sa_flags = 0; + sigaction(signum, &s, static_cast(0)); + } +} + + + /****************************************************************************** * * Main function. @@ -83,7 +119,7 @@ RETSIGTYPE signalHandler(int signal_number) int main(int argc, char** argv) { - typedef enum {OPT_HELP = 'h', OPT_LBT, OPT_SPIN, OPT_VERSION = 'v'} + typedef enum {OPT_HELP = 'h', OPT_LBT, OPT_SPIN, OPT_VERSION = 'V'} OptionType; static OPTIONSTRUCT command_line_options[] = @@ -100,12 +136,10 @@ int main(int argc, char** argv) opterr = 1; int opttype, option_index; - TranslatorInterface* translator = 0; - do { option_index = 0; - opttype = getopt_long(argc, argv, "hv", command_line_options, + opttype = getopt_long(argc, argv, "hV", command_line_options, &option_index); switch (opttype) @@ -115,8 +149,8 @@ int main(int argc, char** argv) << " [translator] [command line for translator] [formula " "file] [automaton file]\n" "General options:\n" - " --h, --help Show this help\n" - " --v, --version Show version and exit\n\n" + " -h, --help Show this help\n" + " -V, --version Show version and exit\n\n" "Translator options:\n" " --lbt lbt\n" " --spin Spin\n" @@ -168,16 +202,15 @@ int main(int argc, char** argv) int exitstatus = 0; - signal(SIGHUP, signalHandler); - signal(SIGINT, signalHandler); - signal(SIGQUIT, signalHandler); - signal(SIGILL, signalHandler); - signal(SIGABRT, signalHandler); - signal(SIGFPE, signalHandler); - signal(SIGSEGV, signalHandler); - signal(SIGPIPE, signalHandler); - signal(SIGALRM, signalHandler); - signal(SIGTERM, signalHandler); + installSignalHandler(SIGHUP); + installSignalHandler(SIGINT); + installSignalHandler(SIGQUIT); + installSignalHandler(SIGABRT); + installSignalHandler(SIGPIPE); + installSignalHandler(SIGALRM); + installSignalHandler(SIGTERM); + installSignalHandler(SIGUSR1); + installSignalHandler(SIGUSR2); ::Ltl::LtlFormula* formula(0);